From 5158c409822692a6afc33b75560c2385375fefd5 Mon Sep 17 00:00:00 2001 From: IzzelAliz Date: Fri, 10 Jul 2020 15:15:03 +0800 Subject: [PATCH] Implement spawn rate limit and per-world spawn limit. Close #12. --- .../common/bridge/world/WorldBridge.java | 8 ++ .../world/server/ChunkManagerBridge.java | 7 ++ .../common/mixin/core/world/WorldMixin.java | 28 +++++ .../core/world/server/ChunkManagerMixin.java | 19 +++ .../server/ServerChunkProviderMixin.java | 110 ++++++++++++++++++ 5 files changed, 172 insertions(+) diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/bridge/world/WorldBridge.java b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/world/WorldBridge.java index 5b177df2..409efecd 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/bridge/world/WorldBridge.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/world/WorldBridge.java @@ -26,4 +26,12 @@ public interface WorldBridge extends IWorldWriterBridge { TileEntity bridge$getTileEntity(BlockPos pos, boolean validate); SpigotWorldConfig bridge$spigotConfig(); + + long bridge$ticksPerAnimalSpawns(); + + long bridge$ticksPerMonsterSpawns(); + + long bridge$ticksPerWaterSpawns(); + + long bridge$ticksPerAmbientSpawns(); } diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/bridge/world/server/ChunkManagerBridge.java b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/world/server/ChunkManagerBridge.java index 4e3a1f73..830dda12 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/bridge/world/server/ChunkManagerBridge.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/world/server/ChunkManagerBridge.java @@ -1,6 +1,7 @@ package io.izzel.arclight.common.bridge.world.server; import io.izzel.arclight.common.mod.util.ArclightCallbackExecutor; +import net.minecraft.util.math.ChunkPos; import net.minecraft.world.server.ChunkHolder; import java.util.function.BooleanSupplier; @@ -9,6 +10,12 @@ public interface ChunkManagerBridge { void bridge$tick(BooleanSupplier hasMoreTime); + Iterable bridge$getLoadedChunksIterable(); + + boolean bridge$isOutsideSpawningRadius(ChunkPos chunkPosIn); + + void bridge$tickEntityTracker(); + ArclightCallbackExecutor bridge$getCallbackExecutor(); ChunkHolder bridge$chunkHolderAt(long chunkPos); diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/WorldMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/WorldMixin.java index 3ae3e8a9..49e23eb5 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/WorldMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/WorldMixin.java @@ -61,6 +61,10 @@ public abstract class WorldMixin implements WorldBridge { protected CraftWorld world; public boolean pvpMode; public boolean keepSpawnInMemory = true; + public long ticksPerAnimalSpawns; + public long ticksPerMonsterSpawns; + public long ticksPerWaterSpawns; + public long ticksPerAmbientSpawns; public boolean populating; public org.bukkit.generator.ChunkGenerator generator; protected org.bukkit.World.Environment environment; @@ -70,6 +74,30 @@ public abstract class WorldMixin implements WorldBridge { private void arclight$init(WorldInfo info, DimensionType dimType, BiFunction provider, IProfiler profilerIn, boolean remote, CallbackInfo ci) { spigotConfig = new SpigotWorldConfig(worldInfo.getWorldName()); ((WorldBorderBridge) this.worldBorder).bridge$setWorld((ServerWorld) (Object) this); + this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); + this.ticksPerMonsterSpawns = this.getServer().getTicksPerMonsterSpawns(); + this.ticksPerWaterSpawns = this.getServer().getTicksPerWaterSpawns(); + this.ticksPerAmbientSpawns = this.getServer().getTicksPerAmbientSpawns(); + } + + @Override + public long bridge$ticksPerAnimalSpawns() { + return ticksPerAnimalSpawns; + } + + @Override + public long bridge$ticksPerMonsterSpawns() { + return ticksPerMonsterSpawns; + } + + @Override + public long bridge$ticksPerWaterSpawns() { + return ticksPerWaterSpawns; + } + + @Override + public long bridge$ticksPerAmbientSpawns() { + return ticksPerAmbientSpawns; } public void arclight$constructor(WorldInfo info, DimensionType dimType, BiFunction provider, IProfiler profilerIn, boolean remote) { diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/server/ChunkManagerMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/server/ChunkManagerMixin.java index 87ba9ad6..0fb87ba4 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/server/ChunkManagerMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/server/ChunkManagerMixin.java @@ -2,6 +2,7 @@ package io.izzel.arclight.common.mixin.core.world.server; import io.izzel.arclight.common.bridge.world.server.ChunkManagerBridge; import io.izzel.arclight.common.mod.util.ArclightCallbackExecutor; +import net.minecraft.util.math.ChunkPos; import net.minecraft.world.server.ChunkHolder; import net.minecraft.world.server.ChunkManager; import org.spongepowered.asm.mixin.Mixin; @@ -16,6 +17,9 @@ public abstract class ChunkManagerMixin implements ChunkManagerBridge { // @formatter:off @Shadow @Nullable protected abstract ChunkHolder func_219220_a(long chunkPosIn); + @Shadow protected abstract Iterable getLoadedChunksIterable(); + @Shadow abstract boolean isOutsideSpawningRadius(ChunkPos chunkPosIn); + @Shadow protected abstract void tickEntityTracker(); @Invoker("tick") public abstract void bridge$tick(BooleanSupplier hasMoreTime); // @formatter:on @@ -30,4 +34,19 @@ public abstract class ChunkManagerMixin implements ChunkManagerBridge { public ChunkHolder bridge$chunkHolderAt(long chunkPos) { return func_219220_a(chunkPos); } + + @Override + public Iterable bridge$getLoadedChunksIterable() { + return this.getLoadedChunksIterable(); + } + + @Override + public boolean bridge$isOutsideSpawningRadius(ChunkPos chunkPosIn) { + return this.isOutsideSpawningRadius(chunkPosIn); + } + + @Override + public void bridge$tickEntityTracker() { + this.tickEntityTracker(); + } } diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/server/ServerChunkProviderMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/server/ServerChunkProviderMixin.java index 04a970d5..3494ee26 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/server/ServerChunkProviderMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/server/ServerChunkProviderMixin.java @@ -1,14 +1,22 @@ package io.izzel.arclight.common.mixin.core.world.server; import com.mojang.datafixers.util.Either; +import io.izzel.arclight.common.bridge.world.WorldBridge; import io.izzel.arclight.common.bridge.world.server.ChunkHolderBridge; import io.izzel.arclight.common.bridge.world.server.ChunkManagerBridge; import io.izzel.arclight.common.bridge.world.server.ServerChunkProviderBridge; import io.izzel.arclight.common.bridge.world.server.TicketManagerBridge; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import net.minecraft.entity.EntityClassification; import net.minecraft.profiler.IProfiler; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.GameRules; +import net.minecraft.world.WorldType; +import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.ChunkStatus; import net.minecraft.world.chunk.IChunk; +import net.minecraft.world.gen.ChunkGenerator; import net.minecraft.world.server.ChunkHolder; import net.minecraft.world.server.ChunkManager; import net.minecraft.world.server.ServerChunkProvider; @@ -16,6 +24,8 @@ import net.minecraft.world.server.ServerWorld; import net.minecraft.world.server.ServerWorldLightManager; import net.minecraft.world.server.TicketManager; import net.minecraft.world.server.TicketType; +import net.minecraft.world.spawner.WorldEntitySpawner; +import net.minecraft.world.storage.WorldInfo; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; @@ -27,6 +37,7 @@ import org.spongepowered.asm.mixin.injection.Redirect; import javax.annotation.Nullable; import java.io.IOException; +import java.util.Optional; import java.util.concurrent.CompletableFuture; @Mixin(ServerChunkProvider.class) @@ -42,6 +53,11 @@ public abstract class ServerChunkProviderMixin implements ServerChunkProviderBri @Shadow @Nullable protected abstract ChunkHolder func_217213_a(long chunkPosIn); @Shadow protected abstract boolean func_217235_l(); @Shadow protected abstract boolean func_217224_a(@Nullable ChunkHolder chunkHolderIn, int p_217224_2_); + @Shadow private long lastGameTime; + @Shadow public boolean spawnHostiles; + @Shadow public boolean spawnPassives; + @Shadow @Final private static int field_217238_b; + @Shadow @Final public ChunkGenerator generator; @Invoker("func_217235_l") public abstract boolean bridge$tickDistanceManager(); @Accessor("lightManager") public abstract ServerWorldLightManager bridge$getLightManager(); // @formatter:on @@ -89,6 +105,100 @@ public abstract class ServerChunkProviderMixin implements ServerChunkProviderBri return this.func_217224_a(chunkholder, j) ? ChunkHolder.MISSING_CHUNK_FUTURE : chunkholder.func_219276_a(requiredStatus, this.chunkManager); } + /** + * @author IzzelAliz + * @reason + */ + @Overwrite + private void tickChunks() { + long i = this.world.getGameTime(); + long j = i - this.lastGameTime; + this.lastGameTime = i; + WorldInfo worldinfo = this.world.getWorldInfo(); + boolean flag = worldinfo.getGenerator() == WorldType.DEBUG_ALL_BLOCK_STATES; + boolean flag1 = this.world.getGameRules().getBoolean(GameRules.DO_MOB_SPAWNING) && !this.world.getPlayers().isEmpty(); + if (!flag) { + this.world.getProfiler().startSection("pollingChunks"); + int k = this.world.getGameRules().getInt(GameRules.RANDOM_TICK_SPEED); + BlockPos blockpos = this.world.getSpawnPoint(); + + boolean spawnAnimal = ((WorldBridge) this.world).bridge$ticksPerAnimalSpawns() != 0 && worldinfo.getGameTime() % ((WorldBridge) this.world).bridge$ticksPerAnimalSpawns() == 0; + boolean spawnMonster = ((WorldBridge) this.world).bridge$ticksPerMonsterSpawns() != 0 && worldinfo.getGameTime() % ((WorldBridge) this.world).bridge$ticksPerMonsterSpawns() == 0; + boolean spawnWater = ((WorldBridge) this.world).bridge$ticksPerWaterSpawns() != 0 && worldinfo.getGameTime() % ((WorldBridge) this.world).bridge$ticksPerWaterSpawns() == 0; + boolean spawnAmbient = ((WorldBridge) this.world).bridge$ticksPerAmbientSpawns() != 0 && worldinfo.getGameTime() % ((WorldBridge) this.world).bridge$ticksPerAmbientSpawns() == 0; + boolean flag2 = spawnAnimal; + + this.world.getProfiler().startSection("naturalSpawnCount"); + int l = this.ticketManager.getSpawningChunksCount(); + EntityClassification[] aentityclassification = EntityClassification.values(); + Object2IntMap object2intmap = this.world.countEntities(); + this.world.getProfiler().endSection(); + ((ChunkManagerBridge) this.chunkManager).bridge$getLoadedChunksIterable().forEach((p_223434_10_) -> { + Optional optional = p_223434_10_.getEntityTickingFuture().getNow(ChunkHolder.UNLOADED_CHUNK).left(); + if (optional.isPresent()) { + Chunk chunk = optional.get(); + this.world.getProfiler().startSection("broadcast"); + p_223434_10_.sendChanges(chunk); + this.world.getProfiler().endSection(); + ChunkPos chunkpos = p_223434_10_.getPosition(); + if (!((ChunkManagerBridge) this.chunkManager).bridge$isOutsideSpawningRadius(chunkpos)) { + chunk.setInhabitedTime(chunk.getInhabitedTime() + j); + if (flag1 && (this.spawnHostiles || this.spawnPassives) && this.world.getWorldBorder().contains(chunk.getPos())) { + this.world.getProfiler().startSection("spawner"); + + for (EntityClassification entityclassification : aentityclassification) { + + boolean spawnThisTick = true; + int limit = entityclassification.getMaxNumberOfCreature(); + switch (entityclassification) { + case MONSTER: + spawnThisTick = spawnMonster; + limit = ((WorldBridge) world).bridge$getWorld().getMonsterSpawnLimit(); + break; + case CREATURE: + spawnThisTick = spawnAnimal; + limit = ((WorldBridge) world).bridge$getWorld().getAnimalSpawnLimit(); + break; + case WATER_CREATURE: + spawnThisTick = spawnWater; + limit = ((WorldBridge) world).bridge$getWorld().getWaterAnimalSpawnLimit(); + break; + case AMBIENT: + spawnThisTick = spawnAmbient; + limit = ((WorldBridge) world).bridge$getWorld().getAmbientSpawnLimit(); + break; + } + + if (!spawnThisTick || limit == 0) { + continue; + } + if (entityclassification != EntityClassification.MISC && (!entityclassification.getPeacefulCreature() || this.spawnPassives) && (entityclassification.getPeacefulCreature() || this.spawnHostiles) && (!entityclassification.getAnimal() || flag2)) { + int i1 = limit * l / field_217238_b; + if (object2intmap.getInt(entityclassification) <= i1) { + WorldEntitySpawner.spawnEntitiesInChunk(entityclassification, this.world, chunk, blockpos); + } + } + } + + this.world.getProfiler().endSection(); + } + + this.world.tickEnvironment(chunk, k); + } + } + }); + this.world.getProfiler().startSection("customSpawners"); + if (flag1) { + this.generator.spawnMobs(this.world, this.spawnHostiles, this.spawnPassives); + } + + this.world.getProfiler().endSection(); + this.world.getProfiler().endSection(); + } + + ((ChunkManagerBridge) this.chunkManager).bridge$tickEntityTracker(); + } + public void close(boolean save) throws IOException { if (save) { this.save(true);