From ac03a977aabcddd8c17c16bfa89cd2a8b5e526b7 Mon Sep 17 00:00:00 2001 From: Julian Krings <47589149+CrazyDev05@users.noreply.github.com> Date: Tue, 11 Feb 2025 23:06:47 +0100 Subject: [PATCH] Mob spawning fixes (#1169) * fix cooldown being 0 in most cases * fix max entity count for spawners --- .../volmit/iris/engine/IrisWorldManager.java | 200 +++++++----------- .../engine/object/IrisEngineChunkData.java | 43 ---- .../iris/engine/object/IrisEngineData.java | 46 ++-- .../volmit/iris/engine/object/IrisRate.java | 2 +- .../iris/engine/object/IrisSpawner.java | 32 +++ .../engine/object/IrisSpawnerCooldowns.java | 34 +++ 6 files changed, 155 insertions(+), 202 deletions(-) delete mode 100644 core/src/main/java/com/volmit/iris/engine/object/IrisEngineChunkData.java create mode 100644 core/src/main/java/com/volmit/iris/engine/object/IrisSpawnerCooldowns.java diff --git a/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java b/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java index 11f7e0d55..08962e4af 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java @@ -62,6 +62,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -299,28 +300,6 @@ public class IrisWorldManager extends EngineAssignedWorldManager { energy += 1.2; } - //@builder - IrisBiome biome = IrisSettings.get().getWorld().isAnbientEntitySpawningSystem() - ? getEngine().getSurfaceBiome(c) : null; - IrisEntitySpawn v = IrisSettings.get().getWorld().isAnbientEntitySpawningSystem() - ? spawnRandomly(Stream.concat(getData().getSpawnerLoader() - .loadAll(getDimension().getEntitySpawners()) - .shuffleCopy(RNG.r).stream() - .filter(this::canSpawn) - .filter((i) -> i.isValid(biome)) - .flatMap((i) -> stream(i, initial)), - Stream.concat(getData().getSpawnerLoader() - .loadAll(getEngine().getRegion(c.getX() << 4, c.getZ() << 4).getEntitySpawners()) - .shuffleCopy(RNG.r).stream().filter(this::canSpawn) - .flatMap((i) -> stream(i, initial)), - getData().getSpawnerLoader() - .loadAll(getEngine().getSurfaceBiome(c.getX() << 4, c.getZ() << 4).getEntitySpawners()) - .shuffleCopy(RNG.r).stream().filter(this::canSpawn) - .flatMap((i) -> stream(i, initial)))) - .collect(Collectors.toList())) - .popRandom(RNG.r) : null; - //@done - if (IrisSettings.get().getWorld().isMarkerEntitySpawningSystem()) { getSpawnersFromMarkers(c).forEach((blockf, spawners) -> { if (spawners.isEmpty()) { @@ -335,94 +314,67 @@ public class IrisWorldManager extends EngineAssignedWorldManager { }); } - if (v != null && v.getReferenceSpawner() != null) { - int maxEntCount = v.getReferenceSpawner().getMaxEntitiesPerChunk(); + if (!IrisSettings.get().getWorld().isAnbientEntitySpawningSystem()) { + return; + } - for (Entity i : c.getEntities()) { - if (i instanceof LivingEntity) { - if (-maxEntCount <= 0) { - return; - } - } - } + //@builder + Predicate filter = i -> i.canSpawn(getEngine(), c.getX(), c.getZ()); + ChunkCounter counter = new ChunkCounter(c.getEntities()); - try { - spawn(c, v); - } catch (Throwable e) { - J.s(() -> spawn(c, v)); - } + IrisBiome biome = getEngine().getSurfaceBiome(c); + IrisEntitySpawn v = spawnRandomly(Stream.concat(getData().getSpawnerLoader() + .loadAll(getDimension().getEntitySpawners()) + .shuffleCopy(RNG.r) + .stream() + .filter(filter) + .filter((i) -> i.isValid(biome)), + Stream.concat(getData() + .getSpawnerLoader() + .loadAll(getEngine().getRegion(c.getX() << 4, c.getZ() << 4).getEntitySpawners()) + .shuffleCopy(RNG.r) + .stream() + .filter(filter), + getData().getSpawnerLoader() + .loadAll(getEngine().getSurfaceBiome(c.getX() << 4, c.getZ() << 4).getEntitySpawners()) + .shuffleCopy(RNG.r) + .stream() + .filter(filter))) + .filter(counter) + .flatMap((i) -> stream(i, initial)) + .collect(Collectors.toList())) + .getRandom(); + //@done + if (v == null || v.getReferenceSpawner() == null) + return; + + try { + spawn(c, v); + } catch (Throwable e) { + J.s(() -> spawn(c, v)); } } private void spawn(Chunk c, IrisEntitySpawn i) { - boolean allow = true; - - if (!i.getReferenceSpawner().getMaximumRatePerChunk().isInfinite()) { - allow = false; - IrisEngineChunkData cd = getEngine().getEngineData().getChunk(c.getX(), c.getZ()); - IrisEngineSpawnerCooldown sc = null; - for (IrisEngineSpawnerCooldown j : cd.getCooldowns()) { - if (j.getSpawner().equals(i.getReferenceSpawner().getLoadKey())) { - sc = j; - break; - } - } - - if (sc == null) { - sc = new IrisEngineSpawnerCooldown(); - sc.setSpawner(i.getReferenceSpawner().getLoadKey()); - cd.getCooldowns().add(sc); - } - - if (sc.canSpawn(i.getReferenceSpawner().getMaximumRatePerChunk())) { - sc.spawn(getEngine()); - allow = true; - } - } - - if (allow) { - int s = i.spawn(getEngine(), c, RNG.r); - actuallySpawned += s; - if (s > 0) { - getCooldown(i.getReferenceSpawner()).spawn(getEngine()); - energy -= s * ((i.getEnergyMultiplier() * i.getReferenceSpawner().getEnergyMultiplier() * 1)); - } + IrisSpawner ref = i.getReferenceSpawner(); + int s = i.spawn(getEngine(), c, RNG.r); + actuallySpawned += s; + if (s > 0) { + ref.spawn(getEngine(), c.getX(), c.getZ()); + energy -= s * ((i.getEnergyMultiplier() * ref.getEnergyMultiplier() * 1)); } } - private void spawn(IrisPosition c, IrisEntitySpawn i) { - boolean allow = true; + private void spawn(IrisPosition pos, IrisEntitySpawn i) { + IrisSpawner ref = i.getReferenceSpawner(); + if (!ref.canSpawn(getEngine(), pos.getX() >> 4, pos.getZ())) + return; - if (!i.getReferenceSpawner().getMaximumRatePerChunk().isInfinite()) { - allow = false; - IrisEngineChunkData cd = getEngine().getEngineData().getChunk(c.getX() >> 4, c.getZ() >> 4); - IrisEngineSpawnerCooldown sc = null; - for (IrisEngineSpawnerCooldown j : cd.getCooldowns()) { - if (j.getSpawner().equals(i.getReferenceSpawner().getLoadKey())) { - sc = j; - break; - } - } - - if (sc == null) { - sc = new IrisEngineSpawnerCooldown(); - sc.setSpawner(i.getReferenceSpawner().getLoadKey()); - cd.getCooldowns().add(sc); - } - - if (sc.canSpawn(i.getReferenceSpawner().getMaximumRatePerChunk())) { - sc.spawn(getEngine()); - allow = true; - } - } - - if (allow) { - int s = i.spawn(getEngine(), c, RNG.r); - actuallySpawned += s; - if (s > 0) { - getCooldown(i.getReferenceSpawner()).spawn(getEngine()); - energy -= s * ((i.getEnergyMultiplier() * i.getReferenceSpawner().getEnergyMultiplier() * 1)); - } + int s = i.spawn(getEngine(), pos, RNG.r); + actuallySpawned += s; + if (s > 0) { + ref.spawn(getEngine(), pos.getX() >> 4, pos.getZ() >> 4); + energy -= s * ((i.getEnergyMultiplier() * ref.getEnergyMultiplier() * 1)); } } @@ -450,31 +402,6 @@ public class IrisWorldManager extends EngineAssignedWorldManager { return rarityTypes; } - public boolean canSpawn(IrisSpawner i) { - return i.isValid(getEngine().getWorld().realWorld()) - && getCooldown(i).canSpawn(i.getMaximumRate()); - } - - private IrisEngineSpawnerCooldown getCooldown(IrisSpawner i) { - IrisEngineData ed = getEngine().getEngineData(); - IrisEngineSpawnerCooldown cd = null; - - for (IrisEngineSpawnerCooldown j : ed.getSpawnerCooldowns().copy()) { - if (j.getSpawner().equals(i.getLoadKey())) { - cd = j; - } - } - - if (cd == null) { - cd = new IrisEngineSpawnerCooldown(); - cd.setSpawner(i.getLoadKey()); - cd.setLastSpawn(M.ms() - i.getMaximumRate().getInterval()); - ed.getSpawnerCooldowns().add(cd); - } - - return cd; - } - @Override public void onTick() { @@ -708,4 +635,27 @@ public class IrisWorldManager extends EngineAssignedWorldManager { return (double) entityCount / (getEngine().getWorld().realWorld().getLoadedChunks().length + 1) * 1.28; } + + @Data + private static class ChunkCounter implements Predicate { + private final Entity[] entities; + private transient int index = 0; + private transient int count = 0; + + @Override + public boolean test(IrisSpawner spawner) { + int max = spawner.getMaxEntitiesPerChunk(); + if (max <= count) + return false; + + while (index < entities.length) { + if (entities[index++] instanceof LivingEntity) { + if (++count >= max) + return false; + } + } + + return true; + } + } } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisEngineChunkData.java b/core/src/main/java/com/volmit/iris/engine/object/IrisEngineChunkData.java deleted file mode 100644 index 24a9c007a..000000000 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisEngineChunkData.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.engine.object; - -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.util.collection.KList; -import lombok.Data; - -@Data -public class IrisEngineChunkData { - private long chunk; - private KList cooldowns = new KList<>(); - - public void cleanup(Engine engine) { - for (IrisEngineSpawnerCooldown i : getCooldowns().copy()) { - IrisSpawner sp = engine.getData().getSpawnerLoader().load(i.getSpawner()); - - if (sp == null || i.canSpawn(sp.getMaximumRate())) { - getCooldowns().remove(i); - } - } - } - - public boolean isEmpty() { - return cooldowns.isEmpty(); - } -} diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisEngineData.java b/core/src/main/java/com/volmit/iris/engine/object/IrisEngineData.java index 418340124..3e4dff47c 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisEngineData.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisEngineData.java @@ -20,51 +20,31 @@ package com.volmit.iris.engine.object; import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; import lombok.Data; +import lombok.EqualsAndHashCode; @Data -public class IrisEngineData { +@EqualsAndHashCode(callSuper = true) +public class IrisEngineData extends IrisSpawnerCooldowns { private IrisEngineStatistics statistics = new IrisEngineStatistics(); - private KList spawnerCooldowns = new KList<>(); - private KList chunks = new KList<>(); + private KMap chunks = new KMap<>(); private Long seed = null; public void removeChunk(int x, int z) { - long k = Cache.key(x, z); - chunks.removeWhere((i) -> i.getChunk() == k); + chunks.remove(Cache.key(x, z)); } - public IrisEngineChunkData getChunk(int x, int z) { - long k = Cache.key(x, z); - - for (IrisEngineChunkData i : chunks) { - if (i.getChunk() == k) { - return i; - } - } - - IrisEngineChunkData c = new IrisEngineChunkData(); - c.setChunk(k); - chunks.add(c); - return c; + public IrisSpawnerCooldowns getChunk(int x, int z) { + return chunks.computeIfAbsent(Cache.key(x, z), k -> new IrisSpawnerCooldowns()); } public void cleanup(Engine engine) { - for (IrisEngineSpawnerCooldown i : getSpawnerCooldowns().copy()) { - IrisSpawner sp = engine.getData().getSpawnerLoader().load(i.getSpawner()); + super.cleanup(engine); - if (sp == null || i.canSpawn(sp.getMaximumRate())) { - getSpawnerCooldowns().remove(i); - } - } - - for (IrisEngineChunkData i : chunks.copy()) { - i.cleanup(engine); - - if (i.isEmpty()) { - getChunks().remove(i); - } - } + chunks.values().removeIf(chunk -> { + chunk.cleanup(engine); + return chunk.isEmpty(); + }); } } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisRate.java b/core/src/main/java/com/volmit/iris/engine/object/IrisRate.java index a365fe544..68dbb7927 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisRate.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisRate.java @@ -43,7 +43,7 @@ public class IrisRate { } public long getInterval() { - long t = per.getMilliseconds() / (amount == 0 ? 1 : amount); + long t = per.toMilliseconds() / (amount == 0 ? 1 : amount); return Math.abs(t <= 0 ? 1 : t); } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisSpawner.java b/core/src/main/java/com/volmit/iris/engine/object/IrisSpawner.java index 6d2359895..c3343a2a0 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisSpawner.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisSpawner.java @@ -19,6 +19,7 @@ package com.volmit.iris.engine.object; import com.volmit.iris.core.loader.IrisRegistrant; +import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.annotations.ArrayType; import com.volmit.iris.engine.object.annotations.Desc; import com.volmit.iris.util.collection.KList; @@ -95,6 +96,37 @@ public class IrisSpawner extends IrisRegistrant { return timeBlock.isWithin(world) && weather.is(world); } + public boolean canSpawn(Engine engine) { + if (!isValid(engine.getWorld().realWorld())) + return false; + + var rate = getMaximumRate(); + return rate.isInfinite() || engine.getEngineData().getCooldown(this).canSpawn(rate); + } + + public boolean canSpawn(Engine engine, int x, int z) { + if (!canSpawn(engine)) + return false; + + var rate = getMaximumRatePerChunk(); + return rate.isInfinite() || engine.getEngineData().getChunk(x, z).getCooldown(this).canSpawn(rate); + } + + public void spawn(Engine engine) { + if (getMaximumRate().isInfinite()) + return; + + engine.getEngineData().getCooldown(this).spawn(engine); + } + + public void spawn(Engine engine, int x, int z) { + spawn(engine); + if (getMaximumRatePerChunk().isInfinite()) + return; + + engine.getEngineData().getChunk(x, z).getCooldown(this).spawn(engine); + } + @Override public String getFolderName() { return "spawners"; diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisSpawnerCooldowns.java b/core/src/main/java/com/volmit/iris/engine/object/IrisSpawnerCooldowns.java new file mode 100644 index 000000000..05447eb74 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisSpawnerCooldowns.java @@ -0,0 +1,34 @@ +package com.volmit.iris.engine.object; + +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.util.collection.KMap; +import lombok.EqualsAndHashCode; +import lombok.NonNull; + +@EqualsAndHashCode +public class IrisSpawnerCooldowns { + private final KMap cooldowns = new KMap<>(); + + public IrisEngineSpawnerCooldown getCooldown(@NonNull IrisSpawner spawner) { + return getCooldown(spawner.getLoadKey()); + } + + public IrisEngineSpawnerCooldown getCooldown(@NonNull String loadKey) { + return cooldowns.computeIfAbsent(loadKey, k -> { + IrisEngineSpawnerCooldown cd = new IrisEngineSpawnerCooldown(); + cd.setSpawner(loadKey); + return cd; + }); + } + + public void cleanup(Engine engine) { + cooldowns.values().removeIf(cd -> { + IrisSpawner sp = engine.getData().getSpawnerLoader().load(cd.getSpawner()); + return sp == null || cd.canSpawn(sp.getMaximumRate()); + }); + } + + public boolean isEmpty() { + return cooldowns.isEmpty(); + } +}