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 = -2147483648;
private int minSpawnSpanHeight = 2147483647;
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 SpawnSpan[] spawnSpans = new 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 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 t) {
LOGGER.at(Level.WARNING).log("Can't set role in spawning context %s: %s", spawnable.getIdentifier(), t.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)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.20000000298023224);
return true;
} else {
this.clearModel();
return false;
}
}
}
}
private void clearModel() {
this.spawnModel = null;
this.minSpawnSpanHeight = 2147483647;
}
public void newModel() {
if (this.spawnModel != null) {
ModelAsset 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 >= 2147483647) {
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((long)this.xBlock);
this.zBlock = ChunkUtil.localCoordinate((long)this.zBlock);
this.environmentIndex = -2147483648;
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 = 2147483647;
int chosenIndex = -1;
for(int index = 0; index < this.spawnSpansUsed; ++index) {
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) {
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;
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 || ((BlockType)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)Fluid.getAssetMap().getAsset(this.groundFluidId);
this.ySpawnMin = (double)this.yBlock + NPCPhysicsMath.blockHeight(this.groundBlockType, this.groundRotation);
this.xSpawn = (double)(ChunkUtil.minBlock(this.worldChunk.getX()) + this.xBlock) + 0.5;
this.zSpawn = (double)(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;
for(groundLevel = -1; min <= max; ++min) {
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;
}
}
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;
}
--groundLevel;
}
}
if (waterLevel == top - 1) {
while(waterLevel < 319 && this.isSpawnSpanBlock(this.xBlock, waterLevel + 1, this.zBlock) == 1) {
++waterLevel;
}
}
if (this.spawnSpans.length <= this.spawnSpansUsed) {
SpawnSpan[] newSpans = new 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 SpawnSpan();
}
this.spawnSpans = newSpans;
}
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 && ((BlockType)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 = (double)RandomExtra.randomRange(0.0F, 6.2831855F);
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 >= (double)this.ySpanMin && y <= (double)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 = (double)(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) {
if (WorldUtil.isSolidOnlyBlock(blockType, fluidId)) {
this.ySpawn = (double)ySpawnBlock + NPCPhysicsMath.blockHeight(blockType, rotation) - this.spawnModel.getBoundingBox().min.y;
return this.isInsideSpan(this.ySpawn);
} else {
return false;
}
} else {
return this.isInsideSpan(this.ySpawn);
}
} else {
return this.ySpawn >= (double)(this.ySpanMin - 1) && this.ySpawn <= (double)this.ySpanMax;
}
}
}
public boolean isInAir(double height) {
this.ySpawn = (double)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, (Object)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 if (!breathesInAir && this.ySpawn + (double)this.spawnModel.getEyeHeight() >= (double)this.airHeight) {
return false;
} else {
return breathesInWater || !((double)(this.waterLevel + 1) - this.ySpawn >= (double)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((Scope)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
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;
private SpawnSpan() {
}
}
}