mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-11-01 08:50:06 +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);
|
||||
|
||||
Reference in new issue
Block a user