mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +00:00
Fix seeded entity spawning
- Derive initial chunk and marker populations from stable location seeds. - Persist initial spawn completion after region-owned application. - Seed entity loot and attribute modifier identities. - Cover spawning, reload completion, and registry fixtures with regression tests.
This commit is contained in:
1 parent
12e8bb5a09
commit
be0d3b0156
33 files changed
+1932
-407
No files matched your search
+95
-92
@@ -44,6 +44,8 @@ import art.arcane.iris.pack.value.IrisPosition;
|
||||
import art.arcane.iris.pack.value.IrisRange;
|
||||
import art.arcane.iris.world.entity.IrisSpawnGroup;
|
||||
import art.arcane.iris.world.entity.IrisSpawner;
|
||||
import art.arcane.iris.world.entity.EntitySpawnSeed;
|
||||
import art.arcane.iris.world.entity.IrisEntitySpawn.SpawnContext;
|
||||
import art.arcane.iris.generation.decoration.IrisSurface;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
@@ -61,6 +63,7 @@ import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.Comparator;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -235,6 +238,10 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
}
|
||||
|
||||
boolean initialSpawnChunk(NativeWorld level, int chunkX, int chunkZ) {
|
||||
if (!isEntitySpawningEnabledForCurrentWorld()
|
||||
|| !NativeSpawnQueries.ambientAllowed(level, chunkX, chunkZ, true)) {
|
||||
return false;
|
||||
}
|
||||
if (!ModdedEntitySpawner.chunksSafe(entityRuntime(level), chunkX, chunkZ)) {
|
||||
return false;
|
||||
}
|
||||
@@ -249,54 +256,25 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
MantleChunk<Matter> chunk = mantle.useChunk(chunkX, chunkZ);
|
||||
try {
|
||||
List<PreparedMarkerSpawn> markers = markerSystemEnabled()
|
||||
? prepareMarkerSpawns(level, chunkX, chunkZ, chunk) : List.of();
|
||||
? prepareMarkerSpawns(level, chunkX, chunkZ, chunk, true) : List.of();
|
||||
Optional<BiomeEnvironment> environment = ambientSystemEnabled()
|
||||
? resolveSurfaceEnvironment(chunkX, chunkZ) : Optional.empty();
|
||||
chunk.raiseFlagUnchecked(INITIAL_SPAWN_COMPLETION_FLAG, () -> {
|
||||
spawnPreparedMarkers(level, markers, true);
|
||||
if (environment.isPresent()) {
|
||||
scheduleInitialFollowUp(level, chunkX, chunkZ, environment.get());
|
||||
if (ambientSystemEnabled() && engine.getComplex() == null) {
|
||||
return false;
|
||||
}
|
||||
spawnPreparedMarkers(level, markers, true);
|
||||
if (environment.isPresent()) {
|
||||
try (BiomeEnvironment.Scope ignored = engine.openBiomeEnvironmentScope(environment.get())) {
|
||||
spawnAmbient(level, chunkX, chunkZ, true, environment.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
chunk.raiseFlagUnchecked(INITIAL_SPAWN_COMPLETION_FLAG, () -> {});
|
||||
} finally {
|
||||
chunk.release();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void scheduleInitialFollowUp(NativeWorld level, int chunkX, int chunkZ, BiomeEnvironment environment) {
|
||||
ModdedScheduler scheduler = ModdedEngineBootstrap.schedulerOrNull();
|
||||
if (scheduler == null) {
|
||||
IrisLogging.error("Iris could not schedule the initial entity-spawn follow-up because the modded scheduler is unavailable.");
|
||||
return;
|
||||
}
|
||||
scheduler.laterGlobal(
|
||||
() -> EngineLifecycleTasks.run(
|
||||
engine,
|
||||
"modded_world_manager_initial_spawn_followup",
|
||||
() -> runInitialFollowUp(level, chunkX, chunkZ, environment)),
|
||||
RNG.r.i(5, 200));
|
||||
}
|
||||
|
||||
private void runInitialFollowUp(NativeWorld level, int chunkX, int chunkZ, BiomeEnvironment environment) {
|
||||
Mantle<Matter> mantle = engine.getMantle().getMantle();
|
||||
if (closed || engine.isClosed() || mantle.isClosed() || !isEntitySpawningEnabledForCurrentWorld()) {
|
||||
return;
|
||||
}
|
||||
if (!level.isChunkLoaded(chunkX, chunkZ) || !mantle.isChunkLoaded(chunkX, chunkZ)) {
|
||||
return;
|
||||
}
|
||||
if (engine.getComplex() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ambientSystemEnabled()) {
|
||||
try (BiomeEnvironment.Scope ignored = engine.openBiomeEnvironmentScope(environment)) {
|
||||
spawnAmbient(level, chunkX, chunkZ, true, environment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void warmupMantleChunkAsync(long key, int chunkX, int chunkZ) {
|
||||
if (closed || !mantleWarmups.add(key)) {
|
||||
return;
|
||||
@@ -380,11 +358,14 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
if (!mantle.isChunkLoaded(chunkX, chunkZ)) {
|
||||
return;
|
||||
}
|
||||
if (!mantle.hasFlag(chunkX, chunkZ, INITIAL_SPAWN_COMPLETION_FLAG)) {
|
||||
return;
|
||||
}
|
||||
|
||||
MantleChunk<Matter> chunk = mantle.useChunk(chunkX, chunkZ);
|
||||
try {
|
||||
if (markerSystemEnabled()) {
|
||||
spawnPreparedMarkers(level, prepareMarkerSpawns(level, chunkX, chunkZ, chunk), false);
|
||||
spawnPreparedMarkers(level, prepareMarkerSpawns(level, chunkX, chunkZ, chunk, false), false);
|
||||
}
|
||||
if (ambientSystemEnabled()) {
|
||||
spawnAmbient(level, chunkX, chunkZ, false);
|
||||
@@ -394,7 +375,7 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
}
|
||||
}
|
||||
|
||||
private List<PreparedMarkerSpawn> prepareMarkerSpawns(NativeWorld level, int chunkX, int chunkZ, MantleChunk<Matter> chunk) {
|
||||
private List<PreparedMarkerSpawn> prepareMarkerSpawns(NativeWorld level, int chunkX, int chunkZ, MantleChunk<Matter> chunk, boolean initial) {
|
||||
int minHeight = engine.getWorld().minHeight();
|
||||
KList<IrisPosition> obstructed = new KList<>();
|
||||
List<PreparedMarkerSpawn> prepared = new ArrayList<>();
|
||||
@@ -429,29 +410,41 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
if (spawners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
IrisSpawner chosen = spawners.getRandom();
|
||||
if (initial) {
|
||||
spawners.sort(Comparator.comparing(IrisSpawner::getLoadKey, Comparator.nullsFirst(Comparator.naturalOrder())));
|
||||
}
|
||||
RNG markerRng = initial ? EntitySpawnSeed.marker(engine.getSeedManager().getEntity(), worldX, worldY, worldZ)
|
||||
: new RNG(RNG.r.nextLong());
|
||||
IrisSpawner chosen = spawners.getRandom(markerRng);
|
||||
if (chosen == null) {
|
||||
return;
|
||||
}
|
||||
prepared.add(new PreparedMarkerSpawn(new IrisPosition(worldX, worldY, worldZ), chosen, environment));
|
||||
prepared.add(new PreparedMarkerSpawn(new IrisPosition(worldX, worldY, worldZ), chosen, resolved, markerRng, environment));
|
||||
});
|
||||
Mantle<Matter> mantle = engine.getMantle().getMantle();
|
||||
for (IrisPosition position : obstructed) {
|
||||
mantle.remove(position.getX(), position.getY(), position.getZ(), MatterMarker.class);
|
||||
}
|
||||
if (initial) {
|
||||
prepared.sort(Comparator.comparingInt((PreparedMarkerSpawn marker) -> marker.position().getX())
|
||||
.thenComparingInt(marker -> marker.position().getY())
|
||||
.thenComparingInt(marker -> marker.position().getZ()));
|
||||
}
|
||||
return List.copyOf(prepared);
|
||||
}
|
||||
|
||||
private KList<IrisSpawner> resolveMarkerSpawners(IrisMarker marker, BiomeEnvironment environment) {
|
||||
KList<IrisSpawner> spawners = new KList<>();
|
||||
Set<IrisSpawner> seen = new HashSet<>();
|
||||
for (String key : marker.getSpawners()) {
|
||||
IrisSpawner spawner = environment.data().getSpawnerLoader().load(key);
|
||||
if (spawner == null) {
|
||||
IrisLogging.error("Cannot load spawner: " + key + " for marker on " + engine.getName());
|
||||
continue;
|
||||
}
|
||||
spawner.setReferenceMarker(marker);
|
||||
spawners.add(spawner);
|
||||
if (!spawner.isCompatExcluded() && seen.add(spawner)) {
|
||||
spawners.add(spawner);
|
||||
}
|
||||
}
|
||||
return spawners;
|
||||
}
|
||||
@@ -459,32 +452,29 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
private void spawnPreparedMarkers(NativeWorld level, List<PreparedMarkerSpawn> markers, boolean initial) {
|
||||
for (PreparedMarkerSpawn marker : markers) {
|
||||
try (BiomeEnvironment.Scope ignored = engine.openBiomeEnvironmentScope(marker.environment())) {
|
||||
spawnFromSpawner(level, marker.position(), marker.spawner(), initial);
|
||||
spawnFromSpawner(level, marker.position(), marker.rng(), new SpawnContext(marker.spawner(), marker.marker(), initial));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void spawnFromSpawner(NativeWorld level, IrisPosition position, IrisSpawner spawner, boolean initial) {
|
||||
KList<IrisEntitySpawn> spawns = initial ? spawner.getInitialSpawns() : spawner.getSpawns();
|
||||
private void spawnFromSpawner(NativeWorld level, IrisPosition position, RNG rng, SpawnContext context) {
|
||||
IrisSpawner spawner = context.spawner();
|
||||
KList<IrisEntitySpawn> spawns = context.initial() ? spawner.getInitialSpawns() : spawner.getSpawns();
|
||||
if (spawns.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (IrisEntitySpawn entry : spawns) {
|
||||
entry.setReferenceSpawner(spawner);
|
||||
entry.setReferenceMarker(spawner.getReferenceMarker());
|
||||
}
|
||||
IrisEntitySpawn chosen = rarityPick(spawns);
|
||||
IrisEntitySpawn chosen = rarityPick(spawns, rng);
|
||||
if (chosen == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int chunkX = position.getX() >> 4;
|
||||
int chunkZ = position.getZ() >> 4;
|
||||
if (!canSpawn(spawner, chunkX, chunkZ)) {
|
||||
if (!canSpawn(spawner, chunkX, chunkZ, context.initial())) {
|
||||
return;
|
||||
}
|
||||
int spawned = spawnEntryAt(level, chosen, spawner, position);
|
||||
if (spawned > 0) {
|
||||
int spawned = spawnEntryAt(level, chosen, position, rng, context);
|
||||
if (spawned > 0 && !context.initial()) {
|
||||
spawner.spawn(engine, chunkX, chunkZ);
|
||||
}
|
||||
}
|
||||
@@ -521,9 +511,11 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
}
|
||||
|
||||
IrisBiome biome = environment.biome();
|
||||
Map<String, Integer> chunkMobs = NativeSpawnQueries.livingEntityCategories(level, chunkX, chunkZ);
|
||||
Map<String, Integer> chunkMobs = initial ? Map.of() : NativeSpawnQueries.livingEntityCategories(level, chunkX, chunkZ);
|
||||
RNG rng = initial ? EntitySpawnSeed.chunk(engine.getSeedManager().getEntity(), chunkX, chunkZ)
|
||||
: new RNG(RNG.r.nextLong());
|
||||
|
||||
KList<IrisEntitySpawn> pool = new KList<>();
|
||||
KList<SpawnCandidate> pool = new KList<>();
|
||||
collectSpawns(pool, environment.data().getSpawnerLoader().loadAll(environment.dimension().getEntitySpawners()), biome, chunkX, chunkZ, chunkMobs, initial);
|
||||
collectSpawns(pool, environment.data().getSpawnerLoader().loadAll(environment.region().getEntitySpawners()), null, chunkX, chunkZ, chunkMobs, initial);
|
||||
collectSpawns(pool, environment.data().getSpawnerLoader().loadAll(biome.getEntitySpawners()), null, chunkX, chunkZ, chunkMobs, initial);
|
||||
@@ -531,22 +523,22 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
return;
|
||||
}
|
||||
|
||||
IrisEntitySpawn chosen = rarityPick(pool);
|
||||
if (chosen == null || chosen.getReferenceSpawner() == null) {
|
||||
SpawnCandidate chosen = rarityPick(pool, rng);
|
||||
if (chosen == null) {
|
||||
return;
|
||||
}
|
||||
IrisSpawner spawner = chosen.getReferenceSpawner();
|
||||
if (!canSpawn(spawner, chunkX, chunkZ)) {
|
||||
IrisSpawner spawner = chosen.spawner();
|
||||
if (!canSpawn(spawner, chunkX, chunkZ, initial)) {
|
||||
return;
|
||||
}
|
||||
int spawned = spawnEntry(level, chosen, spawner, chunkX, chunkZ,
|
||||
remainingCapacity(chosen, spawner, chunkMobs));
|
||||
if (spawned > 0) {
|
||||
int spawned = spawnEntry(level, chosen.entry(), chunkX, chunkZ,
|
||||
remainingCapacity(chosen.entry(), spawner, chunkMobs), rng, new SpawnContext(spawner, null, initial));
|
||||
if (spawned > 0 && !initial) {
|
||||
spawner.spawn(engine, chunkX, chunkZ);
|
||||
}
|
||||
}
|
||||
|
||||
private void collectSpawns(KList<IrisEntitySpawn> pool, KList<IrisSpawner> spawners, IrisBiome biomeFilter, int chunkX, int chunkZ, Map<String, Integer> chunkMobs, boolean initial) {
|
||||
private void collectSpawns(KList<SpawnCandidate> pool, KList<IrisSpawner> spawners, IrisBiome biomeFilter, int chunkX, int chunkZ, Map<String, Integer> chunkMobs, boolean initial) {
|
||||
for (IrisSpawner spawner : spawners) {
|
||||
if (spawner == null) {
|
||||
continue;
|
||||
@@ -554,15 +546,13 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
if (biomeFilter != null && !spawner.isValid(biomeFilter)) {
|
||||
continue;
|
||||
}
|
||||
if (!canSpawn(spawner, chunkX, chunkZ)) {
|
||||
if (!canSpawn(spawner, chunkX, chunkZ, initial)) {
|
||||
continue;
|
||||
}
|
||||
KList<IrisEntitySpawn> spawns = initial ? spawner.getInitialSpawns() : spawner.getSpawns();
|
||||
for (IrisEntitySpawn entry : spawns) {
|
||||
entry.setReferenceSpawner(spawner);
|
||||
entry.setReferenceMarker(spawner.getReferenceMarker());
|
||||
if (remainingCapacity(entry, spawner, chunkMobs) > 0) {
|
||||
pool.add(entry);
|
||||
pool.add(new SpawnCandidate(entry, spawner));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -573,7 +563,8 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
return spawner.remainingCapacity(entity, counts);
|
||||
}
|
||||
|
||||
private int spawnEntry(NativeWorld level, IrisEntitySpawn entry, IrisSpawner spawner, int chunkX, int chunkZ, int remainingCapacity) {
|
||||
private int spawnEntry(NativeWorld level, IrisEntitySpawn entry, int chunkX, int chunkZ,
|
||||
int remainingCapacity, RNG rng, SpawnContext context) {
|
||||
IrisEntity irisEntity = entry.getRealEntity(engine);
|
||||
if (irisEntity == null) {
|
||||
return 0;
|
||||
@@ -581,18 +572,24 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
|
||||
int min = entry.getMinSpawns();
|
||||
int max = entry.getMaxSpawns();
|
||||
int count = Math.min(Math.max(0, remainingCapacity), LootResolver.inclusive(RNG.r, min, max));
|
||||
int count = Math.min(Math.max(0, remainingCapacity), LootResolver.inclusive(rng, min, max));
|
||||
if (count <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
RNG entityRng = entry.getRng().aquire(() -> new RNG(engine.getSeedManager().getEntity()));
|
||||
IrisSpawner spawner = context.spawner();
|
||||
IrisSpawnGroup group = spawner.getGroup();
|
||||
KList<IrisPosition> caveFloors = group == IrisSpawnGroup.CAVE
|
||||
? engine.getMantle().findMarkers(chunkX, chunkZ, MarkerMatter.CAVE_FLOOR)
|
||||
: new KList<>();
|
||||
if (context.initial()) {
|
||||
caveFloors.sort(Comparator.comparingInt(IrisPosition::getX)
|
||||
.thenComparingInt(IrisPosition::getY).thenComparingInt(IrisPosition::getZ));
|
||||
}
|
||||
int spawned = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
RNG entityRng = EntitySpawnSeed.entity(rng.getSeed(), i);
|
||||
RNG positionRng = entityRng.nextParallelRNG(0x632BE59BD9B4E019L);
|
||||
int worldX;
|
||||
int worldY;
|
||||
int worldZ;
|
||||
@@ -600,16 +597,16 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
if (caveFloors.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
IrisPosition caveFloor = caveFloors.getRandom(RNG.r);
|
||||
IrisPosition caveFloor = caveFloors.getRandom(positionRng);
|
||||
worldX = caveFloor.getX();
|
||||
worldY = caveFloor.getY();
|
||||
worldZ = caveFloor.getZ();
|
||||
} else {
|
||||
worldX = (chunkX << 4) + RNG.r.i(16);
|
||||
worldZ = (chunkZ << 4) + RNG.r.i(16);
|
||||
worldX = (chunkX << 4) + positionRng.i(16);
|
||||
worldZ = (chunkZ << 4) + positionRng.i(16);
|
||||
int surfaceY = NativeSpawnQueries.surfaceHeight(level, worldX, worldZ, false);
|
||||
int solidY = NativeSpawnQueries.surfaceHeight(level, worldX, worldZ, true);
|
||||
Integer selectedY = IrisEntitySpawn.selectSurfaceSpawnY(group, irisEntity.getSurface(), solidY, surfaceY, RNG.r);
|
||||
Integer selectedY = IrisEntitySpawn.selectSurfaceSpawnY(group, irisEntity.getSurface(), solidY, surfaceY, positionRng);
|
||||
if (selectedY == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -619,7 +616,7 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
continue;
|
||||
}
|
||||
// Rarity is applied exactly once, as pool weighting in rarityPick - never re-rolled per position (Bukkit parity).
|
||||
if (!lightAllowed(spawner, level, worldX, worldY, worldZ)) {
|
||||
if (!context.initial() && !lightAllowed(spawner, level, worldX, worldY, worldZ)) {
|
||||
continue;
|
||||
}
|
||||
if (!surfaceMatches(irisEntity.getSurface(), level, worldX, worldY, worldZ)) {
|
||||
@@ -635,7 +632,7 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
return spawned;
|
||||
}
|
||||
|
||||
private int spawnEntryAt(NativeWorld level, IrisEntitySpawn entry, IrisSpawner spawner, IrisPosition position) {
|
||||
private int spawnEntryAt(NativeWorld level, IrisEntitySpawn entry, IrisPosition position, RNG rng, SpawnContext context) {
|
||||
IrisEntity irisEntity = entry.getRealEntity(engine);
|
||||
if (irisEntity == null) {
|
||||
return 0;
|
||||
@@ -643,21 +640,21 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
|
||||
int min = entry.getMinSpawns();
|
||||
int max = entry.getMaxSpawns();
|
||||
int count = LootResolver.inclusive(RNG.r, min, max);
|
||||
int count = LootResolver.inclusive(rng, min, max);
|
||||
if (count <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
exhaustMarker(spawner, position);
|
||||
exhaustMarker(context.marker(), position, EntitySpawnSeed.entity(rng.getSeed(), -1));
|
||||
|
||||
RNG entityRng = entry.getRng().aquire(() -> new RNG(engine.getSeedManager().getEntity()));
|
||||
int worldX = position.getX();
|
||||
int worldY = position.getY() + 1;
|
||||
int worldZ = position.getZ();
|
||||
int spawned = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
RNG entityRng = EntitySpawnSeed.entity(rng.getSeed(), i);
|
||||
// Rarity is applied exactly once, as pool weighting in rarityPick - never re-rolled per position (Bukkit parity).
|
||||
if (!lightAllowed(spawner, level, worldX, worldY, worldZ)) {
|
||||
if (!context.initial() && !lightAllowed(context.spawner(), level, worldX, worldY, worldZ)) {
|
||||
continue;
|
||||
}
|
||||
if (irisEntity.getSurface().isFluid()
|
||||
@@ -672,16 +669,15 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
return spawned;
|
||||
}
|
||||
|
||||
private void exhaustMarker(IrisSpawner spawner, IrisPosition position) {
|
||||
IrisMarker marker = spawner.getReferenceMarker();
|
||||
if (marker == null || !marker.shouldExhaust()) {
|
||||
private void exhaustMarker(IrisMarker marker, IrisPosition position, RNG rng) {
|
||||
if (marker == null || !marker.shouldExhaust(rng)) {
|
||||
return;
|
||||
}
|
||||
engine.getMantle().getMantle().remove(position.getX(), position.getY() - engine.getWorld().minHeight(), position.getZ(), MatterMarker.class);
|
||||
}
|
||||
|
||||
private boolean canSpawn(IrisSpawner spawner, int chunkX, int chunkZ) {
|
||||
return spawner.canSpawn(engine, chunkX, chunkZ);
|
||||
private boolean canSpawn(IrisSpawner spawner, int chunkX, int chunkZ, boolean initial) {
|
||||
return !spawner.isCompatExcluded() && (initial || spawner.canSpawn(engine, chunkX, chunkZ));
|
||||
}
|
||||
|
||||
private boolean lightAllowed(IrisSpawner spawner, NativeWorld level, int worldX, int worldY, int worldZ) {
|
||||
@@ -796,9 +792,9 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
return !studio || studioSetting;
|
||||
}
|
||||
|
||||
private IrisEntitySpawn rarityPick(KList<IrisEntitySpawn> entries) {
|
||||
KList<IrisEntitySpawn> weighted = Rarity.expandWeighted(entries);
|
||||
return weighted.isEmpty() ? entries.getRandom() : weighted.getRandom();
|
||||
private static <T extends Rarity> T rarityPick(KList<T> entries, RNG rng) {
|
||||
KList<T> weighted = Rarity.expandWeighted(entries);
|
||||
return weighted.isEmpty() ? entries.getRandom(rng) : weighted.getRandom(rng);
|
||||
}
|
||||
|
||||
private static long pack(int x, int z) {
|
||||
@@ -893,7 +889,14 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
return failure;
|
||||
}
|
||||
|
||||
private record PreparedMarkerSpawn(IrisPosition position, IrisSpawner spawner, BiomeEnvironment environment) {
|
||||
private record PreparedMarkerSpawn(IrisPosition position, IrisSpawner spawner, IrisMarker marker, RNG rng, BiomeEnvironment environment) {
|
||||
}
|
||||
|
||||
private record SpawnCandidate(IrisEntitySpawn entry, IrisSpawner spawner) implements Rarity {
|
||||
@Override
|
||||
public int getRarity() {
|
||||
return entry.getRarity();
|
||||
}
|
||||
}
|
||||
|
||||
private NativeEntityRuntime entityRuntime(NativeWorld level) {
|
||||
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.iris.configuration.IrisSettings;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.mantle.EngineMantle;
|
||||
import art.arcane.iris.generation.runtime.BiomeEnvironment;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.generation.runtime.SeedManager;
|
||||
import art.arcane.iris.generation.terrain.InferredType;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.generation.terrain.IrisRegion;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.pack.loading.ResourceLoader;
|
||||
import art.arcane.iris.pack.validation.CompatStatus;
|
||||
import art.arcane.iris.pack.value.IrisRange;
|
||||
import art.arcane.iris.world.IrisWorld;
|
||||
import art.arcane.iris.world.entity.IrisEntity;
|
||||
import art.arcane.iris.world.entity.IrisEntitySpawn;
|
||||
import art.arcane.iris.world.entity.IrisSpawner;
|
||||
import art.arcane.iris.world.entity.IrisMarker;
|
||||
import art.arcane.iris.world.entity.EntitySpawnSeed;
|
||||
import art.arcane.iris.structure.placement.LootResolver;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeEntityRuntime;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeSpawnQueries;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeSpawnedEntity;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.math.Rarity;
|
||||
import art.arcane.volmlib.util.function.Consumer4;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterMarker;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ModdedInitialEntitySpawnTest {
|
||||
@BeforeClass
|
||||
public static void bootstrap() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialBatchesIgnoreChunkOrderRuntimeRandomPopulationLightAndCooldowns() {
|
||||
Map<Long, List<Spawned>> first;
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
assertTrue(fixture.manager.initialSpawnChunk(fixture.world, -2, 3));
|
||||
RNG.r.nextLong();
|
||||
assertTrue(fixture.manager.initialSpawnChunk(fixture.world, 4, -5));
|
||||
first = fixture.byChunk();
|
||||
fixture.verifyInitialPolicy();
|
||||
}
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
for (int i = 0; i < 64; i++) {
|
||||
RNG.r.nextLong();
|
||||
}
|
||||
assertTrue(fixture.manager.initialSpawnChunk(fixture.world, 4, -5));
|
||||
assertTrue(fixture.manager.initialSpawnChunk(fixture.world, -2, 3));
|
||||
assertEquals(first, fixture.byChunk());
|
||||
assertEquals(2, fixture.completed.size());
|
||||
for (List<Spawned> batch : fixture.byChunk().values()) {
|
||||
assertTrue(batch.size() >= 2 && batch.size() <= 3);
|
||||
}
|
||||
fixture.verifyInitialPolicy();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialCompletionFollowsOwnedSpawnPassAndDoesNotRepeatIt() {
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
assertTrue(fixture.manager.initialSpawnChunk(fixture.world, -2, 3));
|
||||
int count = fixture.spawned.size();
|
||||
assertTrue(count > 0);
|
||||
assertEquals("complete", fixture.trace.getLast());
|
||||
assertTrue(fixture.trace.subList(0, fixture.trace.size() - 1).stream().allMatch("spawn"::equals));
|
||||
assertTrue(fixture.manager.initialSpawnChunk(fixture.world, -2, 3));
|
||||
assertEquals(count, fixture.spawned.size());
|
||||
assertEquals(1, fixture.completed.size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unsafeChunkAndDisabledMobSpawningLeaveCompletionForRetry() {
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
fixture.queries.when(() -> NativeSpawnQueries.ambientAllowed(fixture.world, -2, 3, true)).thenReturn(false);
|
||||
assertFalse(fixture.manager.initialSpawnChunk(fixture.world, -2, 3));
|
||||
assertTrue(fixture.completed.isEmpty());
|
||||
fixture.queries.when(() -> NativeSpawnQueries.ambientAllowed(fixture.world, -2, 3, true)).thenReturn(true);
|
||||
fixture.entities.when(() -> ModdedEntitySpawner.chunksSafe(any(NativeEntityRuntime.class), eq(-2), eq(3)))
|
||||
.thenReturn(false);
|
||||
assertFalse(fixture.manager.initialSpawnChunk(fixture.world, -2, 3));
|
||||
assertTrue(fixture.completed.isEmpty());
|
||||
assertTrue(fixture.spawned.isEmpty());
|
||||
fixture.entities.when(() -> ModdedEntitySpawner.chunksSafe(any(NativeEntityRuntime.class), eq(-2), eq(3)))
|
||||
.thenReturn(true);
|
||||
assertTrue(fixture.manager.initialSpawnChunk(fixture.world, -2, 3));
|
||||
assertTrue(fixture.completed.contains(key(-2, 3)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void initialMarkerSelectionUsesOneStreamAndExcludesDuplicateAndUnsupportedSpawners() throws ReflectiveOperationException {
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
IrisSpawner first = fixture.definitions.get(0);
|
||||
IrisSpawner second = fixture.definitions.get(1);
|
||||
first.setLoadKey("a");
|
||||
second.setLoadKey("b");
|
||||
IrisSpawner excluded = new IrisSpawner();
|
||||
excluded.setLoadKey("excluded");
|
||||
excluded.setCompat(CompatStatus.excludedBy(List.of()));
|
||||
IrisMarker marker = new IrisMarker().setSpawners(new KList<>("b", "excluded", "a", "b"))
|
||||
.setEmptyAbove(false).setExhaustionChance(0.5);
|
||||
BiomeEnvironment environment = fixture.engine.getSurfaceBiomeEnvironment(0, 0);
|
||||
ResourceLoader<IrisSpawner> spawners = environment.data().getSpawnerLoader();
|
||||
when(spawners.load("a")).thenReturn(first);
|
||||
when(spawners.load("b")).thenReturn(second);
|
||||
when(spawners.load("excluded")).thenReturn(excluded);
|
||||
ResourceLoader<IrisMarker> markers = mock(ResourceLoader.class);
|
||||
when(environment.data().getMarkerLoader()).thenReturn(markers);
|
||||
when(markers.load("test")).thenReturn(marker);
|
||||
when(fixture.engine.getWorld().minHeight()).thenReturn(-64);
|
||||
when(fixture.engine.getBiomeEnvironment(-25, 15, 57)).thenReturn(environment);
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
doAnswer(invocation -> {
|
||||
Consumer4<Integer, Integer, Integer, MatterMarker> callback = invocation.getArgument(1);
|
||||
callback.accept(7, 15, 9, new MatterMarker("test"));
|
||||
return null;
|
||||
}).when(chunk).iterate(eq(MatterMarker.class), any());
|
||||
Method resolve = ModdedWorldManager.class.getDeclaredMethod("resolveMarkerSpawners", IrisMarker.class, BiomeEnvironment.class);
|
||||
resolve.setAccessible(true);
|
||||
KList<IrisSpawner> resolved = (KList<IrisSpawner>) resolve.invoke(fixture.manager, marker, environment);
|
||||
assertEquals(List.of(second, first), resolved);
|
||||
Method prepare = ModdedWorldManager.class.getDeclaredMethod("prepareMarkerSpawns", NativeWorld.class,
|
||||
int.class, int.class, MantleChunk.class, boolean.class);
|
||||
prepare.setAccessible(true);
|
||||
Object prepared = prepare.invoke(fixture.manager, fixture.world, -2, 3, chunk, true);
|
||||
Method spawn = ModdedWorldManager.class.getDeclaredMethod("spawnPreparedMarkers", NativeWorld.class, List.class, boolean.class);
|
||||
spawn.setAccessible(true);
|
||||
spawn.invoke(fixture.manager, fixture.world, prepared, true);
|
||||
|
||||
RNG rng = EntitySpawnSeed.marker(7721L, -25, -49, 57);
|
||||
IrisSpawner selected = new KList<>(first, second).getRandom(rng);
|
||||
IrisEntitySpawn entry = Rarity.expandWeighted(selected.getInitialSpawns()).getRandom(rng);
|
||||
int count = LootResolver.inclusive(rng, entry.getMinSpawns(), entry.getMaxSpawns());
|
||||
List<Spawned> expected = new ArrayList<>();
|
||||
for (int ordinal = 0; ordinal < count; ordinal++) {
|
||||
expected.add(new Spawned(entry.getRealEntity(fixture.engine).getType(), -25, -48, 57,
|
||||
EntitySpawnSeed.entity(rng.getSeed(), ordinal).nextLong()));
|
||||
}
|
||||
assertEquals(expected, fixture.spawned);
|
||||
Mantle<Matter> mantle = fixture.engine.getMantle().getMantle();
|
||||
if (marker.shouldExhaust(EntitySpawnSeed.entity(rng.getSeed(), -1))) {
|
||||
verify(mantle).remove(-25, 15, 57, MatterMarker.class);
|
||||
} else {
|
||||
verify(mantle, never()).remove(anyInt(), anyInt(), anyInt(), eq(MatterMarker.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static long key(int x, int z) {
|
||||
return (long) x & 0xFFFFFFFFL | (long) z << 32;
|
||||
}
|
||||
|
||||
private record Spawned(String type, int x, int y, int z, long customization) {
|
||||
}
|
||||
|
||||
private static final class Fixture implements AutoCloseable {
|
||||
private final Engine engine = mock(Engine.class);
|
||||
private final NativeWorld world = mock(NativeWorld.class);
|
||||
private final ModdedWorldManager manager = new ModdedWorldManager(engine);
|
||||
private final MockedStatic<IrisSettings> configured = mockStatic(IrisSettings.class);
|
||||
private final MockedStatic<NativeSpawnQueries> queries = mockStatic(NativeSpawnQueries.class);
|
||||
private final MockedStatic<ModdedEntitySpawner> entities = mockStatic(ModdedEntitySpawner.class);
|
||||
private final List<Spawned> spawned = new ArrayList<>();
|
||||
private final List<String> trace = new ArrayList<>();
|
||||
private final Set<Long> completed = new HashSet<>();
|
||||
private final List<IrisSpawner> definitions = new ArrayList<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Fixture() {
|
||||
IrisSettings settings = new IrisSettings();
|
||||
settings.getWorld().setAmbientEntitySpawningSystem(true);
|
||||
settings.getWorld().setMarkerEntitySpawningSystem(false);
|
||||
configured.when(IrisSettings::get).thenReturn(settings);
|
||||
EngineMantle engineMantle = mock(EngineMantle.class);
|
||||
Mantle<Matter> mantle = mock(Mantle.class);
|
||||
IrisWorld irisWorld = mock(IrisWorld.class);
|
||||
SeedManager seeds = mock(SeedManager.class);
|
||||
when(seeds.getEntity()).thenReturn(7721L);
|
||||
when(engine.getSeedManager()).thenReturn(seeds);
|
||||
when(engine.getWorld()).thenReturn(irisWorld);
|
||||
when(engine.getComplex()).thenReturn(mock(IrisComplex.class));
|
||||
when(engine.getMantle()).thenReturn(engineMantle);
|
||||
when(engineMantle.getMantle()).thenReturn(mantle);
|
||||
when(world.nativeHandle()).thenReturn(mock(ServerLevel.class));
|
||||
when(world.minHeight()).thenReturn(-64);
|
||||
when(world.maxHeight()).thenReturn(320);
|
||||
NativeBlockState support = mock(NativeBlockState.class);
|
||||
when(world.getBlock(anyInt(), anyInt(), anyInt())).thenReturn(support);
|
||||
queries.when(() -> NativeSpawnQueries.solid(support)).thenReturn(true);
|
||||
queries.when(() -> NativeSpawnQueries.ambientAllowed(eq(world), anyInt(), anyInt(), anyBoolean())).thenReturn(true);
|
||||
queries.when(() -> NativeSpawnQueries.surfaceHeight(eq(world), anyInt(), anyInt(), anyBoolean())).thenReturn(50);
|
||||
queries.when(() -> NativeSpawnQueries.livingEntityCategories(eq(world), anyInt(), anyInt()))
|
||||
.thenReturn(Map.of("creature", 1000));
|
||||
queries.when(() -> NativeSpawnQueries.light(eq(world), anyInt(), anyInt(), anyInt())).thenReturn(15);
|
||||
entities.when(() -> ModdedEntitySpawner.chunksSafe(any(NativeEntityRuntime.class), anyInt(), anyInt())).thenReturn(true);
|
||||
entities.when(() -> ModdedEntitySpawner.isAreaClearForSpawn(any(NativeEntityRuntime.class),
|
||||
any(IrisEntity.class), anyInt(), anyInt(), anyInt())).thenReturn(true);
|
||||
entities.when(() -> ModdedEntitySpawner.spawn(eq(engine), any(IrisEntity.class), any(NativeEntityRuntime.class),
|
||||
anyInt(), anyInt(), anyInt(), any(RNG.class))).thenAnswer(invocation -> {
|
||||
IrisEntity entity = invocation.getArgument(1);
|
||||
RNG rng = invocation.getArgument(6);
|
||||
spawned.add(new Spawned(entity.getType(), invocation.getArgument(3), invocation.getArgument(4),
|
||||
invocation.getArgument(5), rng.nextLong()));
|
||||
trace.add("spawn");
|
||||
return mock(NativeSpawnedEntity.class);
|
||||
});
|
||||
when(mantle.isChunkLoaded(anyInt(), anyInt())).thenReturn(true);
|
||||
when(mantle.hasFlag(anyInt(), anyInt(), eq(ModdedWorldManager.INITIAL_SPAWN_COMPLETION_FLAG)))
|
||||
.thenAnswer(invocation -> completed.contains(key(invocation.getArgument(0), invocation.getArgument(1))));
|
||||
when(mantle.useChunk(anyInt(), anyInt())).thenAnswer(invocation -> {
|
||||
long key = key(invocation.getArgument(0), invocation.getArgument(1));
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
doAnswer(flag -> {
|
||||
completed.add(key);
|
||||
trace.add("complete");
|
||||
return null;
|
||||
}).when(chunk).raiseFlagUnchecked(eq(ModdedWorldManager.INITIAL_SPAWN_COMPLETION_FLAG), any());
|
||||
return chunk;
|
||||
});
|
||||
IrisData data = mock(IrisData.class);
|
||||
ResourceLoader<IrisSpawner> loader = mock(ResourceLoader.class);
|
||||
when(data.getSpawnerLoader()).thenReturn(loader);
|
||||
IrisBiome biome = mock(IrisBiome.class);
|
||||
when(biome.getInferredType()).thenReturn(InferredType.LAND);
|
||||
when(biome.getEntitySpawners()).thenReturn(new KList<>());
|
||||
IrisRegion region = mock(IrisRegion.class);
|
||||
when(region.getEntitySpawners()).thenReturn(new KList<>());
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
when(dimension.getEntitySpawners()).thenReturn(new KList<>("land"));
|
||||
for (String type : List.of("minecraft:cow", "minecraft:pig")) {
|
||||
IrisEntity entity = new IrisEntity().setType(type);
|
||||
IrisEntitySpawn entry = spy(new IrisEntitySpawn().setMinSpawns(2).setMaxSpawns(5));
|
||||
doReturn(entity).when(entry).getRealEntity(engine);
|
||||
IrisSpawner spawner = spy(new IrisSpawner().setInitialSpawns(new KList<>(entry))
|
||||
.setMaxEntitiesPerChunk(3).setAllowedLightLevels(new IrisRange(0, 0)));
|
||||
doReturn(false).when(spawner).canSpawn(eq(engine), anyInt(), anyInt());
|
||||
definitions.add(spawner);
|
||||
}
|
||||
when(loader.loadAll(any(KList.class))).thenAnswer(invocation -> {
|
||||
KList<String> names = invocation.getArgument(0);
|
||||
return names.contains("land") ? new KList<>(definitions) : new KList<>();
|
||||
});
|
||||
BiomeEnvironment environment = new BiomeEnvironment(1L, biome, region, dimension, data);
|
||||
when(engine.getSurfaceBiomeEnvironment(anyInt(), anyInt())).thenReturn(environment);
|
||||
when(engine.openBiomeEnvironmentScope(environment)).thenReturn(mock(BiomeEnvironment.Scope.class));
|
||||
}
|
||||
|
||||
private Map<Long, List<Spawned>> byChunk() {
|
||||
Map<Long, List<Spawned>> output = new HashMap<>();
|
||||
for (Spawned spawn : spawned) {
|
||||
output.computeIfAbsent(key(spawn.x() >> 4, spawn.z() >> 4), ignored -> new ArrayList<>()).add(spawn);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
private void verifyInitialPolicy() {
|
||||
queries.verify(() -> NativeSpawnQueries.livingEntityCategories(eq(world), anyInt(), anyInt()), never());
|
||||
queries.verify(() -> NativeSpawnQueries.light(eq(world), anyInt(), anyInt(), anyInt()), never());
|
||||
for (IrisSpawner spawner : definitions) {
|
||||
verify(spawner, never()).canSpawn(eq(engine), anyInt(), anyInt());
|
||||
verify(spawner, never()).spawn(eq(engine), anyInt(), anyInt());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
manager.close();
|
||||
entities.close();
|
||||
queries.close();
|
||||
configured.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-1
@@ -5,11 +5,17 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeProtocolPlayer;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
||||
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeEntityRuntime;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeSpawnQueries;
|
||||
|
||||
import art.arcane.iris.configuration.IrisSettings;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.pack.loading.ResourceLoader;
|
||||
import art.arcane.iris.generation.runtime.BiomeEnvironment;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.generation.runtime.SeedManager;
|
||||
import art.arcane.iris.world.entity.IrisSpawner;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.world.history.SavedBiomeUnavailableException;
|
||||
import art.arcane.iris.generation.mantle.EngineMantle;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
@@ -72,6 +78,15 @@ public class ModdedSavedBiomeConsumersTest {
|
||||
when(mantle.isChunkLoaded(2, -1)).thenReturn(true);
|
||||
when(mantle.useChunk(2, -1)).thenReturn(chunk);
|
||||
BiomeEnvironment environment = environment();
|
||||
ResourceLoader<IrisSpawner> spawners = mock(ResourceLoader.class);
|
||||
when(environment.data().getSpawnerLoader()).thenReturn(spawners);
|
||||
when(spawners.loadAll(any(KList.class))).thenReturn(new KList<>());
|
||||
when(environment.dimension().getEntitySpawners()).thenReturn(new KList<>());
|
||||
when(environment.region().getEntitySpawners()).thenReturn(new KList<>());
|
||||
when(environment.biome().getEntitySpawners()).thenReturn(new KList<>());
|
||||
when(engine.getSeedManager()).thenReturn(mock(SeedManager.class));
|
||||
when(engine.getComplex()).thenReturn(mock(IrisComplex.class));
|
||||
when(engine.openBiomeEnvironmentScope(environment)).thenReturn(mock(BiomeEnvironment.Scope.class));
|
||||
when(engine.getSurfaceBiomeEnvironment(40, -8))
|
||||
.thenThrow(new SavedBiomeUnavailableException("Loading", true))
|
||||
.thenReturn(environment);
|
||||
@@ -81,8 +96,10 @@ public class ModdedSavedBiomeConsumersTest {
|
||||
ModdedWorldManager manager = new ModdedWorldManager(engine);
|
||||
|
||||
try (MockedStatic<IrisSettings> configured = mockStatic(IrisSettings.class);
|
||||
MockedStatic<ModdedEntitySpawner> spawns = mockStatic(ModdedEntitySpawner.class)) {
|
||||
MockedStatic<ModdedEntitySpawner> spawns = mockStatic(ModdedEntitySpawner.class);
|
||||
MockedStatic<NativeSpawnQueries> queries = mockStatic(NativeSpawnQueries.class)) {
|
||||
configured.when(IrisSettings::get).thenReturn(settings);
|
||||
queries.when(() -> NativeSpawnQueries.ambientAllowed(any(NativeWorld.class), eq(2), eq(-1), eq(true))).thenReturn(true);
|
||||
spawns.when(() -> ModdedEntitySpawner.chunksSafe(any(NativeEntityRuntime.class), eq(2), eq(-1))).thenReturn(true);
|
||||
assertThrows(SavedBiomeUnavailableException.class, () -> manager.initialSpawnChunk(new ModdedPlatformWorld(level), 2, -1));
|
||||
verify(chunk, never()).raiseFlagUnchecked(eq(ModdedWorldManager.INITIAL_SPAWN_COMPLETION_FLAG), any());
|
||||
|
||||
+3
-2
@@ -14,6 +14,7 @@ import art.arcane.iris.world.entity.IrisEntity;
|
||||
import art.arcane.iris.world.entity.IrisEntitySpawn;
|
||||
import art.arcane.iris.world.entity.IrisSpawnGroup;
|
||||
import art.arcane.iris.world.entity.IrisSpawner;
|
||||
import art.arcane.iris.world.entity.IrisEntitySpawn.SpawnContext;
|
||||
import art.arcane.iris.generation.decoration.IrisSurface;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
@@ -196,7 +197,7 @@ public class ModdedWaterEntitySpawnTest {
|
||||
doReturn(entity).when(entry).getRealEntity(engine);
|
||||
IrisSpawner spawner = new IrisSpawner().setGroup(IrisSpawnGroup.CAVE);
|
||||
Method spawn = ModdedWorldManager.class.getDeclaredMethod("spawnEntry", NativeWorld.class,
|
||||
IrisEntitySpawn.class, IrisSpawner.class, int.class, int.class, int.class);
|
||||
IrisEntitySpawn.class, int.class, int.class, int.class, RNG.class, SpawnContext.class);
|
||||
spawn.setAccessible(true);
|
||||
ModdedWorldManager manager = new ModdedWorldManager(engine);
|
||||
try (MockedStatic<ModdedEntitySpawner> entities = mockStatic(ModdedEntitySpawner.class)) {
|
||||
@@ -205,7 +206,7 @@ public class ModdedWaterEntitySpawnTest {
|
||||
entities.when(() -> ModdedEntitySpawner.spawn(eq(engine), eq(entity), any(NativeEntityRuntime.class),
|
||||
eq(-25), eq(-49), eq(57), any(RNG.class))).thenReturn(mock(NativeSpawnedEntity.class));
|
||||
|
||||
assertEquals(1, spawn.invoke(manager, world, entry, spawner, -2, 3, 1));
|
||||
assertEquals(1, spawn.invoke(manager, world, entry, -2, 3, 1, new RNG(1L), new SpawnContext(spawner, null, false)));
|
||||
|
||||
entities.verify(() -> ModdedEntitySpawner.spawn(eq(engine), eq(entity), any(NativeEntityRuntime.class),
|
||||
eq(-25), eq(-49), eq(57), any(RNG.class)));
|
||||
|
||||
Reference in new issue
Block a user