Implement spawn rate limit and per-world spawn limit. Close #12.

This commit is contained in:
IzzelAliz 2020-07-10 15:15:03 +08:00
parent 9bc66e5552
commit 5158c40982
5 changed files with 172 additions and 0 deletions

View File

@ -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();
}

View File

@ -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<ChunkHolder> bridge$getLoadedChunksIterable();
boolean bridge$isOutsideSpawningRadius(ChunkPos chunkPosIn);
void bridge$tickEntityTracker();
ArclightCallbackExecutor bridge$getCallbackExecutor();
ChunkHolder bridge$chunkHolderAt(long chunkPos);

View File

@ -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<World, Dimension, AbstractChunkProvider> 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<World, Dimension, AbstractChunkProvider> provider, IProfiler profilerIn, boolean remote) {

View File

@ -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<ChunkHolder> 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<ChunkHolder> bridge$getLoadedChunksIterable() {
return this.getLoadedChunksIterable();
}
@Override
public boolean bridge$isOutsideSpawningRadius(ChunkPos chunkPosIn) {
return this.isOutsideSpawningRadius(chunkPosIn);
}
@Override
public void bridge$tickEntityTracker() {
this.tickEntityTracker();
}
}

View File

@ -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<EntityClassification> object2intmap = this.world.countEntities();
this.world.getProfiler().endSection();
((ChunkManagerBridge) this.chunkManager).bridge$getLoadedChunksIterable().forEach((p_223434_10_) -> {
Optional<Chunk> 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);