618 lines
21 KiB
Java
618 lines
21 KiB
Java
package com.hypixel.hytale.server.spawning;
|
|
|
|
import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap;
|
|
import com.hypixel.hytale.logger.HytaleLogger;
|
|
import com.hypixel.hytale.math.random.RandomExtra;
|
|
import com.hypixel.hytale.math.util.ChunkUtil;
|
|
import com.hypixel.hytale.math.util.MathUtil;
|
|
import com.hypixel.hytale.math.vector.Vector3d;
|
|
import com.hypixel.hytale.math.vector.Vector3f;
|
|
import com.hypixel.hytale.protocol.BlockMaterial;
|
|
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
|
|
import com.hypixel.hytale.server.core.asset.type.fluid.Fluid;
|
|
import com.hypixel.hytale.server.core.asset.type.model.config.Model;
|
|
import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset;
|
|
import com.hypixel.hytale.server.core.modules.collision.CollisionModule;
|
|
import com.hypixel.hytale.server.core.modules.collision.CollisionResult;
|
|
import com.hypixel.hytale.server.core.modules.collision.WorldUtil;
|
|
import com.hypixel.hytale.server.core.universe.world.World;
|
|
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
|
|
import com.hypixel.hytale.server.core.universe.world.chunk.environment.EnvironmentColumn;
|
|
import com.hypixel.hytale.server.npc.util.NPCPhysicsMath;
|
|
import com.hypixel.hytale.server.npc.util.expression.ExecutionContext;
|
|
import com.hypixel.hytale.server.npc.util.expression.Scope;
|
|
import com.hypixel.hytale.server.spawning.suppression.SuppressionSpanHelper;
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
import java.util.logging.Level;
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
|
|
public class SpawningContext {
|
|
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
|
private static final BlockTypeAssetMap<String, BlockType> BLOCK_ASSET_MAP = BlockType.getAssetMap();
|
|
@Nullable
|
|
public World world;
|
|
@Nullable
|
|
public WorldChunk worldChunk;
|
|
public int xBlock;
|
|
public int zBlock;
|
|
public double ySpawnHint;
|
|
public int groundLevel;
|
|
public int groundBlockId;
|
|
public int groundRotation;
|
|
@Nullable
|
|
public BlockType groundBlockType;
|
|
public int groundFluidId;
|
|
@Nullable
|
|
public Fluid groundFluid;
|
|
public int ySpanMin;
|
|
public int ySpanMax;
|
|
public int yBlock;
|
|
public int waterLevel;
|
|
public int airHeight;
|
|
public double ySpawnMin;
|
|
public double xSpawn;
|
|
public double zSpawn;
|
|
public double ySpawn;
|
|
private int environmentIndex = Integer.MIN_VALUE;
|
|
private int minSpawnSpanHeight = Integer.MAX_VALUE;
|
|
public double yaw;
|
|
public double pitch;
|
|
public double roll;
|
|
@Nullable
|
|
private ISpawnableWithModel spawnable;
|
|
@Nullable
|
|
private Model spawnModel;
|
|
@Nullable
|
|
private Scope modifierScope;
|
|
private final CollisionResult collisionResult = new CollisionResult();
|
|
private final Vector3d position = new Vector3d();
|
|
private final ExecutionContext executionContext = new ExecutionContext();
|
|
private SpawningContext.SpawnSpan[] spawnSpans = new SpawningContext.SpawnSpan[4];
|
|
private int spawnSpansUsed;
|
|
private int currentSpawnSpanIndex;
|
|
private static final int SOLID_BLOCK = -1;
|
|
private static final int EMPTY_BLOCK = 0;
|
|
private static final int FLUID_BLOCK = 1;
|
|
|
|
public SpawningContext() {
|
|
for (int i = 0; i < this.spawnSpans.length; i++) {
|
|
this.spawnSpans[i] = new SpawningContext.SpawnSpan();
|
|
}
|
|
|
|
this.spawnSpansUsed = 0;
|
|
}
|
|
|
|
public boolean setSpawnable(@Nonnull ISpawnableWithModel spawnable) {
|
|
return this.setSpawnable(spawnable, false);
|
|
}
|
|
|
|
public boolean setSpawnable(@Nonnull ISpawnableWithModel spawnable, boolean maxScale) {
|
|
if (spawnable == this.spawnable) {
|
|
return true;
|
|
} else {
|
|
this.spawnable = spawnable;
|
|
|
|
String modelName;
|
|
try {
|
|
this.executionContext.setScope(spawnable.createExecutionScope());
|
|
this.modifierScope = this.spawnable.createModifierScope(this.executionContext);
|
|
modelName = spawnable.getSpawnModelName(this.executionContext, this.modifierScope);
|
|
} catch (Throwable var5) {
|
|
LOGGER.at(Level.WARNING).log("Can't set role in spawning context %s: %s", spawnable.getIdentifier(), var5.getMessage());
|
|
spawnable.markNeedsReload();
|
|
this.spawnable = null;
|
|
return false;
|
|
}
|
|
|
|
if (!this.setModel(modelName, maxScale)) {
|
|
LOGGER.at(Level.WARNING).log("Can't set model in spawning context %s: %s", spawnable.getIdentifier(), modelName);
|
|
spawnable.markNeedsReload();
|
|
this.spawnable = null;
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean setModel(@Nullable String modelName, boolean maxScale) {
|
|
if (modelName == null) {
|
|
this.clearModel();
|
|
return false;
|
|
} else {
|
|
String currentModelName = this.spawnModel != null ? this.spawnModel.getModelAssetId() : null;
|
|
if (modelName.equals(currentModelName)) {
|
|
return true;
|
|
} else {
|
|
ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(modelName);
|
|
Model model = null;
|
|
if (modelAsset != null) {
|
|
model = maxScale ? Model.createScaledModel(modelAsset, modelAsset.getMaxScale()) : Model.createRandomScaleModel(modelAsset);
|
|
}
|
|
|
|
if (model != null && model.getBoundingBox() != null) {
|
|
this.spawnModel = model;
|
|
this.minSpawnSpanHeight = MathUtil.ceil(model.getBoundingBox().height() + 0.2F);
|
|
return true;
|
|
} else {
|
|
this.clearModel();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void clearModel() {
|
|
this.spawnModel = null;
|
|
this.minSpawnSpanHeight = Integer.MAX_VALUE;
|
|
}
|
|
|
|
public void newModel() {
|
|
if (this.spawnModel != null) {
|
|
ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(this.spawnModel.getModelAssetId());
|
|
this.spawnModel = Model.createRandomScaleModel(modelAsset);
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
public Model getModel() {
|
|
return this.spawnModel;
|
|
}
|
|
|
|
public void setChunk(@Nonnull WorldChunk worldChunk, int environmentIndex) {
|
|
this.worldChunk = worldChunk;
|
|
this.world = worldChunk.getWorld();
|
|
this.environmentIndex = environmentIndex;
|
|
this.commonInit();
|
|
}
|
|
|
|
public boolean setColumn(int x, int z, int yHint, @Nonnull int[] yRange) {
|
|
this.xBlock = x;
|
|
this.zBlock = z;
|
|
this.ySpawnHint = -1.0;
|
|
this.spawnSpansUsed = 0;
|
|
int min = Math.max(0, yHint + yRange[0]);
|
|
int max = Math.min(319, yHint + yRange[1]);
|
|
this.splitRangeToSpawnSpans(min, max);
|
|
return this.spawnSpansUsed > 0;
|
|
}
|
|
|
|
public boolean setColumn(int x, int z, int yHint, @Nonnull int[] yRange, @Nonnull SuppressionSpanHelper suppressionHelper) {
|
|
this.xBlock = x;
|
|
this.zBlock = z;
|
|
this.ySpawnHint = -1.0;
|
|
this.spawnSpansUsed = 0;
|
|
int y = Math.max(0, yHint + yRange[0]);
|
|
int hintMax = Math.min(319, yHint + yRange[1]);
|
|
|
|
while (y <= hintMax) {
|
|
int min = suppressionHelper.adjustSpawnRangeMin(y);
|
|
if (min >= hintMax) {
|
|
break;
|
|
}
|
|
|
|
int max = Math.min(suppressionHelper.adjustSpawnRangeMax(min, hintMax), hintMax);
|
|
y = max + 1;
|
|
this.splitRangeToSpawnSpans(min, max);
|
|
}
|
|
|
|
return this.spawnSpansUsed > 0;
|
|
}
|
|
|
|
public void setColumn(int x, int z, @Nonnull SuppressionSpanHelper suppressionHelper) {
|
|
this.xBlock = x;
|
|
this.zBlock = z;
|
|
this.ySpawnHint = -1.0;
|
|
this.spawnSpansUsed = 0;
|
|
EnvironmentColumn column = this.worldChunk.getBlockChunk().getEnvironmentColumn(this.xBlock, this.zBlock);
|
|
|
|
for (int i = column.indexOf(0); i < column.size(); i++) {
|
|
int envId = column.getValue(i);
|
|
if (envId == this.environmentIndex) {
|
|
int min = Math.max(0, column.getValueMin(i));
|
|
if (min > 320) {
|
|
break;
|
|
}
|
|
|
|
int adjustedMin = suppressionHelper.adjustSpawnRangeMin(min);
|
|
int max = column.getValueMax(i);
|
|
if (adjustedMin > max) {
|
|
i = column.indexOf(adjustedMin) - 1;
|
|
} else {
|
|
int adjustedMax = suppressionHelper.adjustSpawnRangeMax(adjustedMin, max);
|
|
this.splitRangeToSpawnSpans(adjustedMin, Math.min(adjustedMax, 319));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
public Scope getModifierScope() {
|
|
return this.modifierScope;
|
|
}
|
|
|
|
public boolean set(@Nonnull World world, double x, double y, double z) {
|
|
if (this.minSpawnSpanHeight >= Integer.MAX_VALUE) {
|
|
throw new IllegalStateException("minSpawnSpanHeight not set - forgot to set model or role?");
|
|
} else {
|
|
this.xBlock = MathUtil.floor(x);
|
|
this.zBlock = MathUtil.floor(z);
|
|
this.ySpawnHint = y;
|
|
this.worldChunk = world.getChunkIfLoaded(ChunkUtil.indexChunkFromBlock(this.xBlock, this.zBlock));
|
|
if (this.worldChunk == null) {
|
|
return false;
|
|
} else {
|
|
this.xBlock = ChunkUtil.localCoordinate(this.xBlock);
|
|
this.zBlock = ChunkUtil.localCoordinate(this.zBlock);
|
|
this.environmentIndex = Integer.MIN_VALUE;
|
|
this.world = world;
|
|
this.commonInit();
|
|
int yInt = MathUtil.floor(y);
|
|
this.spawnSpansUsed = 0;
|
|
EnvironmentColumn environmentColumn = this.worldChunk.getBlockChunk().getEnvironmentColumn(this.xBlock, this.zBlock);
|
|
int rangeMin = Math.max(0, environmentColumn.getMin(yInt));
|
|
int rangeMax = Math.min(environmentColumn.getMax(yInt), 319);
|
|
this.splitRangeToSpawnSpans(rangeMin, rangeMax);
|
|
if (this.spawnSpansUsed == 0) {
|
|
return false;
|
|
} else {
|
|
int distance = Integer.MAX_VALUE;
|
|
int chosenIndex = -1;
|
|
|
|
for (int index = 0; index < this.spawnSpansUsed; index++) {
|
|
SpawningContext.SpawnSpan spawnSpan = this.spawnSpans[index];
|
|
int currentDistance;
|
|
if (spawnSpan.top < yInt) {
|
|
currentDistance = yInt - spawnSpan.top;
|
|
} else {
|
|
if (spawnSpan.bottom <= yInt) {
|
|
chosenIndex = index;
|
|
break;
|
|
}
|
|
|
|
currentDistance = spawnSpan.bottom - yInt;
|
|
}
|
|
|
|
if (currentDistance < distance) {
|
|
chosenIndex = index;
|
|
distance = currentDistance;
|
|
}
|
|
}
|
|
|
|
return this.selectSpawnSpan(chosenIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void deleteCurrentSpawnSpan() {
|
|
if (--this.spawnSpansUsed > this.currentSpawnSpanIndex) {
|
|
SpawningContext.SpawnSpan temp = this.spawnSpans[this.currentSpawnSpanIndex];
|
|
System.arraycopy(
|
|
this.spawnSpans, this.currentSpawnSpanIndex + 1, this.spawnSpans, this.currentSpawnSpanIndex, this.spawnSpansUsed - this.currentSpawnSpanIndex
|
|
);
|
|
this.spawnSpans[this.spawnSpansUsed] = temp;
|
|
}
|
|
}
|
|
|
|
public boolean selectRandomSpawnSpan() {
|
|
return this.spawnSpansUsed > 0 && this.selectSpawnSpan(ThreadLocalRandom.current().nextInt(0, this.spawnSpansUsed));
|
|
}
|
|
|
|
private boolean selectSpawnSpan(int index) {
|
|
if (index >= 0 && index < this.spawnSpansUsed) {
|
|
this.currentSpawnSpanIndex = index;
|
|
SpawningContext.SpawnSpan spawnSpan = this.spawnSpans[this.currentSpawnSpanIndex];
|
|
this.ySpanMin = spawnSpan.bottom;
|
|
this.ySpanMax = spawnSpan.top;
|
|
this.waterLevel = spawnSpan.waterLevel;
|
|
this.groundLevel = spawnSpan.groundLevel;
|
|
if (this.waterLevel != -1) {
|
|
this.airHeight = -1;
|
|
if (this.waterLevel < 319) {
|
|
int blockId = this.worldChunk.getBlockChunk().getBlock(this.xBlock, this.waterLevel + 1, this.zBlock);
|
|
int fluidId = this.worldChunk.getFluidId(this.xBlock, this.waterLevel + 1, this.zBlock);
|
|
if (blockId == 0 && fluidId == 0 || BLOCK_ASSET_MAP.getAsset(blockId).getMaterial() == BlockMaterial.Empty && fluidId == 0) {
|
|
this.airHeight = this.waterLevel + 1;
|
|
}
|
|
}
|
|
} else {
|
|
this.airHeight = this.groundLevel + 1;
|
|
}
|
|
|
|
this.yBlock = this.groundLevel;
|
|
this.groundBlockId = this.worldChunk.getBlock(this.xBlock, this.groundLevel, this.zBlock);
|
|
this.groundRotation = this.worldChunk.getRotationIndex(this.xBlock, this.groundLevel, this.zBlock);
|
|
this.groundBlockType = BLOCK_ASSET_MAP.getAsset(this.groundBlockId);
|
|
this.groundFluidId = this.worldChunk.getFluidId(this.xBlock, this.groundLevel, this.zBlock);
|
|
this.groundFluid = Fluid.getAssetMap().getAsset(this.groundFluidId);
|
|
this.ySpawnMin = this.yBlock + NPCPhysicsMath.blockHeight(this.groundBlockType, this.groundRotation);
|
|
this.xSpawn = ChunkUtil.minBlock(this.worldChunk.getX()) + this.xBlock + 0.5;
|
|
this.zSpawn = ChunkUtil.minBlock(this.worldChunk.getZ()) + this.zBlock + 0.5;
|
|
this.ySpawn = this.ySpawnMin - this.spawnModel.getBoundingBox().min.y;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private void splitRangeToSpawnSpans(int min, int max) {
|
|
int span = 0;
|
|
int waterLevel = -1;
|
|
int groundLevel = -1;
|
|
|
|
while (min <= max) {
|
|
int kind = this.isSpawnSpanBlock(this.xBlock, min, this.zBlock);
|
|
if (kind != -1) {
|
|
if (kind == 1) {
|
|
waterLevel = min;
|
|
}
|
|
|
|
span++;
|
|
} else {
|
|
if (span > this.minSpawnSpanHeight) {
|
|
this.addSpawnSpan(min, span, groundLevel, waterLevel);
|
|
}
|
|
|
|
span = 0;
|
|
waterLevel = -1;
|
|
groundLevel = min;
|
|
}
|
|
|
|
min++;
|
|
}
|
|
|
|
if (span > this.minSpawnSpanHeight) {
|
|
this.addSpawnSpan(min, span, groundLevel, waterLevel);
|
|
}
|
|
}
|
|
|
|
private void addSpawnSpan(int top, int span, int groundLevel, int waterLevel) {
|
|
if (groundLevel == -1) {
|
|
groundLevel = top - span;
|
|
|
|
for (int blockType = this.isSpawnSpanBlock(this.xBlock, groundLevel, this.zBlock);
|
|
groundLevel >= 0 && blockType != -1;
|
|
blockType = this.isSpawnSpanBlock(this.xBlock, --groundLevel, this.zBlock)
|
|
) {
|
|
if (waterLevel == -1 && blockType == 1) {
|
|
waterLevel = groundLevel;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (waterLevel == top - 1) {
|
|
while (waterLevel < 319 && this.isSpawnSpanBlock(this.xBlock, waterLevel + 1, this.zBlock) == 1) {
|
|
waterLevel++;
|
|
}
|
|
}
|
|
|
|
if (this.spawnSpans.length <= this.spawnSpansUsed) {
|
|
SpawningContext.SpawnSpan[] newSpans = new SpawningContext.SpawnSpan[this.spawnSpansUsed + 4];
|
|
System.arraycopy(this.spawnSpans, 0, newSpans, 0, this.spawnSpansUsed);
|
|
|
|
for (int i = this.spawnSpansUsed; i < newSpans.length; i++) {
|
|
newSpans[i] = new SpawningContext.SpawnSpan();
|
|
}
|
|
|
|
this.spawnSpans = newSpans;
|
|
}
|
|
|
|
SpawningContext.SpawnSpan spawnSpan = this.spawnSpans[this.spawnSpansUsed++];
|
|
spawnSpan.bottom = top - span;
|
|
spawnSpan.top = top - 1;
|
|
spawnSpan.waterLevel = waterLevel;
|
|
spawnSpan.groundLevel = groundLevel;
|
|
}
|
|
|
|
private int isSpawnSpanBlock(int x, int y, int z) {
|
|
int block = this.worldChunk.getBlock(x, y, z);
|
|
if (block != 0 && BLOCK_ASSET_MAP.getAsset(block).getMaterial() != BlockMaterial.Empty) {
|
|
return -1;
|
|
} else {
|
|
return this.worldChunk.getFluidId(x, y, z) == 0 ? 0 : 1;
|
|
}
|
|
}
|
|
|
|
private void commonInit() {
|
|
this.yaw = RandomExtra.randomRange(0.0F, (float) (Math.PI * 2));
|
|
this.pitch = 0.0;
|
|
this.roll = 0.0;
|
|
}
|
|
|
|
@Nonnull
|
|
public SpawnTestResult canSpawn(boolean testOverlapBlocks, boolean testOverlapEntities) {
|
|
SpawnTestResult spawnTestResult = SpawnTestResult.TEST_OK;
|
|
if (testOverlapBlocks) {
|
|
spawnTestResult = this.intersectsBlock();
|
|
if (spawnTestResult != SpawnTestResult.TEST_OK) {
|
|
return spawnTestResult;
|
|
}
|
|
}
|
|
|
|
if (testOverlapEntities) {
|
|
spawnTestResult = this.intersectsEntity();
|
|
}
|
|
|
|
return spawnTestResult;
|
|
}
|
|
|
|
@Nonnull
|
|
public SpawnTestResult canSpawn() {
|
|
return this.canSpawn(true, true);
|
|
}
|
|
|
|
@Nonnull
|
|
private SpawnTestResult intersectsEntity() {
|
|
return SpawnTestResult.TEST_OK;
|
|
}
|
|
|
|
@Nonnull
|
|
private SpawnTestResult intersectsBlock() {
|
|
if (this.worldChunk != null && this.spawnModel != null && this.spawnable != null) {
|
|
return this.spawnable.canSpawn(this);
|
|
} else {
|
|
throw new IllegalStateException("SpawningContext initialized");
|
|
}
|
|
}
|
|
|
|
public static boolean isWaterBlock(int fluidId) {
|
|
return fluidId != 0;
|
|
}
|
|
|
|
public int getWaterLevel() {
|
|
return this.waterLevel;
|
|
}
|
|
|
|
public int getAirHeight() {
|
|
return this.airHeight;
|
|
}
|
|
|
|
public boolean isInsideSpan(double y) {
|
|
return y >= this.ySpanMin && y <= this.ySpanMax;
|
|
}
|
|
|
|
public boolean isInWater(float minDepth) {
|
|
int depth = this.waterLevel - this.groundLevel - 1;
|
|
if (depth < 0) {
|
|
return false;
|
|
} else {
|
|
int roundedDepth = MathUtil.fastCeil(minDepth);
|
|
if (depth < roundedDepth) {
|
|
return false;
|
|
} else {
|
|
double ySpawn = this.waterLevel - roundedDepth;
|
|
if (!this.isInsideSpan(ySpawn)) {
|
|
return false;
|
|
} else {
|
|
this.ySpawn = ySpawn - this.spawnModel.getBoundingBox().min.y;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean isOnSolidGround() {
|
|
if (isWaterBlock(this.groundFluidId)) {
|
|
return false;
|
|
} else {
|
|
this.ySpawn = this.ySpawnMin - this.spawnModel.getBoundingBox().min.y;
|
|
int ySpawnBlock = MathUtil.floor(this.ySpawnMin);
|
|
if (ySpawnBlock != this.yBlock) {
|
|
BlockType blockType = this.worldChunk.getBlockType(this.xBlock, ySpawnBlock, this.zBlock);
|
|
int fluidId = this.worldChunk.getFluidId(this.xBlock, ySpawnBlock, this.zBlock);
|
|
int rotation = this.worldChunk.getRotationIndex(this.xBlock, ySpawnBlock, this.zBlock);
|
|
if (WorldUtil.isEmptyOnlyBlock(blockType, fluidId) || fluidId != 0) {
|
|
return this.isInsideSpan(this.ySpawn);
|
|
} else if (WorldUtil.isSolidOnlyBlock(blockType, fluidId)) {
|
|
this.ySpawn = ySpawnBlock + NPCPhysicsMath.blockHeight(blockType, rotation) - this.spawnModel.getBoundingBox().min.y;
|
|
return this.isInsideSpan(this.ySpawn);
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
return this.ySpawn >= this.ySpanMin - 1 && this.ySpawn <= this.ySpanMax;
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean isInAir(double height) {
|
|
this.ySpawn = this.getAirHeight() + height - this.spawnModel.getBoundingBox().min.y;
|
|
return this.ySpawn < 320.0 && this.isInsideSpan(this.ySpawn);
|
|
}
|
|
|
|
public boolean validatePosition(int invalidMaterials) {
|
|
if (this.spawnModel == null) {
|
|
return false;
|
|
} else {
|
|
this.position.assign(this.xSpawn, this.ySpawn, this.zSpawn);
|
|
return CollisionModule.get()
|
|
.validatePosition(
|
|
this.world,
|
|
this.spawnModel.getBoundingBox(),
|
|
this.position,
|
|
invalidMaterials,
|
|
null,
|
|
(_this, collisionCode, collision, collisionConfig) -> collisionConfig.blockId != -1,
|
|
this.collisionResult
|
|
)
|
|
!= -1;
|
|
}
|
|
}
|
|
|
|
public boolean canBreathe(boolean breathesInAir, boolean breathesInWater) {
|
|
if (this.spawnModel == null) {
|
|
return false;
|
|
} else {
|
|
return !breathesInAir && this.ySpawn + this.spawnModel.getEyeHeight() >= this.airHeight
|
|
? false
|
|
: breathesInWater || !(this.waterLevel + 1 - this.ySpawn >= this.spawnModel.getEyeHeight());
|
|
}
|
|
}
|
|
|
|
public void release() {
|
|
this.groundBlockType = null;
|
|
this.world = null;
|
|
this.worldChunk = null;
|
|
}
|
|
|
|
public void releaseFull() {
|
|
this.release();
|
|
this.spawnable = null;
|
|
this.modifierScope = null;
|
|
this.spawnModel = null;
|
|
this.executionContext.setScope(null);
|
|
}
|
|
|
|
@Nonnull
|
|
public ExecutionContext getExecutionContext() {
|
|
return this.executionContext;
|
|
}
|
|
|
|
@Nonnull
|
|
public Vector3d newPosition() {
|
|
return new Vector3d(this.xSpawn, this.ySpawn, this.zSpawn);
|
|
}
|
|
|
|
@Nonnull
|
|
public Vector3f newRotation() {
|
|
return new Vector3f((float)this.pitch, (float)this.yaw, (float)this.roll);
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public String toString() {
|
|
return "SpawningContext{xBlock="
|
|
+ this.xBlock
|
|
+ ", zBlock="
|
|
+ this.zBlock
|
|
+ ", yBlock="
|
|
+ this.yBlock
|
|
+ ", ySpawnMin="
|
|
+ this.ySpawnMin
|
|
+ ", xSpawn="
|
|
+ this.xSpawn
|
|
+ ", zSpawn="
|
|
+ this.zSpawn
|
|
+ ", ySpawn="
|
|
+ this.ySpawn
|
|
+ ", yaw="
|
|
+ this.yaw
|
|
+ ", pitch="
|
|
+ this.pitch
|
|
+ ", roll="
|
|
+ this.roll
|
|
+ ", groundBlockId="
|
|
+ this.groundBlockId
|
|
+ "}";
|
|
}
|
|
|
|
private static class SpawnSpan {
|
|
int bottom;
|
|
int top;
|
|
int waterLevel;
|
|
int groundLevel;
|
|
}
|
|
}
|