501 lines
20 KiB
Java
501 lines
20 KiB
Java
package net.minecraft.world.level;
|
|
|
|
import com.mojang.logging.LogUtils;
|
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
|
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
|
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.function.Consumer;
|
|
import java.util.stream.Stream;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.core.Holder;
|
|
import net.minecraft.core.QuartPos;
|
|
import net.minecraft.core.registries.BuiltInRegistries;
|
|
import net.minecraft.core.registries.Registries;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.tags.BiomeTags;
|
|
import net.minecraft.tags.BlockTags;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.RandomSource;
|
|
import net.minecraft.util.VisibleForDebug;
|
|
import net.minecraft.util.profiling.Profiler;
|
|
import net.minecraft.util.profiling.ProfilerFiller;
|
|
import net.minecraft.util.random.WeightedList;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.EntitySpawnReason;
|
|
import net.minecraft.world.entity.EntityType;
|
|
import net.minecraft.world.entity.Mob;
|
|
import net.minecraft.world.entity.MobCategory;
|
|
import net.minecraft.world.entity.SpawnGroupData;
|
|
import net.minecraft.world.entity.SpawnPlacements;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.level.biome.Biome;
|
|
import net.minecraft.world.level.biome.MobSpawnSettings;
|
|
import net.minecraft.world.level.block.Blocks;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
import net.minecraft.world.level.chunk.ChunkGenerator;
|
|
import net.minecraft.world.level.chunk.LevelChunk;
|
|
import net.minecraft.world.level.levelgen.Heightmap;
|
|
import net.minecraft.world.level.levelgen.structure.BuiltinStructures;
|
|
import net.minecraft.world.level.levelgen.structure.Structure;
|
|
import net.minecraft.world.level.levelgen.structure.structures.NetherFortressStructure;
|
|
import net.minecraft.world.level.material.FluidState;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
public final class NaturalSpawner {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final int MIN_SPAWN_DISTANCE = 24;
|
|
public static final int SPAWN_DISTANCE_CHUNK = 8;
|
|
public static final int SPAWN_DISTANCE_BLOCK = 128;
|
|
public static final int INSCRIBED_SQUARE_SPAWN_DISTANCE_CHUNK = Mth.floor(8.0F / Mth.SQRT_OF_TWO);
|
|
static final int MAGIC_NUMBER = (int)Math.pow(17.0, 2.0);
|
|
private static final MobCategory[] SPAWNING_CATEGORIES = (MobCategory[])Stream.of(MobCategory.values())
|
|
.filter(mobCategory -> mobCategory != MobCategory.MISC)
|
|
.toArray(MobCategory[]::new);
|
|
|
|
private NaturalSpawner() {
|
|
}
|
|
|
|
public static NaturalSpawner.SpawnState createState(
|
|
int spawnableChunkCount, Iterable<Entity> entities, NaturalSpawner.ChunkGetter chunkGetter, LocalMobCapCalculator calculator
|
|
) {
|
|
PotentialCalculator potentialCalculator = new PotentialCalculator();
|
|
Object2IntOpenHashMap<MobCategory> object2IntOpenHashMap = new Object2IntOpenHashMap<>();
|
|
|
|
for (Entity entity : entities) {
|
|
if (!(entity instanceof Mob mob && (mob.isPersistenceRequired() || mob.requiresCustomPersistence()))) {
|
|
MobCategory mobCategory = entity.getType().getCategory();
|
|
if (mobCategory != MobCategory.MISC) {
|
|
BlockPos blockPos = entity.blockPosition();
|
|
chunkGetter.query(ChunkPos.asLong(blockPos), levelChunk -> {
|
|
MobSpawnSettings.MobSpawnCost mobSpawnCost = getRoughBiome(blockPos, levelChunk).getMobSettings().getMobSpawnCost(entity.getType());
|
|
if (mobSpawnCost != null) {
|
|
potentialCalculator.addCharge(entity.blockPosition(), mobSpawnCost.charge());
|
|
}
|
|
|
|
if (entity instanceof Mob) {
|
|
calculator.addMob(levelChunk.getPos(), mobCategory);
|
|
}
|
|
|
|
object2IntOpenHashMap.addTo(mobCategory, 1);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return new NaturalSpawner.SpawnState(spawnableChunkCount, object2IntOpenHashMap, potentialCalculator, calculator);
|
|
}
|
|
|
|
static Biome getRoughBiome(BlockPos pos, ChunkAccess chunk) {
|
|
return chunk.getNoiseBiome(QuartPos.fromBlock(pos.getX()), QuartPos.fromBlock(pos.getY()), QuartPos.fromBlock(pos.getZ())).value();
|
|
}
|
|
|
|
public static List<MobCategory> getFilteredSpawningCategories(
|
|
NaturalSpawner.SpawnState spawnState, boolean spawnFriendlies, boolean spawnEnemies, boolean spawnPassives
|
|
) {
|
|
List<MobCategory> list = new ArrayList(SPAWNING_CATEGORIES.length);
|
|
|
|
for (MobCategory mobCategory : SPAWNING_CATEGORIES) {
|
|
if ((spawnFriendlies || !mobCategory.isFriendly())
|
|
&& (spawnEnemies || mobCategory.isFriendly())
|
|
&& (spawnPassives || !mobCategory.isPersistent())
|
|
&& spawnState.canSpawnForCategoryGlobal(mobCategory)) {
|
|
list.add(mobCategory);
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
public static void spawnForChunk(ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnState spawnState, List<MobCategory> categories) {
|
|
ProfilerFiller profilerFiller = Profiler.get();
|
|
profilerFiller.push("spawner");
|
|
|
|
for (MobCategory mobCategory : categories) {
|
|
if (spawnState.canSpawnForCategoryLocal(mobCategory, chunk.getPos())) {
|
|
spawnCategoryForChunk(mobCategory, level, chunk, spawnState::canSpawn, spawnState::afterSpawn);
|
|
}
|
|
}
|
|
|
|
profilerFiller.pop();
|
|
}
|
|
|
|
public static void spawnCategoryForChunk(
|
|
MobCategory category, ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback
|
|
) {
|
|
BlockPos blockPos = getRandomPosWithin(level, chunk);
|
|
if (blockPos.getY() >= level.getMinY() + 1) {
|
|
spawnCategoryForPosition(category, level, chunk, blockPos, filter, callback);
|
|
}
|
|
}
|
|
|
|
@VisibleForDebug
|
|
public static void spawnCategoryForPosition(MobCategory category, ServerLevel level, BlockPos pos) {
|
|
spawnCategoryForPosition(category, level, level.getChunk(pos), pos, (entityType, blockPos, chunkAccess) -> true, (mob, chunkAccess) -> {});
|
|
}
|
|
|
|
public static void spawnCategoryForPosition(
|
|
MobCategory category, ServerLevel level, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback
|
|
) {
|
|
StructureManager structureManager = level.structureManager();
|
|
ChunkGenerator chunkGenerator = level.getChunkSource().getGenerator();
|
|
int i = pos.getY();
|
|
BlockState blockState = chunk.getBlockState(pos);
|
|
if (!blockState.isRedstoneConductor(chunk, pos)) {
|
|
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
|
|
int j = 0;
|
|
|
|
for (int k = 0; k < 3; k++) {
|
|
int l = pos.getX();
|
|
int m = pos.getZ();
|
|
int n = 6;
|
|
MobSpawnSettings.SpawnerData spawnerData = null;
|
|
SpawnGroupData spawnGroupData = null;
|
|
int o = Mth.ceil(level.random.nextFloat() * 4.0F);
|
|
int p = 0;
|
|
|
|
for (int q = 0; q < o; q++) {
|
|
l += level.random.nextInt(6) - level.random.nextInt(6);
|
|
m += level.random.nextInt(6) - level.random.nextInt(6);
|
|
mutableBlockPos.set(l, i, m);
|
|
double d = l + 0.5;
|
|
double e = m + 0.5;
|
|
Player player = level.getNearestPlayer(d, i, e, -1.0, false);
|
|
if (player != null) {
|
|
double f = player.distanceToSqr(d, i, e);
|
|
if (isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, f)) {
|
|
if (spawnerData == null) {
|
|
Optional<MobSpawnSettings.SpawnerData> optional = getRandomSpawnMobAt(level, structureManager, chunkGenerator, category, level.random, mutableBlockPos);
|
|
if (optional.isEmpty()) {
|
|
break;
|
|
}
|
|
|
|
spawnerData = (MobSpawnSettings.SpawnerData)optional.get();
|
|
o = spawnerData.minCount() + level.random.nextInt(1 + spawnerData.maxCount() - spawnerData.minCount());
|
|
}
|
|
|
|
if (isValidSpawnPostitionForType(level, category, structureManager, chunkGenerator, spawnerData, mutableBlockPos, f)
|
|
&& filter.test(spawnerData.type(), mutableBlockPos, chunk)) {
|
|
Mob mob = getMobForSpawn(level, spawnerData.type());
|
|
if (mob == null) {
|
|
return;
|
|
}
|
|
|
|
mob.snapTo(d, i, e, level.random.nextFloat() * 360.0F, 0.0F);
|
|
if (isValidPositionForMob(level, mob, f)) {
|
|
spawnGroupData = mob.finalizeSpawn(level, level.getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.NATURAL, spawnGroupData);
|
|
j++;
|
|
p++;
|
|
level.addFreshEntityWithPassengers(mob);
|
|
callback.run(mob, chunk);
|
|
if (j >= mob.getMaxSpawnClusterSize()) {
|
|
return;
|
|
}
|
|
|
|
if (mob.isMaxGroupSizeReached(p)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel level, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double distance) {
|
|
if (distance <= 576.0) {
|
|
return false;
|
|
} else if (level.getSharedSpawnPos().closerToCenterThan(new Vec3(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5), 24.0)) {
|
|
return false;
|
|
} else {
|
|
ChunkPos chunkPos = new ChunkPos(pos);
|
|
return Objects.equals(chunkPos, chunk.getPos()) || level.canSpawnEntitiesInChunk(chunkPos);
|
|
}
|
|
}
|
|
|
|
private static boolean isValidSpawnPostitionForType(
|
|
ServerLevel level,
|
|
MobCategory category,
|
|
StructureManager structureManager,
|
|
ChunkGenerator generator,
|
|
MobSpawnSettings.SpawnerData data,
|
|
BlockPos.MutableBlockPos pos,
|
|
double distance
|
|
) {
|
|
EntityType<?> entityType = data.type();
|
|
if (entityType.getCategory() == MobCategory.MISC) {
|
|
return false;
|
|
} else if (!entityType.canSpawnFarFromPlayer() && distance > entityType.getCategory().getDespawnDistance() * entityType.getCategory().getDespawnDistance()) {
|
|
return false;
|
|
} else if (!entityType.canSummon() || !canSpawnMobAt(level, structureManager, generator, category, data, pos)) {
|
|
return false;
|
|
} else if (!SpawnPlacements.isSpawnPositionOk(entityType, level, pos)) {
|
|
return false;
|
|
} else {
|
|
return !SpawnPlacements.checkSpawnRules(entityType, level, EntitySpawnReason.NATURAL, pos, level.random)
|
|
? false
|
|
: level.noCollision(entityType.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5));
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
private static Mob getMobForSpawn(ServerLevel level, EntityType<?> entityType) {
|
|
try {
|
|
if (entityType.create(level, EntitySpawnReason.NATURAL) instanceof Mob mob) {
|
|
return mob;
|
|
}
|
|
|
|
LOGGER.warn("Can't spawn entity of type: {}", BuiltInRegistries.ENTITY_TYPE.getKey(entityType));
|
|
} catch (Exception var4) {
|
|
LOGGER.warn("Failed to create mob", (Throwable)var4);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static boolean isValidPositionForMob(ServerLevel level, Mob mob, double distance) {
|
|
return distance > mob.getType().getCategory().getDespawnDistance() * mob.getType().getCategory().getDespawnDistance() && mob.removeWhenFarAway(distance)
|
|
? false
|
|
: mob.checkSpawnRules(level, EntitySpawnReason.NATURAL) && mob.checkSpawnObstruction(level);
|
|
}
|
|
|
|
private static Optional<MobSpawnSettings.SpawnerData> getRandomSpawnMobAt(
|
|
ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory category, RandomSource random, BlockPos pos
|
|
) {
|
|
Holder<Biome> holder = level.getBiome(pos);
|
|
return category == MobCategory.WATER_AMBIENT && holder.is(BiomeTags.REDUCED_WATER_AMBIENT_SPAWNS) && random.nextFloat() < 0.98F
|
|
? Optional.empty()
|
|
: mobsAt(level, structureManager, generator, category, pos, holder).getRandom(random);
|
|
}
|
|
|
|
private static boolean canSpawnMobAt(
|
|
ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory category, MobSpawnSettings.SpawnerData data, BlockPos pos
|
|
) {
|
|
return mobsAt(level, structureManager, generator, category, pos, null).contains(data);
|
|
}
|
|
|
|
private static WeightedList<MobSpawnSettings.SpawnerData> mobsAt(
|
|
ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory cetagory, BlockPos pos, @Nullable Holder<Biome> biome
|
|
) {
|
|
return isInNetherFortressBounds(pos, level, cetagory, structureManager)
|
|
? NetherFortressStructure.FORTRESS_ENEMIES
|
|
: generator.getMobsAt(biome != null ? biome : level.getBiome(pos), structureManager, cetagory, pos);
|
|
}
|
|
|
|
public static boolean isInNetherFortressBounds(BlockPos pos, ServerLevel level, MobCategory category, StructureManager structureManager) {
|
|
if (category == MobCategory.MONSTER && level.getBlockState(pos.below()).is(Blocks.NETHER_BRICKS)) {
|
|
Structure structure = structureManager.registryAccess().lookupOrThrow(Registries.STRUCTURE).getValue(BuiltinStructures.FORTRESS);
|
|
return structure == null ? false : structureManager.getStructureAt(pos, structure).isValid();
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static BlockPos getRandomPosWithin(Level level, LevelChunk chunk) {
|
|
ChunkPos chunkPos = chunk.getPos();
|
|
int i = chunkPos.getMinBlockX() + level.random.nextInt(16);
|
|
int j = chunkPos.getMinBlockZ() + level.random.nextInt(16);
|
|
int k = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, i, j) + 1;
|
|
int l = Mth.randomBetweenInclusive(level.random, level.getMinY(), k);
|
|
return new BlockPos(i, l, j);
|
|
}
|
|
|
|
public static boolean isValidEmptySpawnBlock(BlockGetter block, BlockPos pos, BlockState blockState, FluidState fluidState, EntityType<?> entityType) {
|
|
if (blockState.isCollisionShapeFullBlock(block, pos)) {
|
|
return false;
|
|
} else if (blockState.isSignalSource()) {
|
|
return false;
|
|
} else if (!fluidState.isEmpty()) {
|
|
return false;
|
|
} else {
|
|
return blockState.is(BlockTags.PREVENT_MOB_SPAWNING_INSIDE) ? false : !entityType.isBlockDangerous(blockState);
|
|
}
|
|
}
|
|
|
|
public static void spawnMobsForChunkGeneration(ServerLevelAccessor levelAccessor, Holder<Biome> biome, ChunkPos chunkPos, RandomSource random) {
|
|
MobSpawnSettings mobSpawnSettings = biome.value().getMobSettings();
|
|
WeightedList<MobSpawnSettings.SpawnerData> weightedList = mobSpawnSettings.getMobs(MobCategory.CREATURE);
|
|
if (!weightedList.isEmpty()) {
|
|
int i = chunkPos.getMinBlockX();
|
|
int j = chunkPos.getMinBlockZ();
|
|
|
|
while (random.nextFloat() < mobSpawnSettings.getCreatureProbability()) {
|
|
Optional<MobSpawnSettings.SpawnerData> optional = weightedList.getRandom(random);
|
|
if (!optional.isEmpty()) {
|
|
MobSpawnSettings.SpawnerData spawnerData = (MobSpawnSettings.SpawnerData)optional.get();
|
|
int k = spawnerData.minCount() + random.nextInt(1 + spawnerData.maxCount() - spawnerData.minCount());
|
|
SpawnGroupData spawnGroupData = null;
|
|
int l = i + random.nextInt(16);
|
|
int m = j + random.nextInt(16);
|
|
int n = l;
|
|
int o = m;
|
|
|
|
for (int p = 0; p < k; p++) {
|
|
boolean bl = false;
|
|
|
|
for (int q = 0; !bl && q < 4; q++) {
|
|
BlockPos blockPos = getTopNonCollidingPos(levelAccessor, spawnerData.type(), l, m);
|
|
if (spawnerData.type().canSummon() && SpawnPlacements.isSpawnPositionOk(spawnerData.type(), levelAccessor, blockPos)) {
|
|
float f = spawnerData.type().getWidth();
|
|
double d = Mth.clamp((double)l, (double)i + f, i + 16.0 - f);
|
|
double e = Mth.clamp((double)m, (double)j + f, j + 16.0 - f);
|
|
if (!levelAccessor.noCollision(spawnerData.type().getSpawnAABB(d, blockPos.getY(), e))
|
|
|| !SpawnPlacements.checkSpawnRules(
|
|
spawnerData.type(), levelAccessor, EntitySpawnReason.CHUNK_GENERATION, BlockPos.containing(d, blockPos.getY(), e), levelAccessor.getRandom()
|
|
)) {
|
|
continue;
|
|
}
|
|
|
|
Entity entity;
|
|
try {
|
|
entity = spawnerData.type().create(levelAccessor.getLevel(), EntitySpawnReason.NATURAL);
|
|
} catch (Exception var27) {
|
|
LOGGER.warn("Failed to create mob", (Throwable)var27);
|
|
continue;
|
|
}
|
|
|
|
if (entity == null) {
|
|
continue;
|
|
}
|
|
|
|
entity.snapTo(d, blockPos.getY(), e, random.nextFloat() * 360.0F, 0.0F);
|
|
if (entity instanceof Mob mob && mob.checkSpawnRules(levelAccessor, EntitySpawnReason.CHUNK_GENERATION) && mob.checkSpawnObstruction(levelAccessor)) {
|
|
spawnGroupData = mob.finalizeSpawn(
|
|
levelAccessor, levelAccessor.getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.CHUNK_GENERATION, spawnGroupData
|
|
);
|
|
levelAccessor.addFreshEntityWithPassengers(mob);
|
|
bl = true;
|
|
}
|
|
}
|
|
|
|
l += random.nextInt(5) - random.nextInt(5);
|
|
|
|
for (m += random.nextInt(5) - random.nextInt(5); l < i || l >= i + 16 || m < j || m >= j + 16; m = o + random.nextInt(5) - random.nextInt(5)) {
|
|
l = n + random.nextInt(5) - random.nextInt(5);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static BlockPos getTopNonCollidingPos(LevelReader level, EntityType<?> entityType, int x, int z) {
|
|
int i = level.getHeight(SpawnPlacements.getHeightmapType(entityType), x, z);
|
|
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(x, i, z);
|
|
if (level.dimensionType().hasCeiling()) {
|
|
do {
|
|
mutableBlockPos.move(Direction.DOWN);
|
|
} while (!level.getBlockState(mutableBlockPos).isAir());
|
|
|
|
do {
|
|
mutableBlockPos.move(Direction.DOWN);
|
|
} while (level.getBlockState(mutableBlockPos).isAir() && mutableBlockPos.getY() > level.getMinY());
|
|
}
|
|
|
|
return SpawnPlacements.getPlacementType(entityType).adjustSpawnPosition(level, mutableBlockPos.immutable());
|
|
}
|
|
|
|
@FunctionalInterface
|
|
public interface AfterSpawnCallback {
|
|
void run(Mob mob, ChunkAccess chunkAccess);
|
|
}
|
|
|
|
@FunctionalInterface
|
|
public interface ChunkGetter {
|
|
void query(long l, Consumer<LevelChunk> consumer);
|
|
}
|
|
|
|
@FunctionalInterface
|
|
public interface SpawnPredicate {
|
|
boolean test(EntityType<?> entityType, BlockPos blockPos, ChunkAccess chunkAccess);
|
|
}
|
|
|
|
public static class SpawnState {
|
|
private final int spawnableChunkCount;
|
|
private final Object2IntOpenHashMap<MobCategory> mobCategoryCounts;
|
|
private final PotentialCalculator spawnPotential;
|
|
private final Object2IntMap<MobCategory> unmodifiableMobCategoryCounts;
|
|
private final LocalMobCapCalculator localMobCapCalculator;
|
|
@Nullable
|
|
private BlockPos lastCheckedPos;
|
|
@Nullable
|
|
private EntityType<?> lastCheckedType;
|
|
private double lastCharge;
|
|
|
|
SpawnState(
|
|
int spawnableChunkCount,
|
|
Object2IntOpenHashMap<MobCategory> mobCategoryCounts,
|
|
PotentialCalculator spawnPotential,
|
|
LocalMobCapCalculator localMobCapCalculator
|
|
) {
|
|
this.spawnableChunkCount = spawnableChunkCount;
|
|
this.mobCategoryCounts = mobCategoryCounts;
|
|
this.spawnPotential = spawnPotential;
|
|
this.localMobCapCalculator = localMobCapCalculator;
|
|
this.unmodifiableMobCategoryCounts = Object2IntMaps.unmodifiable(mobCategoryCounts);
|
|
}
|
|
|
|
private boolean canSpawn(EntityType<?> entityType, BlockPos pos, ChunkAccess chunk) {
|
|
this.lastCheckedPos = pos;
|
|
this.lastCheckedType = entityType;
|
|
MobSpawnSettings.MobSpawnCost mobSpawnCost = NaturalSpawner.getRoughBiome(pos, chunk).getMobSettings().getMobSpawnCost(entityType);
|
|
if (mobSpawnCost == null) {
|
|
this.lastCharge = 0.0;
|
|
return true;
|
|
} else {
|
|
double d = mobSpawnCost.charge();
|
|
this.lastCharge = d;
|
|
double e = this.spawnPotential.getPotentialEnergyChange(pos, d);
|
|
return e <= mobSpawnCost.energyBudget();
|
|
}
|
|
}
|
|
|
|
private void afterSpawn(Mob mob, ChunkAccess chunk) {
|
|
EntityType<?> entityType = mob.getType();
|
|
BlockPos blockPos = mob.blockPosition();
|
|
double d;
|
|
if (blockPos.equals(this.lastCheckedPos) && entityType == this.lastCheckedType) {
|
|
d = this.lastCharge;
|
|
} else {
|
|
MobSpawnSettings.MobSpawnCost mobSpawnCost = NaturalSpawner.getRoughBiome(blockPos, chunk).getMobSettings().getMobSpawnCost(entityType);
|
|
if (mobSpawnCost != null) {
|
|
d = mobSpawnCost.charge();
|
|
} else {
|
|
d = 0.0;
|
|
}
|
|
}
|
|
|
|
this.spawnPotential.addCharge(blockPos, d);
|
|
MobCategory mobCategory = entityType.getCategory();
|
|
this.mobCategoryCounts.addTo(mobCategory, 1);
|
|
this.localMobCapCalculator.addMob(new ChunkPos(blockPos), mobCategory);
|
|
}
|
|
|
|
public int getSpawnableChunkCount() {
|
|
return this.spawnableChunkCount;
|
|
}
|
|
|
|
public Object2IntMap<MobCategory> getMobCategoryCounts() {
|
|
return this.unmodifiableMobCategoryCounts;
|
|
}
|
|
|
|
boolean canSpawnForCategoryGlobal(MobCategory category) {
|
|
int i = category.getMaxInstancesPerChunk() * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
|
|
return this.mobCategoryCounts.getInt(category) < i;
|
|
}
|
|
|
|
boolean canSpawnForCategoryLocal(MobCategory category, ChunkPos chunkPos) {
|
|
return this.localMobCapCalculator.canSpawn(category, chunkPos);
|
|
}
|
|
}
|
|
}
|