Mob spawning fixes (#1169)

* fix cooldown being 0 in most cases

* fix max entity count for spawners
This commit is contained in:
Julian Krings 2025-02-11 23:06:47 +01:00 committed by GitHub
parent d7270f66e1
commit ac03a977aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 155 additions and 202 deletions

View File

@ -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<IrisSpawner> 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<IrisSpawner> {
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;
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<IrisEngineSpawnerCooldown> 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();
}
}

View File

@ -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<IrisEngineSpawnerCooldown> spawnerCooldowns = new KList<>();
private KList<IrisEngineChunkData> chunks = new KList<>();
private KMap<Long, IrisSpawnerCooldowns> 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();
});
}
}

View File

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

View File

@ -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";

View File

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