mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 05:50:39 +00:00
Added dimension stacking
- Dimension files define upright top-to-bottom terrain layers - Spacer values set exact air gaps between layers - Optional noise styles shape blended layer boundaries - Runtime queries resolve stacked terrain and biome ownership - Pack validation rejects invalid stack definitions - Project copy and hotload paths include stack dependencies - Superflat fixtures cover gaps, blending, clipping, and fluids - Platform tests cover stacked world generation contracts
This commit is contained in:
1 parent
366033a535
commit
6549b40797
119 files changed
+6874
-743
No files matched your search
+405
-58
@@ -4,6 +4,9 @@ import art.arcane.iris.platform.bukkit.BukkitWorldBinding;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.engine.DimensionStackContext;
|
||||
import art.arcane.iris.engine.DimensionStackLayout;
|
||||
import art.arcane.iris.engine.DimensionTerrainContext;
|
||||
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.GenerationSessionException;
|
||||
@@ -16,6 +19,7 @@ import art.arcane.iris.util.project.context.IrisContext;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.QuartPos;
|
||||
import net.minecraft.core.Registry;
|
||||
@@ -24,6 +28,8 @@ import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.biome.Climate;
|
||||
@@ -80,27 +86,31 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
this.cacheRuntimeId = engine.getCacheID();
|
||||
}
|
||||
|
||||
private static List<Holder<Biome>> getAllBiomes(Registry<Biome> customRegistry, Registry<Biome> registry, Engine engine) {
|
||||
private static List<Holder<Biome>> getAllBiomes(
|
||||
Registry<Biome> customRegistry,
|
||||
Registry<Biome> registry,
|
||||
Engine engine,
|
||||
boolean includeDimensionStack
|
||||
) {
|
||||
LinkedHashSet<Holder<Biome>> biomes = new LinkedHashSet<>();
|
||||
|
||||
for (IrisBiome i : engine.getAllBiomes()) {
|
||||
for (OwnedBiome ownedBiome : getHostOwnedBiomes(engine)) {
|
||||
IrisBiome i = ownedBiome.biome();
|
||||
Holder<Biome> vanillaHolder = resolveBiomeHolder(registry, i.getStructureDerivativeKey());
|
||||
if (vanillaHolder == null) {
|
||||
throw new IllegalStateException("Iris structure biome derivative '"
|
||||
+ i.getStructureDerivativeKey() + "' is not registered for biome '" + i.getLoadKey() + "'");
|
||||
}
|
||||
biomes.add(vanillaHolder);
|
||||
}
|
||||
|
||||
if (i.isCustom()) {
|
||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||
Holder<Biome> customHolder = resolveCustomBiomeHolder(customRegistry, engine, j.getId());
|
||||
if (customHolder == null) {
|
||||
throw new IllegalStateException("Iris custom structure biome '"
|
||||
+ engine.getDimension().getLoadKey() + ":" + j.getId() + "' is not registered");
|
||||
}
|
||||
biomes.add(customHolder);
|
||||
}
|
||||
}
|
||||
if (includeDimensionStack) {
|
||||
biomes.addAll(getVisibleBiomes(
|
||||
customRegistry,
|
||||
registry,
|
||||
engine,
|
||||
resolveFallbackBiome(registry, customRegistry)
|
||||
));
|
||||
}
|
||||
|
||||
if (biomes.isEmpty()) {
|
||||
@@ -110,6 +120,57 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
return new ArrayList<>(biomes);
|
||||
}
|
||||
|
||||
private static List<Holder<Biome>> getVisibleBiomes(
|
||||
Registry<Biome> customRegistry,
|
||||
Registry<Biome> registry,
|
||||
Engine engine,
|
||||
Holder<Biome> fallback
|
||||
) {
|
||||
LinkedHashSet<Holder<Biome>> biomes = new LinkedHashSet<>();
|
||||
boolean fallbackPossible = false;
|
||||
for (OwnedBiome ownedBiome : getOwnedBiomes(engine)) {
|
||||
IrisBiome irisBiome = ownedBiome.biome();
|
||||
if (irisBiome.isCustom()) {
|
||||
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
|
||||
Holder<Biome> customHolder = resolveCustomBiomeHolder(
|
||||
customRegistry, ownedBiome.dimension(), customBiome.getId());
|
||||
if (customHolder == null) {
|
||||
throw new IllegalStateException("Iris custom visible biome '"
|
||||
+ customBiomeKey(ownedBiome.dimension(), customBiome.getId())
|
||||
+ "' is not registered");
|
||||
}
|
||||
biomes.add(customHolder);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
fallbackPossible |= !addVisibleBiomeHolder(
|
||||
biomes, registry, irisBiome.getDerivativeKey());
|
||||
for (String scatter : irisBiome.getBiomeScatter()) {
|
||||
fallbackPossible |= !addVisibleBiomeHolder(biomes, registry, scatter);
|
||||
}
|
||||
for (String scatter : irisBiome.getBiomeSkyScatter()) {
|
||||
fallbackPossible |= !addVisibleBiomeHolder(biomes, registry, scatter);
|
||||
}
|
||||
}
|
||||
if (fallbackPossible && fallback != null) {
|
||||
biomes.add(fallback);
|
||||
}
|
||||
return new ArrayList<>(biomes);
|
||||
}
|
||||
|
||||
private static boolean addVisibleBiomeHolder(
|
||||
Set<Holder<Biome>> biomes,
|
||||
Registry<Biome> registry,
|
||||
String biomeKey
|
||||
) {
|
||||
Holder<Biome> holder = resolveBiomeHolder(registry, biomeKey);
|
||||
if (holder == null) {
|
||||
return false;
|
||||
}
|
||||
biomes.add(holder);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Object getFor(Class<?> type, Object source) {
|
||||
Object o = fieldFor(type, source);
|
||||
|
||||
@@ -175,10 +236,19 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
|
||||
@Override
|
||||
protected Stream<Holder<Biome>> collectPossibleBiomes() {
|
||||
return possibleStructureBiomes().stream();
|
||||
return possibleBiomes(true).stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Holder<Biome>> possibleBiomes() {
|
||||
return possibleBiomes(true);
|
||||
}
|
||||
|
||||
Set<Holder<Biome>> possibleStructureBiomes() {
|
||||
return possibleBiomes(false);
|
||||
}
|
||||
|
||||
private Set<Holder<Biome>> possibleBiomes(boolean includeDimensionStack) {
|
||||
GenerationSessionLease lease = tryAcquireGenerationLease("bukkit_possible_biomes");
|
||||
if (lease == null) {
|
||||
throw new IllegalStateException("Iris possible biome lookup was rejected during an engine transition");
|
||||
@@ -197,7 +267,8 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
.lookup(Registries.BIOME).orElse(null);
|
||||
Registry<Biome> worldRegistry = ((CraftWorld) world).getHandle().registryAccess()
|
||||
.lookup(Registries.BIOME).orElse(null);
|
||||
return Set.copyOf(getAllBiomes(customRegistry, worldRegistry, engine));
|
||||
return Set.copyOf(getAllBiomes(
|
||||
customRegistry, worldRegistry, engine, includeDimensionStack));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,18 +278,22 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
return m;
|
||||
}
|
||||
|
||||
for (IrisBiome i : engine.getAllBiomes()) {
|
||||
for (OwnedBiome ownedBiome : getOwnedBiomes(engine)) {
|
||||
IrisBiome i = ownedBiome.biome();
|
||||
if (i.isCustom()) {
|
||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||
Holder<Biome> holder = resolveCustomBiomeHolder(customRegistry, engine, j.getId());
|
||||
String key = customBiomeKey(ownedBiome.dimension(), j.getId());
|
||||
Holder<Biome> holder = resolveCustomBiomeHolder(
|
||||
customRegistry, ownedBiome.dimension(), j.getId());
|
||||
if (holder == null) {
|
||||
if (fallback != null) {
|
||||
m.put(j.getId(), fallback);
|
||||
m.put(key, fallback);
|
||||
}
|
||||
IrisLogging.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
|
||||
IrisLogging.error("Cannot find biome for IrisBiomeCustom " + key
|
||||
+ " from engine " + engine.getName());
|
||||
continue;
|
||||
}
|
||||
m.put(j.getId(), holder);
|
||||
m.put(key, holder);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,7 +307,8 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
return Collections.unmodifiableMap(spawnBiomes);
|
||||
}
|
||||
|
||||
for (IrisBiome irisBiome : engine.getAllBiomes()) {
|
||||
for (OwnedBiome ownedBiome : getOwnedBiomes(engine)) {
|
||||
IrisBiome irisBiome = ownedBiome.biome();
|
||||
if (!irisBiome.isCustom()) {
|
||||
continue;
|
||||
}
|
||||
@@ -241,7 +317,8 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
continue;
|
||||
}
|
||||
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
|
||||
Holder<Biome> customHolder = resolveCustomBiomeHolder(customRegistry, engine, customBiome.getId());
|
||||
Holder<Biome> customHolder = resolveCustomBiomeHolder(
|
||||
customRegistry, ownedBiome.dimension(), customBiome.getId());
|
||||
if (customHolder != null) {
|
||||
spawnBiomes.putIfAbsent(customHolder.value(), vanillaHolder);
|
||||
}
|
||||
@@ -268,6 +345,34 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
}
|
||||
}
|
||||
|
||||
Holder<Biome> getVisibleSurfaceBiome(int blockX, int blockZ) {
|
||||
GenerationSessionLease lease = tryAcquireGenerationLease("bukkit_surface_spawn_biome");
|
||||
if (lease == null) {
|
||||
return null;
|
||||
}
|
||||
try (lease; IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
|
||||
if (!isRuntimeAvailable()) {
|
||||
return null;
|
||||
}
|
||||
ensureCachesCurrent();
|
||||
int quartX = QuartPos.fromBlock(blockX);
|
||||
int quartZ = QuartPos.fromBlock(blockZ);
|
||||
int sampleX = QuartPos.toBlock(quartX);
|
||||
int sampleZ = QuartPos.toBlock(quartZ);
|
||||
boolean cacheable = isBiomeCacheable(engine, sampleX, sampleZ);
|
||||
DimensionStackContext stackContext = engine.getDimensionStackContext();
|
||||
DimensionStackLayout.Layer layer = stackContext == null
|
||||
? null
|
||||
: stackContext.getLayout(sampleX, sampleZ).surfaceLayer();
|
||||
int internalY = layer == null
|
||||
? Engine.hostHeight(engine, sampleX, sampleZ, true)
|
||||
: layer.clippedSurfaceY();
|
||||
int quartY = QuartPos.fromBlock(internalY + engine.getWorld().minHeight());
|
||||
return getVisibleNoiseBiomeWithActiveGenerationLease(
|
||||
quartX, quartY, quartZ, null, null, cacheable);
|
||||
}
|
||||
}
|
||||
|
||||
private RegistryAccess registry() {
|
||||
RegistryAccess access = registryAccess.aquire(() -> (RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer()));
|
||||
|
||||
@@ -294,28 +399,38 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
throw new IllegalStateException("Iris structure biome lookup has no active engine runtime");
|
||||
}
|
||||
ensureCachesCurrent();
|
||||
if (isGuaranteedSurfaceBiome(y)) {
|
||||
return getSurfaceStructureBiomeHolder(x, z);
|
||||
}
|
||||
return getStructureNoiseBiomeWithActiveGenerationLease(x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
long cacheKey = packNoiseKey(x, y, z);
|
||||
private Holder<Biome> getStructureNoiseBiomeWithActiveGenerationLease(int x, int y, int z) {
|
||||
if (isGuaranteedSurfaceBiome(y)) {
|
||||
return getSurfaceStructureBiomeHolder(x, z);
|
||||
}
|
||||
|
||||
boolean cacheable = isBiomeCacheable(x, z);
|
||||
long cacheKey = packNoiseKey(x, y, z);
|
||||
if (cacheable) {
|
||||
Holder<Biome> cachedHolder = structureBiomeCache.get(cacheKey);
|
||||
if (cachedHolder != null) {
|
||||
return cachedHolder;
|
||||
}
|
||||
}
|
||||
|
||||
Holder<Biome> resolvedHolder = resolveStructureBiomeHolder(x, y, z);
|
||||
Holder<Biome> existingHolder = structureBiomeCache.putIfAbsent(cacheKey, resolvedHolder);
|
||||
if (existingHolder != null) {
|
||||
return existingHolder;
|
||||
}
|
||||
|
||||
if (structureBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
|
||||
structureBiomeCache.clear();
|
||||
}
|
||||
|
||||
Holder<Biome> resolvedHolder = resolveStructureBiomeHolder(x, y, z);
|
||||
if (!cacheable) {
|
||||
return resolvedHolder;
|
||||
}
|
||||
Holder<Biome> existingHolder = structureBiomeCache.putIfAbsent(cacheKey, resolvedHolder);
|
||||
if (existingHolder != null) {
|
||||
return existingHolder;
|
||||
}
|
||||
|
||||
if (structureBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
|
||||
structureBiomeCache.clear();
|
||||
}
|
||||
|
||||
return resolvedHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -346,6 +461,62 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<BlockPos, Holder<Biome>> findClosestBiome3d(
|
||||
BlockPos origin,
|
||||
int searchRadius,
|
||||
int sampleResolutionHorizontal,
|
||||
int sampleResolutionVertical,
|
||||
Predicate<Holder<Biome>> allowed,
|
||||
Climate.Sampler sampler,
|
||||
LevelReader level
|
||||
) {
|
||||
GenerationSessionLease lease = tryAcquireGenerationLease("bukkit_locate_visible_biome");
|
||||
if (lease == null) {
|
||||
throw new IllegalStateException("Iris visible biome search was rejected during an engine transition");
|
||||
}
|
||||
try (lease; IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
|
||||
prepareVisibleBiomeBatch();
|
||||
DimensionStackContext stackContext = engine.getDimensionStackContext();
|
||||
if (stackContext == null) {
|
||||
return super.findClosestBiome3d(
|
||||
origin, searchRadius, sampleResolutionHorizontal, sampleResolutionVertical,
|
||||
allowed, sampler, level);
|
||||
}
|
||||
Set<Holder<Biome>> candidates = new HashSet<>();
|
||||
for (Holder<Biome> biome : getVisibleBiomes(
|
||||
biomeCustomRegistry, biomeRegistry, engine, fallbackBiome)) {
|
||||
if (allowed.test(biome)) {
|
||||
candidates.add(biome);
|
||||
}
|
||||
}
|
||||
if (candidates.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
int sampleRadius = Math.floorDiv(searchRadius, sampleResolutionHorizontal);
|
||||
int[] sampleYs = Mth.outFromOrigin(
|
||||
origin.getY(), level.getMinY() + 1, level.getMaxY() + 1, sampleResolutionVertical).toArray();
|
||||
for (BlockPos.MutableBlockPos sampleColumn : BlockPos.spiralAround(
|
||||
BlockPos.ZERO, sampleRadius, Direction.EAST, Direction.SOUTH)) {
|
||||
int blockX = origin.getX() + sampleColumn.getX() * sampleResolutionHorizontal;
|
||||
int blockZ = origin.getZ() + sampleColumn.getZ() * sampleResolutionHorizontal;
|
||||
int quartX = QuartPos.fromBlock(blockX);
|
||||
int quartZ = QuartPos.fromBlock(blockZ);
|
||||
DimensionStackLayout stackLayout = stackContext.sample(
|
||||
QuartPos.toBlock(quartX), QuartPos.toBlock(quartZ));
|
||||
for (int blockY : sampleYs) {
|
||||
int quartY = QuartPos.fromBlock(blockY);
|
||||
Holder<Biome> visibleBiome = resolveVisibleBiomeHolder(
|
||||
quartX, quartY, quartZ, null, stackLayout);
|
||||
if (candidates.contains(visibleBiome)) {
|
||||
return Pair.of(new BlockPos(blockX, blockY, blockZ), visibleBiome);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static int horizontalBiomeSearchQuartStep(int blockY, int searchRadius) {
|
||||
return blockY == STRONGHOLD_RING_SEARCH_Y && searchRadius == STRONGHOLD_RING_SEARCH_RADIUS
|
||||
? STRONGHOLD_RING_SEARCH_QUART_STEP
|
||||
@@ -468,7 +639,7 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
private Holder<Biome> resolveSurfaceStructureBiomeHolder(int x, int z) {
|
||||
int blockX = x << 2;
|
||||
int blockZ = z << 2;
|
||||
IrisBiome irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
||||
IrisBiome irisBiome = resolveSurfaceStructureBiome(engine, blockX, blockZ);
|
||||
if (irisBiome == null) {
|
||||
throw new IllegalStateException("Iris returned no surface structure biome at block "
|
||||
+ blockX + "," + blockZ);
|
||||
@@ -532,14 +703,32 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
int z,
|
||||
Climate.Sampler sampler,
|
||||
IrisDimensionCarvingResolver.State resolverState
|
||||
) {
|
||||
boolean cacheable = isBiomeCacheable(x, z);
|
||||
return getVisibleNoiseBiomeWithActiveGenerationLease(
|
||||
x, y, z, sampler, resolverState, cacheable);
|
||||
}
|
||||
|
||||
private Holder<Biome> getVisibleNoiseBiomeWithActiveGenerationLease(
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
Climate.Sampler sampler,
|
||||
IrisDimensionCarvingResolver.State resolverState,
|
||||
boolean cacheable
|
||||
) {
|
||||
long cacheKey = packNoiseKey(x, y, z);
|
||||
Holder<Biome> cachedHolder = noiseBiomeCache.get(cacheKey);
|
||||
if (cachedHolder != null) {
|
||||
return cachedHolder;
|
||||
if (cacheable) {
|
||||
Holder<Biome> cachedHolder = noiseBiomeCache.get(cacheKey);
|
||||
if (cachedHolder != null) {
|
||||
return cachedHolder;
|
||||
}
|
||||
}
|
||||
|
||||
Holder<Biome> resolvedHolder = resolveVisibleBiomeHolder(x, y, z, resolverState);
|
||||
if (!cacheable) {
|
||||
return resolvedHolder;
|
||||
}
|
||||
Holder<Biome> existingHolder = noiseBiomeCache.putIfAbsent(cacheKey, resolvedHolder);
|
||||
if (existingHolder != null) {
|
||||
return existingHolder;
|
||||
@@ -552,6 +741,18 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
return resolvedHolder;
|
||||
}
|
||||
|
||||
private boolean isBiomeCacheable(int quartX, int quartZ) {
|
||||
return isBiomeCacheable(
|
||||
engine,
|
||||
QuartPos.toBlock(quartX),
|
||||
QuartPos.toBlock(quartZ)
|
||||
);
|
||||
}
|
||||
|
||||
static boolean isBiomeCacheable(Engine engine, int blockX, int blockZ) {
|
||||
return !engine.answersFromNaturalTerrain(blockX, blockZ);
|
||||
}
|
||||
|
||||
private GenerationSessionLease tryAcquireGenerationLease(String operation) {
|
||||
if (engine.isClosed()) {
|
||||
return null;
|
||||
@@ -597,7 +798,7 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
}
|
||||
|
||||
private Holder<Biome> resolveStructureBiomeHolder(int x, int y, int z) {
|
||||
BiomeResolution resolution = resolveBiomeResolution(x, y, z);
|
||||
BiomeResolution resolution = resolveStructureBiomeResolution(x, y, z);
|
||||
if (resolution == null) {
|
||||
throw new IllegalStateException("Iris returned no structure biome at quart "
|
||||
+ x + "," + y + "," + z);
|
||||
@@ -619,11 +820,26 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
int z,
|
||||
IrisDimensionCarvingResolver.State resolverState
|
||||
) {
|
||||
BiomeResolution resolution = resolveBiomeResolution(x, y, z, resolverState);
|
||||
return resolveVisibleBiomeHolder(x, y, z, resolverState, null);
|
||||
}
|
||||
|
||||
private Holder<Biome> resolveVisibleBiomeHolder(
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
IrisDimensionCarvingResolver.State resolverState,
|
||||
DimensionStackLayout stackLayout
|
||||
) {
|
||||
BiomeResolution resolution = resolveBiomeResolution(
|
||||
x, y, z, resolverState, true, stackLayout);
|
||||
if (resolution == null) {
|
||||
return getFallbackBiome();
|
||||
}
|
||||
|
||||
return resolveVisibleBiomeHolder(resolution);
|
||||
}
|
||||
|
||||
private Holder<Biome> resolveVisibleBiomeHolder(BiomeResolution resolution) {
|
||||
if (resolution.irisBiome.isCustom()) {
|
||||
return resolveCustomHolder(resolution);
|
||||
}
|
||||
@@ -642,7 +858,8 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
private Holder<Biome> resolveCustomHolder(BiomeResolution resolution) {
|
||||
IrisBiomeCustom customBiome = resolution.irisBiome.getCustomBiome(resolution.rng, engine, resolution.blockX, resolution.blockY, resolution.blockZ);
|
||||
if (customBiome != null) {
|
||||
Holder<Biome> holder = customBiomes.get(customBiome.getId());
|
||||
Holder<Biome> holder = customBiomes.get(customBiomeKey(
|
||||
resolution.dimension, customBiome.getId()));
|
||||
if (holder != null) {
|
||||
return holder;
|
||||
}
|
||||
@@ -651,8 +868,8 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
return getFallbackBiome();
|
||||
}
|
||||
|
||||
private BiomeResolution resolveBiomeResolution(int x, int y, int z) {
|
||||
return resolveBiomeResolution(x, y, z, null);
|
||||
private BiomeResolution resolveStructureBiomeResolution(int x, int y, int z) {
|
||||
return resolveBiomeResolution(x, y, z, null, false);
|
||||
}
|
||||
|
||||
private BiomeResolution resolveBiomeResolution(
|
||||
@@ -660,6 +877,28 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
int y,
|
||||
int z,
|
||||
IrisDimensionCarvingResolver.State resolverState
|
||||
) {
|
||||
return resolveBiomeResolution(x, y, z, resolverState, true);
|
||||
}
|
||||
|
||||
private BiomeResolution resolveBiomeResolution(
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
IrisDimensionCarvingResolver.State resolverState,
|
||||
boolean includeDimensionStack
|
||||
) {
|
||||
return resolveBiomeResolution(
|
||||
x, y, z, resolverState, includeDimensionStack, null);
|
||||
}
|
||||
|
||||
private BiomeResolution resolveBiomeResolution(
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
IrisDimensionCarvingResolver.State resolverState,
|
||||
boolean includeDimensionStack,
|
||||
DimensionStackLayout stackLayout
|
||||
) {
|
||||
if (engine == null || engine.isClosed()) {
|
||||
return null;
|
||||
@@ -675,31 +914,63 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
int worldMinHeight = engine.getWorld().minHeight();
|
||||
int internalY = blockY - worldMinHeight;
|
||||
int caveSwitchInternalY = Math.max(-8 - worldMinHeight, 40);
|
||||
DimensionStackLayout.Layer stackLayer = includeDimensionStack
|
||||
? stackLayout == null
|
||||
? resolveDimensionStackLayer(engine, blockX, internalY, blockZ)
|
||||
: stackLayout.layerAt(internalY)
|
||||
: null;
|
||||
IrisDimension owningDimension = stackLayer == null
|
||||
? engine.getDimension()
|
||||
: stackLayer.terrainContext().getDimension();
|
||||
if (stackLayer != null && !stackLayer.terrainContext().isSelfReferencing()) {
|
||||
IrisBiome stackedBiome = stackLayer.biome();
|
||||
return stackedBiome == null
|
||||
? null
|
||||
: createBiomeResolution(
|
||||
stackedBiome, false, owningDimension, blockX, blockY, blockZ);
|
||||
}
|
||||
boolean deepUnderground = internalY <= caveSwitchInternalY;
|
||||
boolean underground = false;
|
||||
IrisBiome irisBiome;
|
||||
if (deepUnderground) {
|
||||
int surfaceInternalY = engine.getComplex().getHeightStream().get(blockX, blockZ).intValue();
|
||||
if (stackLayer != null) {
|
||||
int surfaceInternalY = stackLayer.surfaceY();
|
||||
underground = internalY <= surfaceInternalY - 8;
|
||||
irisBiome = underground
|
||||
? engine.getCaveBiome(
|
||||
blockX,
|
||||
internalY,
|
||||
blockZ,
|
||||
resolverState,
|
||||
stackLayer.biome(),
|
||||
surfaceInternalY
|
||||
)
|
||||
: stackLayer.biome();
|
||||
} else if (deepUnderground) {
|
||||
int surfaceInternalY = Engine.hostHeight(engine, blockX, blockZ, true);
|
||||
underground = internalY <= surfaceInternalY - 8;
|
||||
irisBiome = underground
|
||||
? engine.getCaveBiome(blockX, internalY, blockZ, resolverState)
|
||||
: engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
||||
: engine.getHostSurfaceBiome(blockX, blockZ);
|
||||
} else {
|
||||
irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
||||
irisBiome = engine.getHostSurfaceBiome(blockX, blockZ);
|
||||
}
|
||||
if (irisBiome == null && underground) {
|
||||
irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
||||
irisBiome = stackLayer == null
|
||||
? engine.getHostSurfaceBiome(blockX, blockZ)
|
||||
: stackLayer.biome();
|
||||
}
|
||||
if (irisBiome == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createBiomeResolution(irisBiome, underground, blockX, blockY, blockZ);
|
||||
return createBiomeResolution(
|
||||
irisBiome, underground, owningDimension, blockX, blockY, blockZ);
|
||||
}
|
||||
|
||||
private BiomeResolution createBiomeResolution(
|
||||
IrisBiome irisBiome,
|
||||
boolean underground,
|
||||
IrisDimension dimension,
|
||||
int blockX,
|
||||
int blockY,
|
||||
int blockZ
|
||||
@@ -708,7 +979,8 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
^ (((long) blockX) * 341873128712L)
|
||||
^ (((long) blockY) * 132897987541L)
|
||||
^ (((long) blockZ) * 42317861L));
|
||||
return new BiomeResolution(irisBiome, underground, blockX, blockY, blockZ, noiseRng);
|
||||
return new BiomeResolution(
|
||||
irisBiome, underground, dimension, blockX, blockY, blockZ, noiseRng);
|
||||
}
|
||||
|
||||
private Holder<Biome> getFallbackBiome() {
|
||||
@@ -734,15 +1006,20 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
return ((long) x << 32) ^ ((long) z & 4294967295L);
|
||||
}
|
||||
|
||||
private static Holder<Biome> resolveCustomBiomeHolder(Registry<Biome> customRegistry, Engine engine, String customBiomeId) {
|
||||
if (customRegistry == null || engine == null || customBiomeId == null || customBiomeId.isBlank()) {
|
||||
private static Holder<Biome> resolveCustomBiomeHolder(
|
||||
Registry<Biome> customRegistry,
|
||||
IrisDimension dimension,
|
||||
String customBiomeId
|
||||
) {
|
||||
if (customRegistry == null || dimension == null
|
||||
|| customBiomeId == null || customBiomeId.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Identifier resourceLocation = Identifier.fromNamespaceAndPath(
|
||||
engine.getDimension().getLoadKey().toLowerCase(java.util.Locale.ROOT),
|
||||
customBiomeId.toLowerCase(java.util.Locale.ROOT)
|
||||
);
|
||||
Identifier resourceLocation = Identifier.tryParse(customBiomeKey(dimension, customBiomeId));
|
||||
if (resourceLocation == null) {
|
||||
return null;
|
||||
}
|
||||
Biome biome = customRegistry.getValue(resourceLocation);
|
||||
if (biome == null) {
|
||||
return null;
|
||||
@@ -761,6 +1038,63 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
return optionalReferenceHolder.get();
|
||||
}
|
||||
|
||||
static String customBiomeKey(IrisDimension dimension, String customBiomeId) {
|
||||
return dimension.getCustomBiomeKey(customBiomeId);
|
||||
}
|
||||
|
||||
private static List<OwnedBiome> getOwnedBiomes(Engine engine) {
|
||||
ArrayList<OwnedBiome> biomes = new ArrayList<>(getHostOwnedBiomes(engine));
|
||||
DimensionStackContext stackContext = engine.getDimensionStackContext();
|
||||
if (stackContext == null) {
|
||||
return biomes;
|
||||
}
|
||||
for (DimensionTerrainContext terrainContext : stackContext.getLayersBottomToTop()) {
|
||||
if (terrainContext.isSelfReferencing()) {
|
||||
continue;
|
||||
}
|
||||
IrisDimension dimension = terrainContext.getDimension();
|
||||
addOwnedBiomes(
|
||||
biomes,
|
||||
dimension,
|
||||
dimension.getReachableBiomes(terrainContext)
|
||||
);
|
||||
}
|
||||
return biomes;
|
||||
}
|
||||
|
||||
private static List<OwnedBiome> getHostOwnedBiomes(Engine engine) {
|
||||
IrisDimension hostDimension = engine.getDimension();
|
||||
ArrayList<OwnedBiome> biomes = new ArrayList<>();
|
||||
addOwnedBiomes(biomes, hostDimension, hostDimension.getReachableBiomes(engine));
|
||||
return biomes;
|
||||
}
|
||||
|
||||
private static void addOwnedBiomes(
|
||||
List<OwnedBiome> target,
|
||||
IrisDimension dimension,
|
||||
Iterable<IrisBiome> biomes
|
||||
) {
|
||||
for (IrisBiome biome : biomes) {
|
||||
if (biome != null) {
|
||||
target.add(new OwnedBiome(dimension, biome));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static DimensionStackLayout.Layer resolveDimensionStackLayer(
|
||||
Engine engine,
|
||||
int blockX,
|
||||
int internalY,
|
||||
int blockZ
|
||||
) {
|
||||
DimensionStackContext stackContext = engine.getDimensionStackContext();
|
||||
return stackContext == null ? null : stackContext.getLayerAt(blockX, internalY, blockZ);
|
||||
}
|
||||
|
||||
private static IrisBiome resolveSurfaceStructureBiome(Engine engine, int blockX, int blockZ) {
|
||||
return engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
||||
}
|
||||
|
||||
private static Holder<Biome> resolveBiomeHolder(Registry<Biome> registry, String biomeKey) {
|
||||
if (registry == null || biomeKey == null || biomeKey.isBlank()) {
|
||||
return null;
|
||||
@@ -809,18 +1143,31 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
private static final class BiomeResolution {
|
||||
private final IrisBiome irisBiome;
|
||||
private final boolean underground;
|
||||
private final IrisDimension dimension;
|
||||
private final int blockX;
|
||||
private final int blockY;
|
||||
private final int blockZ;
|
||||
private final RNG rng;
|
||||
|
||||
private BiomeResolution(IrisBiome irisBiome, boolean underground, int blockX, int blockY, int blockZ, RNG rng) {
|
||||
private BiomeResolution(
|
||||
IrisBiome irisBiome,
|
||||
boolean underground,
|
||||
IrisDimension dimension,
|
||||
int blockX,
|
||||
int blockY,
|
||||
int blockZ,
|
||||
RNG rng
|
||||
) {
|
||||
this.irisBiome = irisBiome;
|
||||
this.underground = underground;
|
||||
this.dimension = dimension;
|
||||
this.blockX = blockX;
|
||||
this.blockY = blockY;
|
||||
this.blockZ = blockZ;
|
||||
this.rng = rng;
|
||||
}
|
||||
}
|
||||
|
||||
private record OwnedBiome(IrisDimension dimension, IrisBiome biome) {
|
||||
}
|
||||
}
|
||||
+76
-14
@@ -1,5 +1,7 @@
|
||||
package art.arcane.iris.core.nms.v26_2_R1;
|
||||
|
||||
import art.arcane.iris.engine.DimensionStackContext;
|
||||
import art.arcane.iris.engine.DimensionStackLayout;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.NativeFeatureGenerationPolicy;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
@@ -40,6 +42,9 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.IntBinaryOperator;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Bukkit twin of the modded imported-feature stage. Same control, same semantics, same seeds: with
|
||||
@@ -198,8 +203,7 @@ final class ImportedFeatureStage {
|
||||
*/
|
||||
private Set<String> visibleBiomeKeys() {
|
||||
Set<String> keys = new LinkedHashSet<>();
|
||||
String namespace = engine.getDimension().getLoadKey().toLowerCase(Locale.ROOT);
|
||||
for (IrisBiome irisBiome : engine.getAllBiomes()) {
|
||||
for (IrisBiome irisBiome : engine.getDimension().getReachableBiomes(engine)) {
|
||||
addKey(keys, irisBiome.getStructureDerivativeKey());
|
||||
addKey(keys, irisBiome.getDerivativeKey());
|
||||
addKey(keys, irisBiome.getVanillaDerivativeKey());
|
||||
@@ -213,7 +217,7 @@ final class ImportedFeatureStage {
|
||||
continue;
|
||||
}
|
||||
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
|
||||
addKey(keys, namespace + ":" + customBiome.getId());
|
||||
addKey(keys, engine.getDimension().getCustomBiomeKey(customBiome.getId()));
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
@@ -226,8 +230,7 @@ final class ImportedFeatureStage {
|
||||
private Map<String, Holder<Biome>> customBiomeDerivatives(Registry<Biome> registry,
|
||||
Map<String, Holder<Biome>> byKey) {
|
||||
Map<String, Holder<Biome>> derivatives = new HashMap<>();
|
||||
String namespace = engine.getDimension().getLoadKey().toLowerCase(Locale.ROOT);
|
||||
for (IrisBiome irisBiome : engine.getAllBiomes()) {
|
||||
for (IrisBiome irisBiome : engine.getDimension().getReachableBiomes(engine)) {
|
||||
if (!irisBiome.isCustom()) {
|
||||
continue;
|
||||
}
|
||||
@@ -246,7 +249,7 @@ final class ImportedFeatureStage {
|
||||
continue;
|
||||
}
|
||||
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
|
||||
derivatives.put(namespace + ":" + customBiome.getId().toLowerCase(Locale.ROOT), derivative);
|
||||
derivatives.put(engine.getDimension().getCustomBiomeKey(customBiome.getId()), derivative);
|
||||
}
|
||||
}
|
||||
return derivatives;
|
||||
@@ -288,14 +291,24 @@ final class ImportedFeatureStage {
|
||||
List<FeatureSorter.StepFeatureData> steps = table.steps();
|
||||
WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
|
||||
long decorationSeed = random.setDecorationSeed(level.getSeed(), origin.getX(), origin.getZ());
|
||||
Set<Holder<Biome>> chunkBiomes = chunkBiomes(level, sectionPos, table);
|
||||
IrisStaticObjectLayer staticObjects = engine.getDimension().getStaticObjectLayer(engine.getData());
|
||||
int staticMinY = engine.getMinHeight();
|
||||
WorldGenLevel placementLevel = staticObjects.isEmpty() ? level : NativeStructureWorldgenAccess.create(
|
||||
DimensionStackContext stackContext = engine.getDimensionStackContext();
|
||||
Set<Holder<Biome>> chunkBiomes = chunkBiomes(level, sectionPos, table, stackContext);
|
||||
IntBinaryOperator surfaceFirstFreeY = stackContext == null
|
||||
? (x, z) -> level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z)
|
||||
: (x, z) -> Engine.hostHeight(engine, x, z, false) + staticMinY + 1;
|
||||
IntBinaryOperator floorFirstFreeY = stackContext == null
|
||||
? (x, z) -> level.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z)
|
||||
: (x, z) -> Engine.hostHeight(engine, x, z, true) + staticMinY + 1;
|
||||
WorldGenLevel placementLevel = staticObjects.isEmpty() && stackContext == null
|
||||
? level
|
||||
: NativeStructureWorldgenAccess.create(
|
||||
level, centerPos,
|
||||
(x, z) -> level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z),
|
||||
(x, z) -> level.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z),
|
||||
position -> staticObjects.contains(position.getX(), position.getY() - staticMinY, position.getZ()));
|
||||
surfaceFirstFreeY,
|
||||
floorFirstFreeY,
|
||||
stackContext != null,
|
||||
importedFeatureProtection(staticObjects, stackContext, staticMinY));
|
||||
|
||||
try {
|
||||
for (int stepIndex = 0; stepIndex < steps.size(); stepIndex++) {
|
||||
@@ -313,6 +326,30 @@ final class ImportedFeatureStage {
|
||||
}
|
||||
}
|
||||
|
||||
private static Predicate<BlockPos> importedFeatureProtection(
|
||||
IrisStaticObjectLayer staticObjects,
|
||||
DimensionStackContext stackContext,
|
||||
int minimumY
|
||||
) {
|
||||
Map<Long, DimensionStackLayout> layouts = new ConcurrentHashMap<>();
|
||||
return position -> {
|
||||
if (!staticObjects.isEmpty() && staticObjects.contains(
|
||||
position.getX(), position.getY() - minimumY, position.getZ())) {
|
||||
return true;
|
||||
}
|
||||
if (stackContext == null) {
|
||||
return false;
|
||||
}
|
||||
long columnKey = ((long) position.getX() << 32)
|
||||
^ (position.getZ() & 0xFFFFFFFFL);
|
||||
DimensionStackLayout layout = layouts.computeIfAbsent(
|
||||
columnKey,
|
||||
ignored -> stackContext.sample(position.getX(), position.getZ())
|
||||
);
|
||||
return layout.isHostFeatureProtectedY(position.getY() - minimumY);
|
||||
};
|
||||
}
|
||||
|
||||
private void placeStep(WorldGenLevel level, FeatureTable table, FeatureSorter.StepFeatureData stepData,
|
||||
Registry<PlacedFeature> featureRegistry, Set<Holder<Biome>> chunkBiomes,
|
||||
ChunkGenerator owner, WorldgenRandom random, long decorationSeed,
|
||||
@@ -353,12 +390,37 @@ final class ImportedFeatureStage {
|
||||
return id == null ? feature.toString() : id.toString();
|
||||
}
|
||||
|
||||
private Set<Holder<Biome>> chunkBiomes(WorldGenLevel level, SectionPos sectionPos, FeatureTable table) {
|
||||
private Set<Holder<Biome>> chunkBiomes(
|
||||
WorldGenLevel level,
|
||||
SectionPos sectionPos,
|
||||
FeatureTable table,
|
||||
DimensionStackContext stackContext
|
||||
) {
|
||||
List<Holder<Biome>> collected = new ArrayList<>();
|
||||
ChunkPos.rangeClosed(sectionPos.chunk(), 1).forEach((ChunkPos chunkPos) -> {
|
||||
ChunkAccess neighbour = level.getChunk(chunkPos.x(), chunkPos.z());
|
||||
for (LevelChunkSection section : neighbour.getSections()) {
|
||||
section.getBiomes().getAll(collected::add);
|
||||
LevelChunkSection[] sections = neighbour.getSections();
|
||||
for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {
|
||||
LevelChunkSection section = sections[sectionIndex];
|
||||
if (stackContext == null) {
|
||||
section.getBiomes().getAll(collected::add);
|
||||
continue;
|
||||
}
|
||||
int sectionMinimumY = neighbour.getSectionYFromSectionIndex(sectionIndex) << 4;
|
||||
for (int quartY = 0; quartY < 4; quartY++) {
|
||||
int internalY = sectionMinimumY + (quartY << 2) - level.getMinY();
|
||||
for (int quartX = 0; quartX < 4; quartX++) {
|
||||
int blockX = chunkPos.getMinBlockX() + (quartX << 2);
|
||||
for (int quartZ = 0; quartZ < 4; quartZ++) {
|
||||
int blockZ = chunkPos.getMinBlockZ() + (quartZ << 2);
|
||||
DimensionStackLayout.Layer layer = stackContext.getLayerAt(
|
||||
blockX, internalY, blockZ);
|
||||
if (layer.terrainContext().isSelfReferencing()) {
|
||||
collected.add(section.getBiomes().get(quartX, quartY, quartZ));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Set<Holder<Biome>> present = new LinkedHashSet<>();
|
||||
|
||||
+76
-14
@@ -1,5 +1,7 @@
|
||||
package art.arcane.iris.core.nms.v26_2_R1;
|
||||
|
||||
import art.arcane.iris.engine.DimensionStackContext;
|
||||
import art.arcane.iris.engine.DimensionStackLayout;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.GenerationSessionException;
|
||||
import art.arcane.iris.engine.framework.GenerationSessionLease;
|
||||
@@ -115,6 +117,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.IntBinaryOperator;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
|
||||
@@ -429,7 +432,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
undergroundStep,
|
||||
decision.preserveSourceY(),
|
||||
decision.yBand(),
|
||||
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
|
||||
(x, z) -> Engine.hostHeight(engine, x, z, true) + engine.getMinHeight());
|
||||
StructureStart wrapped = NativeStructureReferenceEnvelope.wrapForPublication(
|
||||
start, structure, start.getReferences(),
|
||||
NativeStructureTerrainIntegrator.resolveNativeTerrain(start, decision.terrain()),
|
||||
@@ -726,6 +729,14 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
return (x, z) -> engine.getHeight(x, z, true) + runtimeMinY + 1;
|
||||
}
|
||||
|
||||
private IntBinaryOperator hostWorldgenSurfaceHeight() {
|
||||
return (x, z) -> Engine.hostHeight(engine, x, z, false) + runtimeMinY + 1;
|
||||
}
|
||||
|
||||
private IntBinaryOperator hostWorldgenFloorHeight() {
|
||||
return (x, z) -> Engine.hostHeight(engine, x, z, true) + runtimeMinY + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WeightedList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
|
||||
Holder<Biome> vanillaSpawnBiome = customBiomeSource.getVanillaSpawnBiome(holder);
|
||||
@@ -868,17 +879,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
}
|
||||
try {
|
||||
WorldgenTerrainHeightmaps.primeStructurePlacement(
|
||||
world, chunkPos, heightmapStarts, worldgenSurfaceHeight(), worldgenFloorHeight());
|
||||
world, chunkPos, heightmapStarts,
|
||||
hostWorldgenSurfaceHeight(), hostWorldgenFloorHeight());
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"heightmap priming", nativeStructureBatchContext(placementGroups),
|
||||
chunkPos.x(), chunkPos.z(), error);
|
||||
}
|
||||
IrisStaticObjectLayer staticObjects = engine.getDimension().getStaticObjectLayer(engine.getData());
|
||||
int staticMinY = engine.getMinHeight();
|
||||
WorldGenLevel boundedWorld = staticObjects.isEmpty() ? world : NativeStructureWorldgenAccess.create(
|
||||
world, chunkPos, worldgenSurfaceHeight(), worldgenFloorHeight(),
|
||||
position -> staticObjects.contains(position.getX(), position.getY() - staticMinY, position.getZ()));
|
||||
Predicate<BlockPos> protectedPosition = nativeStructureProtection(staticObjects);
|
||||
WorldGenLevel boundedWorld = staticObjects.isEmpty()
|
||||
&& engine.getDimensionStackContext() == null
|
||||
? world
|
||||
: NativeStructureWorldgenAccess.create(
|
||||
world, chunkPos, hostWorldgenSurfaceHeight(), hostWorldgenFloorHeight(),
|
||||
engine.getDimensionStackContext() != null,
|
||||
protectedPosition);
|
||||
try {
|
||||
NativeStructureVegetationClearer.clearIntersectingVegetation(
|
||||
boundedWorld, chunk, area, vegetationTargets);
|
||||
@@ -891,7 +907,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
try {
|
||||
vacuumFoundationPlan = NativeStructureSurfaceFitter.prepareSurfaceStructures(
|
||||
boundedWorld, area, terrainTargets,
|
||||
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
|
||||
(x, z) -> Engine.hostHeight(engine, x, z, true) + engine.getMinHeight());
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"terrain integration", nativeStructureBatchContext(placementGroups),
|
||||
@@ -945,17 +961,45 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
BoundingBox area, ChunkPos chunkPos, String structureId, StructureStart start,
|
||||
IrisNativeStructureDecision decision) {
|
||||
WorldGenLevel boundedWorld = world instanceof NativeStructureWorldgenAccess ? world : NativeStructureWorldgenAccess.create(
|
||||
world, chunkPos, worldgenSurfaceHeight(), worldgenFloorHeight(), position -> false);
|
||||
world,
|
||||
chunkPos,
|
||||
hostWorldgenSurfaceHeight(),
|
||||
hostWorldgenFloorHeight(),
|
||||
engine.getDimensionStackContext() != null,
|
||||
nativeStructureProtection(
|
||||
engine.getDimension().getStaticObjectLayer(engine.getData())));
|
||||
world.setCurrentlyGenerating(() -> "Iris native structure " + structureId);
|
||||
try {
|
||||
NativeStructurePostProcessor.place(boundedWorld, structureManager, this, random, area, chunkPos,
|
||||
structureId, start, decision, this::resolvePaletteBlock,
|
||||
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
|
||||
(x, z) -> Engine.hostHeight(engine, x, z, true) + engine.getMinHeight());
|
||||
} finally {
|
||||
world.setCurrentlyGenerating(null);
|
||||
}
|
||||
}
|
||||
|
||||
private Predicate<BlockPos> nativeStructureProtection(IrisStaticObjectLayer staticObjects) {
|
||||
DimensionStackContext stackContext = engine.getDimensionStackContext();
|
||||
int minimumY = engine.getMinHeight();
|
||||
Map<Long, DimensionStackLayout> layouts = new ConcurrentHashMap<>();
|
||||
return position -> {
|
||||
if (!staticObjects.isEmpty() && staticObjects.contains(
|
||||
position.getX(), position.getY() - minimumY, position.getZ())) {
|
||||
return true;
|
||||
}
|
||||
if (stackContext == null) {
|
||||
return false;
|
||||
}
|
||||
long columnKey = ((long) position.getX() << 32)
|
||||
^ (position.getZ() & 0xFFFFFFFFL);
|
||||
DimensionStackLayout layout = layouts.computeIfAbsent(
|
||||
columnKey,
|
||||
ignored -> stackContext.sample(position.getX(), position.getZ())
|
||||
);
|
||||
return layout.isHostFeatureProtectedY(position.getY() - minimumY);
|
||||
};
|
||||
}
|
||||
|
||||
private List<List<Structure>> structuresByStep(Registry<Structure> registry) {
|
||||
StructureStepCache cached = structureStepCache;
|
||||
if (cached != null && cached.registry() == registry) {
|
||||
@@ -1041,7 +1085,17 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
@Override
|
||||
public void spawnOriginalMobs(WorldGenRegion region) {
|
||||
ChunkPos center = region.getCenter();
|
||||
Holder<Biome> visibleBiome = region.getBiome(center.getWorldPosition().atY(region.getMaxY()));
|
||||
Holder<Biome> visibleBiome;
|
||||
if (engine.getDimensionStackContext() == null) {
|
||||
visibleBiome = region.getBiome(center.getWorldPosition().atY(region.getMaxY()));
|
||||
} else {
|
||||
visibleBiome = customBiomeSource.getVisibleSurfaceBiome(
|
||||
center.getMinBlockX() + 8,
|
||||
center.getMinBlockZ() + 8);
|
||||
if (visibleBiome == null) {
|
||||
visibleBiome = region.getBiome(center.getWorldPosition().atY(region.getMaxY()));
|
||||
}
|
||||
}
|
||||
Holder<Biome> vanillaBiome = customBiomeSource.getVanillaSpawnBiome(visibleBiome);
|
||||
WorldgenRandom random = new WorldgenRandom(new LegacyRandomSource(RandomSupport.generateUniqueSeed()));
|
||||
random.setDecorationSeed(region.getSeed(), center.getMinBlockX(), center.getMinBlockZ());
|
||||
@@ -1081,9 +1135,12 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
|
||||
try (GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_nms_base_height");
|
||||
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
|
||||
boolean ignoreFluid = !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState());
|
||||
int height = engine.getDimensionStackContext() == null
|
||||
? engine.getHeight(i, j, ignoreFluid)
|
||||
: Engine.hostHeight(engine, i, j, ignoreFluid);
|
||||
return levelheightaccessor.getMinY()
|
||||
+ engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState()))
|
||||
+ 1;
|
||||
+ height + 1;
|
||||
} catch (GenerationSessionException e) {
|
||||
throw new IllegalStateException("Iris base height query could not acquire its engine runtime.", e);
|
||||
}
|
||||
@@ -1093,8 +1150,13 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
|
||||
try (GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_nms_base_column");
|
||||
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
|
||||
int block = engine.getHeight(i, j, true);
|
||||
int water = engine.getHeight(i, j, false);
|
||||
boolean dimensionStack = engine.getDimensionStackContext() != null;
|
||||
int block = dimensionStack
|
||||
? Engine.hostHeight(engine, i, j, true)
|
||||
: engine.getHeight(i, j, true);
|
||||
int water = dimensionStack
|
||||
? Engine.hostHeight(engine, i, j, false)
|
||||
: engine.getHeight(i, j, false);
|
||||
BlockState[] column = new BlockState[levelheightaccessor.getHeight()];
|
||||
for (int k = 0; k < column.length; k++) {
|
||||
if (k <= block) {
|
||||
|
||||
+5
@@ -824,6 +824,11 @@ public class NMSBinding implements INMSBinding {
|
||||
return NativeStructureVolumeIndex.volumes(engine, minX, minZ, maxX, maxZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateNativeStructureVolumeIndex(Engine engine) {
|
||||
NativeStructureVolumeIndex.invalidate(engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBiomeId(Biome biome) {
|
||||
for (World i : Bukkit.getWorlds()) {
|
||||
|
||||
+6
-2
@@ -64,6 +64,7 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
private final ChunkPos generationCenter;
|
||||
private final IntBinaryOperator surfaceFirstFreeY;
|
||||
private final IntBinaryOperator floorFirstFreeY;
|
||||
private final boolean virtualizeHeights;
|
||||
private final Predicate<BlockPos> protectedPosition;
|
||||
private final Holder<Biome> fallbackBiome;
|
||||
private final BiomeManager biomeManager;
|
||||
@@ -81,6 +82,7 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
boundary.surfaceFirstFreeY(), "Native structure world access requires a surface resolver");
|
||||
this.floorFirstFreeY = Objects.requireNonNull(
|
||||
boundary.floorFirstFreeY(), "Native structure world access requires an ocean-floor resolver");
|
||||
this.virtualizeHeights = boundary.virtualizeHeights();
|
||||
this.protectedPosition = Objects.requireNonNull(
|
||||
boundary.protectedPosition(), "World access requires a protected-position predicate");
|
||||
BlockPos biomeSample = generationCenter.getMiddleBlockPosition(delegate.getSeaLevel());
|
||||
@@ -103,9 +105,10 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
static NativeStructureWorldgenAccess create(WorldGenLevel delegate, ChunkPos generationCenter,
|
||||
IntBinaryOperator surfaceFirstFreeY,
|
||||
IntBinaryOperator floorFirstFreeY,
|
||||
boolean virtualizeHeights,
|
||||
Predicate<BlockPos> protectedPosition) {
|
||||
return new NativeStructureWorldgenAccess(delegate, new Boundary(
|
||||
generationCenter, surfaceFirstFreeY, floorFirstFreeY, protectedPosition));
|
||||
generationCenter, surfaceFirstFreeY, floorFirstFreeY, virtualizeHeights, protectedPosition));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -250,7 +253,7 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
|
||||
@Override
|
||||
public int getHeight(Heightmap.Types type, int x, int z) {
|
||||
if (isInsideGenerationRegion(x >> 4, z >> 4)) {
|
||||
if (!virtualizeHeights && isInsideGenerationRegion(x >> 4, z >> 4)) {
|
||||
return delegate.getHeight(type, x, z);
|
||||
}
|
||||
return height(type, x, z);
|
||||
@@ -549,6 +552,7 @@ final class NativeStructureWorldgenAccess implements WorldGenLevel {
|
||||
private record Boundary(ChunkPos generationCenter,
|
||||
IntBinaryOperator surfaceFirstFreeY,
|
||||
IntBinaryOperator floorFirstFreeY,
|
||||
boolean virtualizeHeights,
|
||||
Predicate<BlockPos> protectedPosition) {
|
||||
}
|
||||
|
||||
|
||||
+176
-1
@@ -1,15 +1,29 @@
|
||||
package art.arcane.iris.core.nms.v26_2_R1;
|
||||
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class CustomBiomeSourceStructureContractTest {
|
||||
@BeforeClass
|
||||
public static void bootstrapMinecraftRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nativeStructuresUseTerrainSafeDerivativeAtEveryBiomeBoundary() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
|
||||
@@ -69,14 +83,53 @@ public class CustomBiomeSourceStructureContractTest {
|
||||
String realResolution = source.substring(realResolutionStart, naturalResolutionStart);
|
||||
String naturalResolution = source.substring(naturalResolutionStart, resolutionEnd);
|
||||
|
||||
assertTrue(realResolution.contains("engine.getComplex().getTrueBiomeStream().get(blockX, blockZ)"));
|
||||
assertTrue(realResolution.contains("resolveSurfaceStructureBiome(engine, blockX, blockZ)"));
|
||||
assertFalse(realResolution.contains("getNaturalTrueBiomeStream()"));
|
||||
assertTrue(naturalResolution.contains(
|
||||
"engine.getComplex().getNaturalTrueBiomeStream().get(blockX, blockZ)"));
|
||||
assertFalse(naturalResolution.contains("getTrueBiomeStream()"));
|
||||
int helperStart = source.indexOf("private static IrisBiome resolveSurfaceStructureBiome(");
|
||||
int helperEnd = source.indexOf("private static Holder<Biome> resolveBiomeHolder(", helperStart);
|
||||
String helper = source.substring(helperStart, helperEnd);
|
||||
assertTrue(helper.contains("engine.getComplex().getTrueBiomeStream().get(blockX, blockZ)"));
|
||||
assertFalse(helper.contains("getDimensionStackContext()"));
|
||||
assertFalse(helper.contains("getSurfaceBiome("));
|
||||
assertFalse(source.contains("studioBootstrapSurfaceStructureBiomeCache"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stackedCustomBiomesUseTheirOwningDimensionNamespace() {
|
||||
IrisDimension host = new IrisDimension();
|
||||
host.setLoadKey("Host");
|
||||
IrisDimension upper = new IrisDimension();
|
||||
upper.setLoadKey("Layers/Upper");
|
||||
|
||||
assertEquals("layers:upper/aurora", CustomBiomeSource.customBiomeKey(upper, "Aurora"));
|
||||
assertNotEquals(
|
||||
CustomBiomeSource.customBiomeKey(host, "Aurora"),
|
||||
CustomBiomeSource.customBiomeKey(upper, "Aurora")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void visibleBiomeResolutionSelectsStackOwnershipBeforeHostCaves() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
|
||||
int resolutionStart = source.indexOf("private BiomeResolution resolveBiomeResolution(");
|
||||
int resolutionEnd = source.indexOf("private BiomeResolution createBiomeResolution(", resolutionStart);
|
||||
|
||||
assertTrue(resolutionStart >= 0);
|
||||
assertTrue(resolutionEnd > resolutionStart);
|
||||
String resolution = source.substring(resolutionStart, resolutionEnd);
|
||||
|
||||
assertTrue(resolution.contains("resolveDimensionStackLayer("));
|
||||
assertTrue(resolution.contains("!stackLayer.terrainContext().isSelfReferencing()"));
|
||||
assertTrue(resolution.indexOf("resolveDimensionStackLayer(")
|
||||
< resolution.indexOf("boolean deepUnderground"));
|
||||
assertTrue(resolution.contains("includeDimensionStack"));
|
||||
assertTrue(source.contains("resolveBiomeResolution(x, y, z, null, false)"));
|
||||
assertTrue(source.contains("resolution.dimension, customBiome.getId()"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void visibleBiomeBatchReusesTheCallersGenerationLease() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
|
||||
@@ -94,4 +147,126 @@ public class CustomBiomeSourceStructureContractTest {
|
||||
< lookup.indexOf("getVisibleNoiseBiomeWithActiveGenerationLease(x, y, z, sampler)"));
|
||||
assertFalse(source.contains("studioBootstrapNoiseBiomeCache"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stackBiomesAreVisibleButExcludedFromStructureReachability() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
|
||||
|
||||
assertTrue(source.contains("return possibleBiomes(true).stream()"));
|
||||
assertTrue(source.contains("public Set<Holder<Biome>> possibleBiomes()"));
|
||||
assertTrue(source.contains("irisBiome.getDerivativeKey()"));
|
||||
assertTrue(source.contains("irisBiome.getBiomeScatter()"));
|
||||
assertTrue(source.contains("irisBiome.getBiomeSkyScatter()"));
|
||||
assertTrue(source.contains("return possibleBiomes(false)"));
|
||||
assertTrue(source.contains("for (OwnedBiome ownedBiome : getHostOwnedBiomes(engine))"));
|
||||
assertTrue(source.contains("for (OwnedBiome ownedBiome : getOwnedBiomes(engine))"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void biomeLocatorSearchesTheVisibleStack() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
|
||||
int start = source.indexOf("public Pair<BlockPos, Holder<Biome>> findClosestBiome3d(");
|
||||
int end = source.indexOf("static int horizontalBiomeSearchQuartStep", start);
|
||||
String method = source.substring(start, end);
|
||||
|
||||
assertTrue(method.contains("bukkit_locate_visible_biome"));
|
||||
assertTrue(method.contains("return super.findClosestBiome3d("));
|
||||
assertTrue(method.contains("getVisibleBiomes("));
|
||||
assertTrue(method.contains("QuartPos.toBlock(quartX), QuartPos.toBlock(quartZ)"));
|
||||
assertTrue(method.contains("resolveVisibleBiomeHolder("));
|
||||
assertFalse(method.contains("getStructureNoiseBiomeWithActiveGenerationLease("));
|
||||
assertTrue(method.indexOf("tryAcquireGenerationLease(\"bukkit_locate_visible_biome\")")
|
||||
< method.indexOf("engine.getDimensionStackContext()"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void surfaceSpawnBiomeUsesTheVisibleQuartCell() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
|
||||
int start = source.indexOf("Holder<Biome> getVisibleSurfaceBiome(");
|
||||
int end = source.indexOf("private RegistryAccess registry()", start);
|
||||
String method = source.substring(start, end);
|
||||
|
||||
assertTrue(method.contains("QuartPos.fromBlock(blockX)"));
|
||||
assertTrue(method.contains("QuartPos.toBlock(quartX)"));
|
||||
assertTrue(method.contains("QuartPos.fromBlock(internalY + engine.getWorld().minHeight())"));
|
||||
assertTrue(method.contains("stackContext.getLayout(sampleX, sampleZ).surfaceLayer()"));
|
||||
assertTrue(method.contains("getVisibleNoiseBiomeWithActiveGenerationLease("));
|
||||
assertTrue(method.contains("quartX, quartY, quartZ, null, null, cacheable"));
|
||||
assertTrue(method.indexOf("boolean cacheable = isBiomeCacheable(engine, sampleX, sampleZ)")
|
||||
< method.indexOf("Engine.hostHeight(engine, sampleX, sampleZ, true)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void temporaryNaturalBiomeAnswersAreNotMemoized() throws IOException {
|
||||
Engine temporary = engineAnsweringNaturalFallback(true, 12, -8);
|
||||
Engine stable = engineAnsweringNaturalFallback(false, 12, -8);
|
||||
assertFalse(CustomBiomeSource.isBiomeCacheable(temporary, 12, -8));
|
||||
assertTrue(CustomBiomeSource.isBiomeCacheable(stable, 12, -8));
|
||||
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
|
||||
int structureStart = source.indexOf(
|
||||
"private Holder<Biome> getStructureNoiseBiomeWithActiveGenerationLease(");
|
||||
int structureEnd = source.indexOf("@Override\n public Pair<BlockPos, Holder<Biome>> findBiomeHorizontal(",
|
||||
structureStart);
|
||||
assertCacheGuarded(source.substring(structureStart, structureEnd), "structureBiomeCache");
|
||||
|
||||
int firstVisible = source.indexOf(
|
||||
"Holder<Biome> getVisibleNoiseBiomeWithActiveGenerationLease(");
|
||||
int visibleStart = source.indexOf(
|
||||
"Holder<Biome> getVisibleNoiseBiomeWithActiveGenerationLease(", firstVisible + 1);
|
||||
int visibleEnd = source.indexOf("private boolean isBiomeCacheable(", visibleStart);
|
||||
assertCacheGuarded(source.substring(visibleStart, visibleEnd), "noiseBiomeCache");
|
||||
|
||||
String predicate = source.substring(
|
||||
visibleEnd,
|
||||
source.indexOf("private GenerationSessionLease tryAcquireGenerationLease(", visibleEnd)
|
||||
);
|
||||
assertTrue(predicate.contains("QuartPos.toBlock(quartX)"));
|
||||
assertTrue(predicate.contains("QuartPos.toBlock(quartZ)"));
|
||||
assertTrue(predicate.contains("return !engine.answersFromNaturalTerrain(blockX, blockZ)"));
|
||||
assertFalse(predicate.contains("getDimensionStackContext()"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void importedFeaturesCollectOnlyHostOwnedStackCells() throws IOException {
|
||||
Path biomeSource = Path.of(System.getProperty("iris.customBiomeSource"));
|
||||
String source = Files.readString(biomeSource.getParent().resolve("ImportedFeatureStage.java"));
|
||||
|
||||
assertTrue(source.contains("stackContext.getLayerAt("));
|
||||
assertTrue(source.contains("layer.terrainContext().isSelfReferencing()"));
|
||||
assertTrue(source.contains("section.getBiomes().get(quartX, quartY, quartZ)"));
|
||||
}
|
||||
|
||||
private static void assertCacheGuarded(String method, String cacheName) {
|
||||
int predicate = method.indexOf("boolean cacheable = isBiomeCacheable(");
|
||||
int read = method.indexOf(cacheName + ".get(", predicate);
|
||||
int readGuard = method.lastIndexOf("if (cacheable)", read);
|
||||
int bypass = method.indexOf("if (!cacheable)", read);
|
||||
int write = method.indexOf(cacheName + ".putIfAbsent(", bypass);
|
||||
|
||||
assertTrue(predicate >= 0);
|
||||
assertTrue(read > predicate);
|
||||
assertTrue(readGuard >= predicate);
|
||||
assertTrue(bypass > read);
|
||||
assertTrue(write > bypass);
|
||||
}
|
||||
|
||||
private static Engine engineAnsweringNaturalFallback(
|
||||
boolean naturalFallback,
|
||||
int expectedX,
|
||||
int expectedZ
|
||||
) {
|
||||
return (Engine) Proxy.newProxyInstance(
|
||||
Engine.class.getClassLoader(),
|
||||
new Class<?>[]{Engine.class},
|
||||
(proxy, method, arguments) -> {
|
||||
if (!method.getName().equals("answersFromNaturalTerrain")) {
|
||||
throw new UnsupportedOperationException(method.getName());
|
||||
}
|
||||
assertEquals(Integer.valueOf(expectedX), arguments[0]);
|
||||
assertEquals(Integer.valueOf(expectedZ), arguments[1]);
|
||||
return naturalFallback;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -78,6 +78,8 @@ public class IrisChunkGeneratorFailureContractTest {
|
||||
< placement.indexOf("for (NativePlacementGroup group"));
|
||||
assertTrue(placement.indexOf("repairVacuumFoundations")
|
||||
> placement.indexOf("for (NativePlacementGroup group"));
|
||||
assertTrue(placement.contains("nativeStructureProtection(staticObjects)"));
|
||||
assertTrue(placement.contains("Engine.hostHeight(engine, x, z, true)"));
|
||||
assertFalse(placement.contains("IrisLogging.reportError"));
|
||||
}
|
||||
|
||||
@@ -542,8 +544,8 @@ public class IrisChunkGeneratorFailureContractTest {
|
||||
String spawn = source.substring(spawnStart, spawnEnd);
|
||||
|
||||
assertTrue(spawn.contains("customBiomeSource.getVanillaSpawnBiome(visibleBiome)"));
|
||||
assertTrue(spawn.contains("customBiomeSource.getVisibleSurfaceBiome("));
|
||||
assertTrue(spawn.contains("NaturalSpawner.spawnMobsForChunkGeneration("));
|
||||
assertTrue(spawn.contains("region.getBiome(center.getWorldPosition().atY(region.getMaxY()))"));
|
||||
assertTrue(spawn.contains("new LegacyRandomSource(RandomSupport.generateUniqueSeed())"));
|
||||
assertTrue(spawn.contains("random.setDecorationSeed(region.getSeed()"));
|
||||
assertFalse(spawn.contains("delegate.spawnOriginalMobs"));
|
||||
|
||||
+24
-2
@@ -135,6 +135,24 @@ public class NativeStructureWorldgenAccessTest {
|
||||
assertEquals(1, recording.blockTicks.count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void virtualizedHeightsHideInRegionTerrainFromTheDelegate() {
|
||||
RecordingDelegate recording = new RecordingDelegate();
|
||||
ChunkPos generationCenter = generationCenter();
|
||||
NativeStructureWorldgenAccess access = NativeStructureWorldgenAccess.create(
|
||||
recording.world(), generationCenter,
|
||||
(x, z) -> SURFACE_FIRST_FREE_Y,
|
||||
(x, z) -> FLOOR_FIRST_FREE_Y,
|
||||
true,
|
||||
position -> false);
|
||||
int x = generationCenter.getMiddleBlockX();
|
||||
int z = generationCenter.getMiddleBlockZ();
|
||||
|
||||
assertEquals(SURFACE_FIRST_FREE_Y, access.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z));
|
||||
assertEquals(FLOOR_FIRST_FREE_Y, access.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z));
|
||||
assertEquals(0, recording.heightReads);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void protectedCellsRemainReadableButRejectWritesTilesAndTicks() {
|
||||
RecordingDelegate recording = new RecordingDelegate();
|
||||
@@ -142,7 +160,9 @@ public class NativeStructureWorldgenAccessTest {
|
||||
NativeStructureWorldgenAccess access = NativeStructureWorldgenAccess.create(
|
||||
recording.world(), generationCenter(),
|
||||
(x, z) -> SURFACE_FIRST_FREE_Y,
|
||||
(x, z) -> FLOOR_FIRST_FREE_Y, position::equals);
|
||||
(x, z) -> FLOOR_FIRST_FREE_Y,
|
||||
false,
|
||||
position::equals);
|
||||
|
||||
assertSame(Blocks.DIRT, access.getBlockState(position).getBlock());
|
||||
assertFalse(access.ensureCanWrite(position));
|
||||
@@ -211,7 +231,9 @@ public class NativeStructureWorldgenAccessTest {
|
||||
return NativeStructureWorldgenAccess.create(
|
||||
recording.world(), generationCenter(),
|
||||
(x, z) -> SURFACE_FIRST_FREE_Y,
|
||||
(x, z) -> FLOOR_FIRST_FREE_Y, position -> false);
|
||||
(x, z) -> FLOOR_FIRST_FREE_Y,
|
||||
false,
|
||||
position -> false);
|
||||
}
|
||||
|
||||
private static ChunkPos generationCenter() {
|
||||
|
||||
+12
@@ -332,6 +332,18 @@ public class NativeStructureFactoryTest {
|
||||
assertTrue(failureStart < source.indexOf("NativeStructureOwnershipStore.record("));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void manualNativeStructureGenerationUsesTheRootTerrainHeight() throws Exception {
|
||||
String source = Files.readString(
|
||||
Path.of(System.getProperty("iris.nativeStructureStartInjectorSource")),
|
||||
StandardCharsets.UTF_8);
|
||||
|
||||
assertTrue(source.contains(
|
||||
"Engine.hostHeight(context.engine(), x, z, true)"));
|
||||
assertFalse(source.contains(
|
||||
"context.engine().getHeight(x, z, true)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failedManualStartPublicationClearsRecordedOwnershipAndPreservesTheFailure() throws Exception {
|
||||
String source = Files.readString(
|
||||
|
||||
Reference in new issue
Block a user