mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 21:40:05 +00:00
Add subterrain features
- Generate authored underground rooms, materials, fluids, and formations. - Preserve underground biome ownership across generation and saved terrain. - Add subterrain searches and regression coverage.
This commit is contained in:
1 parent
be0d3b0156
commit
5858eb366d
76 files changed
+4187
-201
No files matched your search
@@ -325,7 +325,7 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
|
||||
|
||||
private H getNoiseBiome(Engine engine, int quartX, int quartY, int quartZ,
|
||||
S sampler) {
|
||||
if (isGuaranteedSurfaceBiome(quartY, engine.getMinHeight())) {
|
||||
if (isGuaranteedSurfaceBiome(engine, quartY)) {
|
||||
return getSurfaceStructureBiome(engine, quartX, quartZ, sampler);
|
||||
}
|
||||
boolean cacheable = isBiomeCacheable(
|
||||
@@ -512,7 +512,7 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
|
||||
}
|
||||
boolean monumentQuery = isMonumentSurfaceBiomeQuery(
|
||||
y, radius, engine.getMinHeight(), engine.getDimension().getFluidHeight());
|
||||
surfaceQuery = monumentQuery || isGuaranteedSurfaceBiome(minQuartY, engine.getMinHeight());
|
||||
surfaceQuery = monumentQuery || isGuaranteedSurfaceBiome(engine, minQuartY);
|
||||
}
|
||||
if (!surfaceQuery) {
|
||||
return query.fallback().get();
|
||||
@@ -701,6 +701,11 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
|
||||
int blockZ = quartZ << 2;
|
||||
int internalY = blockY - engine.getMinHeight();
|
||||
int caveSwitchY = Math.max(-8 - engine.getMinHeight(), 40);
|
||||
IrisBiome featureBiome = engine.getSubterrainBiome(blockX, internalY, blockZ);
|
||||
if (featureBiome != null) {
|
||||
return createBiomeResolution(engine, featureBiome, true, engine.getDimension(), engine.getData(),
|
||||
blockX, blockY, blockZ);
|
||||
}
|
||||
DimensionStackLayout.Layer stackLayer = includeDimensionStack
|
||||
? stackLayout == null
|
||||
? resolveDimensionStackLayer(engine, blockX, internalY, blockZ)
|
||||
@@ -846,7 +851,13 @@ final class ModdedBiomePolicy<H, S> implements NativeModdedBiomePolicy<H, S> {
|
||||
return configuredBiomeKeys;
|
||||
}
|
||||
|
||||
static boolean isGuaranteedSurfaceBiome(int quartY, int minHeight) {
|
||||
static boolean isGuaranteedSurfaceBiome(Engine engine, int quartY) {
|
||||
IrisDimension dimension = engine.getDimension();
|
||||
if (dimension != null && dimension.allowsSubterrainFeatures()
|
||||
&& !dimension.getSubterrainFeatures().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
int minHeight = engine.getMinHeight();
|
||||
int internalY = (quartY << 2) - minHeight;
|
||||
int caveSwitchY = Math.max(-8 - minHeight, 40);
|
||||
return internalY > caveSwitchY;
|
||||
|
||||
+115
-6
@@ -58,6 +58,7 @@ import art.arcane.volmlib.util.matter.MatterMarker;
|
||||
import art.arcane.volmlib.util.matter.slices.MarkerMatter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
@@ -511,14 +512,18 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
}
|
||||
|
||||
IrisBiome biome = environment.biome();
|
||||
Map<String, Integer> chunkMobs = initial ? Map.of() : NativeSpawnQueries.livingEntityCategories(level, chunkX, chunkZ);
|
||||
Map<String, Integer> chunkMobs = new HashMap<>(initial ? Map.of()
|
||||
: NativeSpawnQueries.livingEntityCategories(level, chunkX, chunkZ));
|
||||
RNG rng = initial ? EntitySpawnSeed.chunk(engine.getSeedManager().getEntity(), chunkX, chunkZ)
|
||||
: new RNG(RNG.r.nextLong());
|
||||
|
||||
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);
|
||||
collectSpawns(pool, environment.data().getSpawnerLoader().loadAll(environment.dimension().getEntitySpawners()), biome, chunkX, chunkZ, chunkMobs, initial, false);
|
||||
collectSpawns(pool, environment.data().getSpawnerLoader().loadAll(environment.region().getEntitySpawners()), null, chunkX, chunkZ, chunkMobs, initial, false);
|
||||
collectSpawns(pool, environment.data().getSpawnerLoader().loadAll(biome.getEntitySpawners()), null, chunkX, chunkZ, chunkMobs, initial, false);
|
||||
if (environment.dimension().hasUndergroundSpawners(engine)) {
|
||||
spawnUnderground(level, chunkX, chunkZ, initial, chunkMobs, rng.nextParallelRNG(0x5A17D3B48269C0EFL));
|
||||
}
|
||||
if (pool.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -533,14 +538,111 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
}
|
||||
int spawned = spawnEntry(level, chosen.entry(), chunkX, chunkZ,
|
||||
remainingCapacity(chosen.entry(), spawner, chunkMobs), rng, new SpawnContext(spawner, null, initial));
|
||||
recordSpawned(chosen.entry(), chunkMobs, spawned);
|
||||
if (spawned > 0 && !initial) {
|
||||
spawner.spawn(engine, chunkX, chunkZ);
|
||||
}
|
||||
}
|
||||
|
||||
private void collectSpawns(KList<SpawnCandidate> pool, KList<IrisSpawner> spawners, IrisBiome biomeFilter, int chunkX, int chunkZ, Map<String, Integer> chunkMobs, boolean initial) {
|
||||
private void spawnUnderground(NativeWorld level, int chunkX, int chunkZ, boolean initial,
|
||||
Map<String, Integer> chunkMobs, RNG random) {
|
||||
for (IrisSurface surface : List.of(IrisSurface.LAND, IrisSurface.WATER, IrisSurface.LAVA)) {
|
||||
IrisPosition candidate = findUndergroundCandidate(level, chunkX, chunkZ,
|
||||
random.nextParallelRNG(surface.ordinal()), surface);
|
||||
if (candidate == null) {
|
||||
continue;
|
||||
}
|
||||
BiomeEnvironment environment;
|
||||
try {
|
||||
environment = engine.getBiomeOrMantleEnvironment(candidate.getX(),
|
||||
candidate.getY() - engine.getWorld().minHeight(), candidate.getZ());
|
||||
} catch (SavedBiomeUnavailableException unavailable) {
|
||||
continue;
|
||||
}
|
||||
try (BiomeEnvironment.Scope ignored = engine.openBiomeEnvironmentScope(environment)) {
|
||||
KList<SpawnCandidate> pool = new KList<>();
|
||||
collectSpawns(pool, environment.data().getSpawnerLoader().loadAll(environment.dimension().getEntitySpawners()),
|
||||
environment.biome(), chunkX, chunkZ, chunkMobs, initial, true);
|
||||
collectSpawns(pool, environment.data().getSpawnerLoader().loadAll(environment.region().getEntitySpawners()),
|
||||
null, chunkX, chunkZ, chunkMobs, initial, true);
|
||||
collectSpawns(pool, environment.data().getSpawnerLoader().loadAll(environment.biome().getEntitySpawners()),
|
||||
null, chunkX, chunkZ, chunkMobs, initial, true);
|
||||
pool.removeIf(selection -> !matchesUndergroundSurface(selection.entry(), surface));
|
||||
SpawnCandidate selected = rarityPick(pool, random);
|
||||
if (selected == null) {
|
||||
continue;
|
||||
}
|
||||
int capacity = remainingCapacity(selected.entry(), selected.spawner(), chunkMobs);
|
||||
int spawned = spawnUndergroundEntry(level, candidate, selected, capacity, random, initial);
|
||||
recordSpawned(selected.entry(), chunkMobs, spawned);
|
||||
if (spawned > 0 && !initial) {
|
||||
selected.spawner().spawn(engine, chunkX, chunkZ);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean matchesUndergroundSurface(IrisEntitySpawn entry, IrisSurface surface) {
|
||||
IrisEntity entity = entry.getRealEntity(engine);
|
||||
return entity != null && entity.getSurface().isFluid() == surface.isFluid()
|
||||
&& (!surface.isFluid() || entity.getSurface() == surface);
|
||||
}
|
||||
|
||||
static IrisPosition findUndergroundCandidate(NativeWorld level, int chunkX, int chunkZ, RNG random, IrisSurface surface) {
|
||||
int minimumY = level.minHeight() + 1;
|
||||
for (int attempt = 0; attempt < 8; attempt++) {
|
||||
int x = (chunkX << 4) + random.i(1, 15);
|
||||
int z = (chunkZ << 4) + random.i(1, 15);
|
||||
int maximumY = Math.min(level.maxHeight() - 3, NativeSpawnQueries.surfaceHeight(level, x, z, true) - 1);
|
||||
if (maximumY < minimumY) {
|
||||
continue;
|
||||
}
|
||||
int span = maximumY - minimumY + 1;
|
||||
int startY = LootResolver.inclusive(random, minimumY, maximumY);
|
||||
for (int sample = 0; sample < Math.min(span, 128); sample++) {
|
||||
int y = minimumY + Math.floorMod(startY - minimumY - sample, span);
|
||||
NativeBlockState state = level.getBlock(x, y, z);
|
||||
boolean acceptable = surface.isFluid() ? matchesSurface(surface, state)
|
||||
: state != null && state.isAir() && NativeSpawnQueries.solid(level.getBlock(x, y - 1, z))
|
||||
&& level.getBlock(x, y + 1, z).isAir();
|
||||
if (acceptable) {
|
||||
return new IrisPosition(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int spawnUndergroundEntry(NativeWorld level, IrisPosition candidate, SpawnCandidate selection,
|
||||
int capacity, RNG random, boolean initial) {
|
||||
IrisEntity entity = selection.entry().getRealEntity(engine);
|
||||
if (entity == null) {
|
||||
return 0;
|
||||
}
|
||||
int x = candidate.getX();
|
||||
int y = candidate.getY();
|
||||
int z = candidate.getZ();
|
||||
int count = Math.min(Math.max(0, capacity), LootResolver.inclusive(random,
|
||||
selection.entry().getMinSpawns(), selection.entry().getMaxSpawns()));
|
||||
int spawned = 0;
|
||||
for (int ordinal = 0; ordinal < count; ordinal++) {
|
||||
if (!initial && !lightAllowed(selection.spawner(), level, x, y, z)
|
||||
|| !surfaceMatches(entity.getSurface(), level, x, y, z)
|
||||
|| !ModdedEntitySpawner.isAreaClearForSpawn(entityRuntime(level), entity, x, y, z)) {
|
||||
continue;
|
||||
}
|
||||
if (ModdedEntitySpawner.spawn(engine, entity, entityRuntime(level), x, y, z,
|
||||
EntitySpawnSeed.entity(random.getSeed(), ordinal)) != null) {
|
||||
spawned++;
|
||||
}
|
||||
}
|
||||
return spawned;
|
||||
}
|
||||
|
||||
private void collectSpawns(KList<SpawnCandidate> pool, KList<IrisSpawner> spawners, IrisBiome biomeFilter, int chunkX, int chunkZ, Map<String, Integer> chunkMobs, boolean initial, boolean underground) {
|
||||
for (IrisSpawner spawner : spawners) {
|
||||
if (spawner == null) {
|
||||
if (spawner == null || (spawner.getGroup() == IrisSpawnGroup.CAVE) != underground) {
|
||||
continue;
|
||||
}
|
||||
if (biomeFilter != null && !spawner.isValid(biomeFilter)) {
|
||||
@@ -558,6 +660,13 @@ public final class ModdedWorldManager implements EngineWorldManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void recordSpawned(IrisEntitySpawn entry, Map<String, Integer> counts, int spawned) {
|
||||
IrisEntity entity = entry.getRealEntity(engine);
|
||||
if (spawned > 0 && entity != null) {
|
||||
counts.merge(entity.spawnCategory(), spawned, Integer::sum);
|
||||
}
|
||||
}
|
||||
|
||||
private int remainingCapacity(IrisEntitySpawn entry, IrisSpawner spawner, Map<String, Integer> counts) {
|
||||
IrisEntity entity = entry.getRealEntity(engine);
|
||||
return spawner.remainingCapacity(entity, counts);
|
||||
|
||||
+10
-4
@@ -38,12 +38,18 @@ public class IrisModdedStructureParityTest {
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
private static Engine surfaceEngine(int minHeight) {
|
||||
Engine engine = mock(Engine.class);
|
||||
when(engine.getMinHeight()).thenReturn(minHeight);
|
||||
return engine;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void surfaceBiomeFastPathBeginsAboveTheCaveSwitch() {
|
||||
assertFalse(ModdedBiomePolicy.isGuaranteedSurfaceBiome(-2, -256));
|
||||
assertTrue(ModdedBiomePolicy.isGuaranteedSurfaceBiome(-1, -256));
|
||||
assertFalse(ModdedBiomePolicy.isGuaranteedSurfaceBiome(10, 0));
|
||||
assertTrue(ModdedBiomePolicy.isGuaranteedSurfaceBiome(11, 0));
|
||||
assertFalse(ModdedBiomePolicy.isGuaranteedSurfaceBiome(surfaceEngine(-256), -2));
|
||||
assertTrue(ModdedBiomePolicy.isGuaranteedSurfaceBiome(surfaceEngine(-256), -1));
|
||||
assertFalse(ModdedBiomePolicy.isGuaranteedSurfaceBiome(surfaceEngine(0), 10));
|
||||
assertTrue(ModdedBiomePolicy.isGuaranteedSurfaceBiome(surfaceEngine(0), 11));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+39
@@ -2,6 +2,11 @@ package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.DimensionStackLayout;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.generation.subterrain.IrisSubterrainFeature;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.generation.runtime.GenerationSessionException;
|
||||
import art.arcane.iris.generation.runtime.GenerationSessionLease;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
@@ -11,20 +16,54 @@ import art.arcane.volmlib.util.stream.interpolation.Interpolated;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ModdedBiomePolicyTest {
|
||||
@Test
|
||||
public void authoredBiomeWinsAboveSurfaceGateAndBeforeStackOwnership() throws Exception {
|
||||
Engine engine = mock(Engine.class);
|
||||
IrisBiome feature = mock(IrisBiome.class);
|
||||
IrisDimension host = new IrisDimension().setSubterrainFeatures(new KList<>(
|
||||
new IrisSubterrainFeature().setId("room")));
|
||||
IrisData data = mock(IrisData.class);
|
||||
when(data.getDataFolder()).thenReturn(new File("synthetic-pack"));
|
||||
when(engine.getData()).thenReturn(data);
|
||||
when(engine.getDimension()).thenReturn(host);
|
||||
when(engine.getComplex()).thenReturn(mock(IrisComplex.class));
|
||||
when(engine.getMinHeight()).thenReturn(-64);
|
||||
when(engine.getSubterrainBiome(12, 224, -20)).thenReturn(feature);
|
||||
assertFalse(ModdedBiomePolicy.isGuaranteedSurfaceBiome(engine, 40));
|
||||
ModdedBiomePolicy<BiomeToken, Object> policy = boundTo(engine);
|
||||
DimensionStackLayout stack = mock(DimensionStackLayout.class);
|
||||
Method method = ModdedBiomePolicy.class.getDeclaredMethod("resolveBiomeResolution", Engine.class,
|
||||
int.class, int.class, int.class, boolean.class, DimensionStackLayout.class);
|
||||
method.setAccessible(true);
|
||||
Object resolution = method.invoke(policy, engine, 3, 40, -5, true, stack);
|
||||
Field biome = resolution.getClass().getDeclaredField("irisBiome");
|
||||
biome.setAccessible(true);
|
||||
assertSame(feature, biome.get(resolution));
|
||||
Field dimension = resolution.getClass().getDeclaredField("dimension");
|
||||
dimension.setAccessible(true);
|
||||
assertSame(host, dimension.get(resolution));
|
||||
verifyNoInteractions(stack);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registryOrderAndHolderIdentitySurviveThePolicyBoundaryAndRefreshOnRepoint() {
|
||||
BiomeToken ocean = new BiomeToken("minecraft:ocean");
|
||||
|
||||
+28
@@ -18,6 +18,7 @@ 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.IrisSpawnGroup;
|
||||
import art.arcane.iris.world.entity.IrisMarker;
|
||||
import art.arcane.iris.world.entity.EntitySpawnSeed;
|
||||
import art.arcane.iris.structure.placement.LootResolver;
|
||||
@@ -97,6 +98,33 @@ public class ModdedInitialEntitySpawnTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undergroundAndSurfacePassesShareTheSuccessfulPopulationBudget() {
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
BiomeEnvironment environment = fixture.engine.getSurfaceBiomeEnvironment(0, 0);
|
||||
when(environment.dimension().hasUndergroundSpawners(fixture.engine)).thenReturn(true);
|
||||
when(fixture.engine.getBiomeOrMantleEnvironment(anyInt(), anyInt(), anyInt())).thenReturn(environment);
|
||||
NativeBlockState stone = mock(NativeBlockState.class);
|
||||
NativeBlockState air = mock(NativeBlockState.class);
|
||||
when(air.isAir()).thenReturn(true);
|
||||
when(fixture.world.getBlock(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
|
||||
int y = invocation.getArgument(1);
|
||||
return y <= -21 || y == 50 ? stone : air;
|
||||
});
|
||||
fixture.queries.when(() -> NativeSpawnQueries.solid(stone)).thenReturn(true);
|
||||
IrisEntity entity = new IrisEntity().setType("minecraft:cow");
|
||||
IrisEntitySpawn entry = spy(new IrisEntitySpawn().setMinSpawns(2).setMaxSpawns(2));
|
||||
doReturn(entity).when(entry).getRealEntity(fixture.engine);
|
||||
fixture.definitions.add(new IrisSpawner().setGroup(IrisSpawnGroup.CAVE)
|
||||
.setInitialSpawns(new KList<>(entry)).setMaxEntitiesPerChunk(3));
|
||||
assertTrue(fixture.manager.initialSpawnChunk(fixture.world, -2, 3));
|
||||
assertEquals(3, fixture.spawned.size());
|
||||
assertEquals(2, fixture.spawned.stream().filter(spawn -> spawn.y() == -20).count());
|
||||
assertEquals(1, fixture.spawned.stream().filter(spawn -> spawn.y() == 51).count());
|
||||
fixture.queries.verify(() -> NativeSpawnQueries.livingEntityCategories(eq(fixture.world), anyInt(), anyInt()), never());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialCompletionFollowsOwnedSpawnPassAndDoesNotRepeatIt() {
|
||||
try (Fixture fixture = new Fixture()) {
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.decoration.IrisSurface;
|
||||
import art.arcane.iris.generation.runtime.BiomeEnvironment;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
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.value.IrisPosition;
|
||||
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.IrisSpawnGroup;
|
||||
import art.arcane.iris.world.entity.IrisSpawner;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeEntityRuntime;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeSpawnQueries;
|
||||
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.math.RNG;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ModdedUndergroundSpawnOwnershipTest {
|
||||
@BeforeClass
|
||||
public static void bootstrap() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void candidateSamplingAndPoolSelectionUseAbsoluteUndergroundOwner() throws Exception {
|
||||
NativeWorld world = mock(NativeWorld.class);
|
||||
when(world.minHeight()).thenReturn(-64);
|
||||
when(world.maxHeight()).thenReturn(320);
|
||||
NativeBlockState stone = mock(NativeBlockState.class);
|
||||
NativeBlockState air = mock(NativeBlockState.class);
|
||||
when(air.isAir()).thenReturn(true);
|
||||
when(world.getBlock(anyInt(), anyInt(), anyInt())).thenAnswer(call ->
|
||||
(int) call.getArgument(1) <= -21 ? stone : air);
|
||||
Engine engine = mock(Engine.class);
|
||||
when(engine.getWorld()).thenReturn(IrisWorld.builder().minHeight(-64).maxHeight(320).build());
|
||||
IrisBiome cave = new IrisBiome().setEntitySpawners(new KList<>("residents"));
|
||||
IrisDimension dimension = new IrisDimension();
|
||||
IrisRegion region = new IrisRegion();
|
||||
IrisData data = mock(IrisData.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
ResourceLoader<IrisSpawner> loader = mock(ResourceLoader.class);
|
||||
when(data.getSpawnerLoader()).thenReturn(loader);
|
||||
when(loader.loadAll(dimension.getEntitySpawners())).thenReturn(new KList<>());
|
||||
when(loader.loadAll(region.getEntitySpawners())).thenReturn(new KList<>());
|
||||
IrisEntitySpawn entry = mock(IrisEntitySpawn.class);
|
||||
IrisEntity entity = mock(IrisEntity.class);
|
||||
when(entry.getRealEntity(engine)).thenReturn(entity);
|
||||
when(entry.getRarity()).thenReturn(1);
|
||||
when(entry.getMinSpawns()).thenReturn(1);
|
||||
when(entry.getMaxSpawns()).thenReturn(1);
|
||||
when(entity.getSurface()).thenReturn(IrisSurface.LAND);
|
||||
when(entity.spawnCategory()).thenReturn("creature");
|
||||
IrisSpawner spawner = new IrisSpawner().setGroup(IrisSpawnGroup.CAVE).setInitialSpawns(new KList<>(entry));
|
||||
when(loader.loadAll(cave.getEntitySpawners())).thenReturn(new KList<>(spawner));
|
||||
BiomeEnvironment environment = new BiomeEnvironment(9L, cave, region, dimension, data);
|
||||
when(engine.getBiomeOrMantleEnvironment(anyInt(), eq(44), anyInt())).thenReturn(environment);
|
||||
BiomeEnvironment.Scope scope = mock(BiomeEnvironment.Scope.class);
|
||||
when(engine.openBiomeEnvironmentScope(environment)).thenReturn(scope);
|
||||
NativeEntityRuntime runtime = mock(NativeEntityRuntime.class);
|
||||
when(runtime.world()).thenReturn(world);
|
||||
ModdedWorldManager manager = new ModdedWorldManager(engine);
|
||||
try (MockedStatic<NativeSpawnQueries> queries = mockStatic(NativeSpawnQueries.class);
|
||||
MockedStatic<ModdedEntitySpawner> entities = mockStatic(ModdedEntitySpawner.class)) {
|
||||
queries.when(() -> NativeSpawnQueries.surfaceHeight(eq(world), anyInt(), anyInt(), eq(true))).thenReturn(64);
|
||||
queries.when(() -> NativeSpawnQueries.solid(stone)).thenReturn(true);
|
||||
IrisPosition first = ModdedWorldManager.findUndergroundCandidate(world, 2, -1, new RNG(88771L), IrisSurface.LAND);
|
||||
IrisPosition second = ModdedWorldManager.findUndergroundCandidate(world, 2, -1, new RNG(88771L), IrisSurface.LAND);
|
||||
assertNotNull(first);
|
||||
assertEquals(first, second);
|
||||
assertEquals(-20, first.getY());
|
||||
assertTrue(first.getX() >= 33 && first.getX() <= 47 && first.getZ() >= -15 && first.getZ() <= -1);
|
||||
Field field = ModdedWorldManager.class.getDeclaredField("entityRuntime");
|
||||
field.setAccessible(true);
|
||||
field.set(manager, runtime);
|
||||
entities.when(() -> ModdedEntitySpawner.isAreaClearForSpawn(eq(runtime), eq(entity), anyInt(), eq(-20), anyInt()))
|
||||
.thenReturn(true);
|
||||
Method spawn = ModdedWorldManager.class.getDeclaredMethod("spawnUnderground", NativeWorld.class,
|
||||
int.class, int.class, boolean.class, Map.class, RNG.class);
|
||||
spawn.setAccessible(true);
|
||||
spawn.invoke(manager, world, 2, -1, true, Map.of(), new RNG(88771L));
|
||||
entities.verify(() -> ModdedEntitySpawner.spawn(eq(engine), eq(entity), eq(runtime), anyInt(), eq(-20), anyInt(), any(RNG.class)));
|
||||
} finally {
|
||||
manager.close();
|
||||
}
|
||||
verify(scope).close();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user