This commit is contained in:
Brian Neumann-Fopiano committed 2026-07-16 19:38:53 -04:00
1 parent e8dd4e1127
commit 0193f75daa
21 files changed
+272 -180

No files matched your search

@@ -70,10 +70,10 @@ public class CustomBiomeSource extends BiomeSource {
LinkedHashSet<Holder<Biome>> biomes = new LinkedHashSet<>(); LinkedHashSet<Holder<Biome>> biomes = new LinkedHashSet<>();
for (IrisBiome i : engine.getAllBiomes()) { for (IrisBiome i : engine.getAllBiomes()) {
Holder<Biome> vanillaHolder = NMSBinding.biomeToBiomeBase(registry, i.getVanillaDerivative()); Holder<Biome> vanillaHolder = resolveBiomeHolder(registry, i.getStructureDerivativeKey());
if (vanillaHolder == null) { if (vanillaHolder == null) {
throw new IllegalStateException("Iris structure biome derivative '" throw new IllegalStateException("Iris structure biome derivative '"
+ i.getVanillaDerivativeKey() + "' is not registered for biome '" + i.getLoadKey() + "'"); + i.getStructureDerivativeKey() + "' is not registered for biome '" + i.getLoadKey() + "'");
} }
biomes.add(vanillaHolder); biomes.add(vanillaHolder);
@@ -312,10 +312,10 @@ public class CustomBiomeSource extends BiomeSource {
throw new IllegalStateException("Iris returned no surface structure biome at block " throw new IllegalStateException("Iris returned no surface structure biome at block "
+ blockX + "," + blockZ); + blockX + "," + blockZ);
} }
Holder<Biome> holder = NMSBinding.biomeToBiomeBase(biomeRegistry, irisBiome.getVanillaDerivative()); Holder<Biome> holder = resolveBiomeHolder(biomeRegistry, irisBiome.getStructureDerivativeKey());
if (holder == null) { if (holder == null) {
throw new IllegalStateException("Iris structure biome derivative '" throw new IllegalStateException("Iris structure biome derivative '"
+ irisBiome.getVanillaDerivativeKey() + "' is not registered at block " + irisBiome.getStructureDerivativeKey() + "' is not registered at block "
+ blockX + "," + blockZ); + blockX + "," + blockZ);
} }
return holder; return holder;
@@ -371,10 +371,11 @@ public class CustomBiomeSource extends BiomeSource {
+ x + "," + y + "," + z); + x + "," + y + "," + z);
} }
Holder<Biome> holder = NMSBinding.biomeToBiomeBase(biomeRegistry, resolution.irisBiome.getVanillaDerivative()); Holder<Biome> holder = resolveBiomeHolder(
biomeRegistry, resolution.irisBiome.getStructureDerivativeKey());
if (holder == null) { if (holder == null) {
throw new IllegalStateException("Iris structure biome derivative '" throw new IllegalStateException("Iris structure biome derivative '"
+ resolution.irisBiome.getVanillaDerivativeKey() + "' is not registered at block " + resolution.irisBiome.getStructureDerivativeKey() + "' is not registered at block "
+ resolution.blockX + "," + resolution.blockY + "," + resolution.blockZ); + resolution.blockX + "," + resolution.blockY + "," + resolution.blockZ);
} }
return holder; return holder;
@@ -505,6 +506,17 @@ public class CustomBiomeSource extends BiomeSource {
return optionalReferenceHolder.get(); return optionalReferenceHolder.get();
} }
private static Holder<Biome> resolveBiomeHolder(Registry<Biome> registry, String biomeKey) {
if (registry == null || biomeKey == null || biomeKey.isBlank()) {
return null;
}
Identifier identifier = Identifier.tryParse(biomeKey);
if (identifier == null) {
return null;
}
return registry.get(ResourceKey.create(Registries.BIOME, identifier)).orElse(null);
}
private static Holder<Biome> resolveFallbackBiome(Registry<Biome> registry, Registry<Biome> customRegistry) { private static Holder<Biome> resolveFallbackBiome(Registry<Biome> registry, Registry<Biome> customRegistry) {
Holder<Biome> plains = NMSBinding.biomeToBiomeBase(registry, org.bukkit.block.Biome.PLAINS); Holder<Biome> plains = NMSBinding.biomeToBiomeBase(registry, org.bukkit.block.Biome.PLAINS);
if (plains != null) { if (plains != null) {
@@ -3,7 +3,6 @@ package art.arcane.iris.core.nms.v26_2_R1;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.IrisStructureLocator; import art.arcane.iris.engine.framework.IrisStructureLocator;
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy; import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
import art.arcane.iris.core.structure.NativeStructureLocateCapability;
import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisNativeStructureDecision; import art.arcane.iris.engine.object.IrisNativeStructureDecision;
import art.arcane.iris.engine.object.IrisStructureStiltSettings; import art.arcane.iris.engine.object.IrisStructureStiltSettings;
@@ -174,7 +173,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
String key = id.toString(); String key = id.toString();
IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(engine, IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(engine,
key, NativeStructurePostProcessor.isUndergroundStep(holder.value().step())); key, NativeStructurePostProcessor.isUndergroundStep(holder.value().step()));
if (NativeStructureLocateCapability.isPaperUnavailable(key) || !decision.generate()) { if (!decision.generate()) {
continue; continue;
} }
candidates.add(new NativeLocateCandidate(holder, key)); candidates.add(new NativeLocateCandidate(holder, key));
@@ -0,0 +1,22 @@
package art.arcane.iris.core.nms.v26_2_R1;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class CustomBiomeSourceStructureContractTest {
@Test
public void nativeStructuresUseTerrainSafeDerivativeAtEveryBiomeBoundary() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
assertTrue(source.contains("resolveBiomeHolder(registry, i.getStructureDerivativeKey())"));
assertTrue(source.contains("resolveBiomeHolder(biomeRegistry, irisBiome.getStructureDerivativeKey())"));
assertTrue(source.contains("resolution.irisBiome.getStructureDerivativeKey()"));
assertFalse(source.contains("resolution.irisBiome.getVanillaDerivative()"));
}
}
@@ -17,7 +17,7 @@ import static org.junit.Assert.assertSame;
public class IrisChunkGeneratorMonumentLocateContractTest { public class IrisChunkGeneratorMonumentLocateContractTest {
@Test @Test
public void nativeLocateAllowsExplicitReplacementBeforeNativeCapabilityGate() throws IOException { public void nativeLocateAllowsMonumentsAfterPolicyAndReachabilityChecks() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource"))); String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
int findStart = source.indexOf("findNearestMapStructure(ServerLevel level"); int findStart = source.indexOf("findNearestMapStructure(ServerLevel level");
assertTrue(findStart >= 0); assertTrue(findStart >= 0);
@@ -42,9 +42,9 @@ public class IrisChunkGeneratorMonumentLocateContractTest {
int reachabilityStart = source.indexOf("private Set<String> reachableStructureKeys", filterStart); int reachabilityStart = source.indexOf("private Set<String> reachableStructureKeys", filterStart);
assertTrue(reachabilityStart > filterStart); assertTrue(reachabilityStart > filterStart);
String filterMethod = source.substring(filterStart, reachabilityStart); String filterMethod = source.substring(filterStart, reachabilityStart);
int monumentReject = filterMethod.indexOf("if (NativeStructureLocateCapability.isPaperUnavailable(key)"); int policyFilter = filterMethod.indexOf("if (!decision.generate())");
int rejectContinue = filterMethod.indexOf("continue;", monumentReject); int filterContinue = filterMethod.indexOf("continue;", policyFilter);
int emptyNativePartition = filterMethod.indexOf("if (candidates.isEmpty())", rejectContinue); int emptyNativePartition = filterMethod.indexOf("if (candidates.isEmpty())", filterContinue);
int reachabilityLookup = filterMethod.indexOf("reachableStructureKeys(level)", emptyNativePartition); int reachabilityLookup = filterMethod.indexOf("reachableStructureKeys(level)", emptyNativePartition);
assertTrue(policyResolution >= 0); assertTrue(policyResolution >= 0);
@@ -58,10 +58,11 @@ public class IrisChunkGeneratorMonumentLocateContractTest {
assertTrue(nativeFilter >= 0); assertTrue(nativeFilter >= 0);
assertTrue(delegateLocate > nativeFilter); assertTrue(delegateLocate > nativeFilter);
assertTrue(nearestSelection > delegateLocate); assertTrue(nearestSelection > delegateLocate);
assertTrue(monumentReject >= 0); assertTrue(policyFilter >= 0);
assertTrue(rejectContinue > monumentReject); assertTrue(filterContinue > policyFilter);
assertTrue(emptyNativePartition > rejectContinue); assertTrue(emptyNativePartition > filterContinue);
assertTrue(reachabilityLookup > emptyNativePartition); assertTrue(reachabilityLookup > emptyNativePartition);
assertFalse(filterMethod.contains("NativeStructureLocateCapability"));
assertFalse(irisHelper.contains("NativeStructureLocateCapability.isPaperUnavailable(structureId)")); assertFalse(irisHelper.contains("NativeStructureLocateCapability.isPaperUnavailable(structureId)"));
assertTrue(irisHelper.contains("new BlockPos(result.originX(), result.baseY(), result.originZ())")); assertTrue(irisHelper.contains("new BlockPos(result.originX(), result.baseY(), result.originZ())"));
} }
@@ -173,6 +173,24 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(raised, 0, 68, 0)); assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(raised, 0, 68, 0));
} }
@Test
public void raisedSurfaceTerrainDoesNotSliceTreeBlocks() {
Map<BlockPos, BlockState> blocks = new HashMap<>();
BlockState log = Blocks.OAK_LOG.defaultBlockState();
BlockState leaves = Blocks.OAK_LEAVES.defaultBlockState();
put(blocks, 0, 63, 0, Blocks.DIRT.defaultBlockState());
put(blocks, 0, 64, 0, Blocks.GRASS_BLOCK.defaultBlockState());
put(blocks, 0, 66, 0, log);
put(blocks, 0, 68, 0, leaves);
NativeStructurePostProcessor.applySurfaceColumn(
world(blocks), new BlockPos.MutableBlockPos(),
0, 0, 64, 68, -64, 319);
assertEquals(log, state(blocks, 0, 66, 0));
assertEquals(leaves, state(blocks, 0, 68, 0));
}
@Test @Test
public void loweredFluidColumnsRemainFluidFilled() { public void loweredFluidColumnsRemainFluidFilled() {
Map<BlockPos, BlockState> blocks = new HashMap<>(); Map<BlockPos, BlockState> blocks = new HashMap<>();
@@ -266,11 +284,18 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
} }
@Test @Test
public void vegetationCleanupUsesTheSameCircularTaperAsTerrain() { public void templateAirDoesNotEraseTreesInsideVillagePieces() throws Exception {
BoundingBox piece = new BoundingBox(0, 60, 0, 4, 80, 4); BlockPos origin = new BlockPos(0, 80, 0);
StructureTemplate template = template(List.of(
new StructureTemplate.StructureBlockInfo(BlockPos.ZERO, Blocks.AIR.defaultBlockState(), null)));
StructurePlaceSettings settings = new StructurePlaceSettings().setRotation(Rotation.NONE);
Map<BlockPos, BlockState> blocks = new HashMap<>();
BlockState log = Blocks.OAK_LOG.defaultBlockState();
blocks.put(origin, log);
assertTrue(NativeStructurePostProcessor.withinSurfaceTerrainRadius(16, 2, piece, 12)); NativeStructurePostProcessor.clearTemplateAir(world(blocks), template, origin, 80, settings);
assertFalse(NativeStructurePostProcessor.withinSurfaceTerrainRadius(16, 16, piece, 12));
assertEquals(log, blocks.get(origin));
} }
private static NativeStructurePostProcessor.SurfaceAnchor anchor(int meetY, int strength) { private static NativeStructurePostProcessor.SurfaceAnchor anchor(int meetY, int strength) {
@@ -27,8 +27,8 @@ public class NativeStructurePostProcessorVegetationTest {
} }
@Test @Test
public void surfaceStructuresClearTheirEntireFootprintByDefault() { public void surfaceStructuresPreserveVegetationUnlessConfigured() {
assertTrue(NativeStructurePostProcessor.shouldClearEntireVegetationFootprint( assertFalse(NativeStructurePostProcessor.shouldClearEntireVegetationFootprint(
GenerationStep.Decoration.SURFACE_STRUCTURES, false)); GenerationStep.Decoration.SURFACE_STRUCTURES, false));
assertTrue(NativeStructurePostProcessor.shouldClearEntireVegetationFootprint( assertTrue(NativeStructurePostProcessor.shouldClearEntireVegetationFootprint(
GenerationStep.Decoration.SURFACE_STRUCTURES, true)); GenerationStep.Decoration.SURFACE_STRUCTURES, true));
@@ -21,7 +21,6 @@ package art.arcane.iris.core.commands;
import art.arcane.iris.Iris; import art.arcane.iris.Iris;
import art.arcane.iris.platform.bukkit.BukkitPlatform; import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.core.service.ObjectStudioSaveService; import art.arcane.iris.core.service.ObjectStudioSaveService;
import art.arcane.iris.core.structure.NativeStructureLocateCapability;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.IrisStructureLocator; import art.arcane.iris.engine.framework.IrisStructureLocator;
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy; import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
@@ -136,10 +135,6 @@ public class CommandFind implements DirectorExecutor {
locateIrisStructure(e, structureKey, commandSender); locateIrisStructure(e, structureKey, commandSender);
return; return;
} }
if (NativeStructureLocateCapability.isPaperUnavailable(structureKey)) {
commandSender.sendMessage(C.RED + NativeStructureLocateCapability.unavailableMessage());
return;
}
} }
if (nativeStructure == null && IrisStructureLocator.isPlaced(e, structureKey)) { if (nativeStructure == null && IrisStructureLocator.isPlaced(e, structureKey)) {
@@ -21,7 +21,6 @@ package art.arcane.iris.core.commands;
import art.arcane.iris.Iris; import art.arcane.iris.Iris;
import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.structure.BulkStructureImporter; import art.arcane.iris.core.structure.BulkStructureImporter;
import art.arcane.iris.core.structure.NativeStructureLocateCapability;
import art.arcane.iris.core.structure.StructureCaptureImporter; import art.arcane.iris.core.structure.StructureCaptureImporter;
import art.arcane.iris.core.structure.StructureImporter; import art.arcane.iris.core.structure.StructureImporter;
import art.arcane.iris.core.structure.StructureIndexService; import art.arcane.iris.core.structure.StructureIndexService;
@@ -185,7 +184,6 @@ public class CommandStructure implements DirectorExecutor {
int nativeEligible = 0; int nativeEligible = 0;
int disabled = 0; int disabled = 0;
int unreachable = 0; int unreachable = 0;
int unavailable = 0;
int searchLimited = 0; int searchLimited = 0;
int errors = 0; int errors = 0;
for (String keyName : structureKeys) { for (String keyName : structureKeys) {
@@ -228,19 +226,12 @@ public class CommandStructure implements DirectorExecutor {
continue; continue;
} }
nativeEligible++; nativeEligible++;
if (NativeStructureLocateCapability.isPaperUnavailable(keyName)) {
unavailable++;
messages.add(C.YELLOW + "[native-eligible/locate-unavailable] " + C.WHITE + keyName + C.YELLOW + ": "
+ NativeStructureLocateCapability.unavailableMessage());
continue;
}
messages.add(C.GREEN + "[native-eligible] " + C.WHITE + keyName); messages.add(C.GREEN + "[native-eligible] " + C.WHITE + keyName);
} }
messages.add(C.GREEN + "Structure verify: " + C.WHITE + located + C.GREEN + " Iris placements located, " messages.add(C.GREEN + "Structure verify: " + C.WHITE + located + C.GREEN + " Iris placements located, "
+ C.WHITE + nativeEligible + C.GREEN + " native structures eligible, " + C.WHITE + nativeEligible + C.GREEN + " native structures eligible, "
+ C.WHITE + disabled + C.GREEN + " disabled by policy, " + C.WHITE + disabled + C.GREEN + " disabled by policy, "
+ C.WHITE + unreachable + C.GREEN + " biome-unreachable, " + C.WHITE + unreachable + C.GREEN + " biome-unreachable, "
+ C.WHITE + unavailable + C.GREEN + " generation-eligible but unavailable to synchronous locate, "
+ C.WHITE + searchLimited + C.GREEN + " density searches safety-limited, " + C.WHITE + searchLimited + C.GREEN + " density searches safety-limited, "
+ C.WHITE + errors + C.GREEN + " errors. Native eligibility is checked without running blocking live locates."); + C.WHITE + errors + C.GREEN + " errors. Native eligibility is checked without running blocking live locates.");
sendVerificationMessages(commandSender, target, messages); sendVerificationMessages(commandSender, target, messages);
@@ -35,18 +35,17 @@ public class IrisStructureLocateCommandContractTest {
int replacementCheck = method.indexOf( int replacementCheck = method.indexOf(
"decision.status() == NativeStructureGenerationStatus.REPLACED_BY_IRIS", policyResolution); "decision.status() == NativeStructureGenerationStatus.REPLACED_BY_IRIS", policyResolution);
int replacementLocate = method.indexOf("locateIrisStructure(e, structureKey, commandSender)", replacementCheck); int replacementLocate = method.indexOf("locateIrisStructure(e, structureKey, commandSender)", replacementCheck);
int capabilityCheck = method.indexOf("NativeStructureLocateCapability.isPaperUnavailable(structureKey)", replacementLocate);
int genericIrisLookup = method.indexOf( int genericIrisLookup = method.indexOf(
"nativeStructure == null && IrisStructureLocator.isPlaced(e, structureKey)", capabilityCheck); "nativeStructure == null && IrisStructureLocator.isPlaced(e, structureKey)", replacementLocate);
int nativeLocate = method.indexOf("targetWorld.locateNearestStructure(", genericIrisLookup); int nativeLocate = method.indexOf("targetWorld.locateNearestStructure(", genericIrisLookup);
assertTrue(nativeResolution >= 0); assertTrue(nativeResolution >= 0);
assertTrue(policyResolution > nativeResolution); assertTrue(policyResolution > nativeResolution);
assertTrue(replacementCheck > policyResolution); assertTrue(replacementCheck > policyResolution);
assertTrue(replacementLocate > replacementCheck); assertTrue(replacementLocate > replacementCheck);
assertTrue(capabilityCheck > replacementLocate); assertTrue(genericIrisLookup > replacementLocate);
assertTrue(genericIrisLookup > capabilityCheck);
assertTrue(nativeLocate > policyResolution); assertTrue(nativeLocate > policyResolution);
assertTrue(method.contains("decision.status() != NativeStructureGenerationStatus.REPLACED_BY_IRIS")); assertTrue(method.contains("decision.status() != NativeStructureGenerationStatus.REPLACED_BY_IRIS"));
assertFalse(method.contains("NativeStructureLocateCapability"));
} }
@Test @Test
@@ -7,6 +7,7 @@ import art.arcane.iris.engine.object.IrisStructureStiltSettings;
import art.arcane.volmlib.util.math.RNG; import art.arcane.volmlib.util.math.RNG;
import com.mojang.datafixers.util.Either; import com.mojang.datafixers.util.Either;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.Identifier; import net.minecraft.resources.Identifier;
import net.minecraft.tags.BlockTags; import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
@@ -252,7 +253,7 @@ public final class NativeStructurePostProcessor {
public static boolean shouldClearEntireVegetationFootprint(GenerationStep.Decoration step, public static boolean shouldClearEntireVegetationFootprint(GenerationStep.Decoration step,
boolean configured) { boolean configured) {
return configured || !isUndergroundStep(step); return configured;
} }
public static void prepareSurfaceStructures(WorldGenLevel world, BoundingBox area, public static void prepareSurfaceStructures(WorldGenLevel world, BoundingBox area,
@@ -433,9 +434,15 @@ public final class NativeStructurePostProcessor {
return; return;
} }
for (int y = originalY + 1; y < targetY; y++) { for (int y = originalY + 1; y < targetY; y++) {
world.setBlock(position.set(x, y, z), materials.subsurface(), 2); position.set(x, y, z);
if (!isTreeBlock(world.getBlockState(position))) {
world.setBlock(position, materials.subsurface(), 2);
}
}
position.set(x, targetY, z);
if (!isTreeBlock(world.getBlockState(position))) {
world.setBlock(position, materials.surface(), 2);
} }
world.setBlock(position.set(x, targetY, z), materials.surface(), 2);
} }
private static BlockState clearSurfaceDecorationAndResolveFill( private static BlockState clearSurfaceDecorationAndResolveFill(
@@ -531,8 +538,10 @@ public final class NativeStructurePostProcessor {
BlockState air = Blocks.AIR.defaultBlockState(); BlockState air = Blocks.AIR.defaultBlockState();
for (StructureTemplate.StructureBlockInfo airBlock : airBlocks) { for (StructureTemplate.StructureBlockInfo airBlock : airBlocks) {
BlockPos airPosition = airBlock.pos(); BlockPos airPosition = airBlock.pos();
BlockState existingState = world.getBlockState(airPosition);
if (shouldClearLegacyAir( if (shouldClearLegacyAir(
airPosition.getY(), groundY, world.getBlockState(airPosition).isAir())) { airPosition.getY(), groundY, existingState.isAir())
&& !isTreeBlock(existingState)) {
world.setBlock(airPosition, air, 2); world.setBlock(airPosition, air, 2);
} }
} }
@@ -620,7 +629,7 @@ public final class NativeStructurePostProcessor {
} }
boolean[] clearColumns = new boolean[area.getXSpan() * area.getZSpan()]; boolean[] clearColumns = new boolean[area.getXSpan() * area.getZSpan()];
for (VegetationTarget target : targets) { for (VegetationTarget target : targets) {
if (target != null && target.start() != null && target.start().isValid()) { if (target != null && target.force() && target.start() != null && target.start().isValid()) {
markVegetationColumns(area, snapshot, target, clearColumns); markVegetationColumns(area, snapshot, target, clearColumns);
} }
} }
@@ -673,25 +682,19 @@ public final class NativeStructurePostProcessor {
private static void markVegetationColumns(BoundingBox area, VegetationSnapshot snapshot, private static void markVegetationColumns(BoundingBox area, VegetationSnapshot snapshot,
VegetationTarget target, boolean[] clearColumns) { VegetationTarget target, boolean[] clearColumns) {
int width = area.getXSpan(); int width = area.getXSpan();
int padding = requiresSurfaceTerrain(target.start()) ? SURFACE_TERRAIN_RADIUS : 0;
int[] pieceTops = new int[clearColumns.length]; int[] pieceTops = new int[clearColumns.length];
Arrays.fill(pieceTops, Integer.MIN_VALUE); Arrays.fill(pieceTops, Integer.MIN_VALUE);
for (StructurePiece piece : target.start().getPieces()) { for (StructurePiece piece : target.start().getPieces()) {
BoundingBox bounds = piece.getBoundingBox(); BoundingBox bounds = piece.getBoundingBox();
int minX = Math.max(area.minX(), bounds.minX() - padding); int minX = Math.max(area.minX(), bounds.minX());
int maxX = Math.min(area.maxX(), bounds.maxX() + padding); int maxX = Math.min(area.maxX(), bounds.maxX());
int minZ = Math.max(area.minZ(), bounds.minZ() - padding); int minZ = Math.max(area.minZ(), bounds.minZ());
int maxZ = Math.min(area.maxZ(), bounds.maxZ() + padding); int maxZ = Math.min(area.maxZ(), bounds.maxZ());
if (minX > maxX || minZ > maxZ) { if (minX > maxX || minZ > maxZ) {
continue; continue;
} }
for (int z = minZ; z <= maxZ; z++) { for (int z = minZ; z <= maxZ; z++) {
for (int x = minX; x <= maxX; x++) { for (int x = minX; x <= maxX; x++) {
if (padding > 0) {
if (!withinSurfaceTerrainRadius(x, z, bounds, padding)) {
continue;
}
}
int column = (z - area.minZ()) * width + x - area.minX(); int column = (z - area.minZ()) * width + x - area.minX();
pieceTops[column] = Math.max(pieceTops[column], bounds.maxY()); pieceTops[column] = Math.max(pieceTops[column], bounds.maxY());
} }
@@ -707,12 +710,6 @@ public final class NativeStructurePostProcessor {
} }
} }
static boolean withinSurfaceTerrainRadius(int x, int z, BoundingBox bounds, int radius) {
int outX = IrisObjectVacuum.outset(x, bounds.minX(), bounds.maxX());
int outZ = IrisObjectVacuum.outset(z, bounds.minZ(), bounds.maxZ());
return (long) outX * outX + (long) outZ * outZ <= (long) radius * radius;
}
static boolean shouldClearVegetationColumn(int pieceTopY, int lowestTreeY, boolean force) { static boolean shouldClearVegetationColumn(int pieceTopY, int lowestTreeY, boolean force) {
return force || pieceTopY >= lowestTreeY; return force || pieceTopY >= lowestTreeY;
} }
@@ -742,7 +739,13 @@ public final class NativeStructurePostProcessor {
} }
private static boolean isTreeBlock(BlockState state) { private static boolean isTreeBlock(BlockState state) {
return state.is(BlockTags.LOGS) || state.is(BlockTags.LEAVES); if (state.is(BlockTags.LOGS) || state.is(BlockTags.LEAVES)) {
return true;
}
String path = BuiltInRegistries.BLOCK.getKey(state.getBlock()).getPath();
return path.endsWith("_log") || path.endsWith("_wood")
|| path.endsWith("_stem") || path.endsWith("_hyphae")
|| path.endsWith("_leaves");
} }
private static List<FoundationColumn> foundationEnvelope(BoundingBox area, StructureStart start) { private static List<FoundationColumn> foundationEnvelope(BoundingBox area, StructureStart start) {
@@ -242,10 +242,10 @@ final class IrisModdedBiomeSource extends BiomeSource {
throw new IllegalStateException("Iris returned no surface structure biome at quart " throw new IllegalStateException("Iris returned no surface structure biome at quart "
+ quartX + "," + quartZ); + quartX + "," + quartZ);
} }
Holder<Biome> resolved = resolveHolder(registry, irisBiome.getVanillaDerivativeKey()); Holder<Biome> resolved = resolveHolder(registry, irisBiome.getStructureDerivativeKey());
if (resolved == null) { if (resolved == null) {
throw new IllegalStateException("Iris structure biome derivative '" throw new IllegalStateException("Iris structure biome derivative '"
+ irisBiome.getVanillaDerivativeKey() + "' is not registered at quart " + irisBiome.getStructureDerivativeKey() + "' is not registered at quart "
+ quartX + "," + quartZ); + quartX + "," + quartZ);
} }
return resolved; return resolved;
@@ -262,10 +262,10 @@ final class IrisModdedBiomeSource extends BiomeSource {
if (registry == null) { if (registry == null) {
throw new IllegalStateException("Iris structure biome lookup has no biome registry"); throw new IllegalStateException("Iris structure biome lookup has no biome registry");
} }
Holder<Biome> resolved = resolveHolder(registry, resolution.irisBiome().getVanillaDerivativeKey()); Holder<Biome> resolved = resolveHolder(registry, resolution.irisBiome().getStructureDerivativeKey());
if (resolved == null) { if (resolved == null) {
throw new IllegalStateException("Iris structure biome derivative '" throw new IllegalStateException("Iris structure biome derivative '"
+ resolution.irisBiome().getVanillaDerivativeKey() + "' is not registered at block " + resolution.irisBiome().getStructureDerivativeKey() + "' is not registered at block "
+ resolution.blockX() + "," + resolution.blockY() + "," + resolution.blockZ()); + resolution.blockX() + "," + resolution.blockY() + "," + resolution.blockZ());
} }
return resolved; return resolved;
@@ -353,7 +353,7 @@ final class IrisModdedBiomeSource extends BiomeSource {
throw new IllegalStateException("Iris returned no structure biome at quart " throw new IllegalStateException("Iris returned no structure biome at quart "
+ quartX + "," + quartY + "," + quartZ); + quartX + "," + quartY + "," + quartZ);
} }
String derivativeKey = irisBiome.getVanillaDerivativeKey(); String derivativeKey = irisBiome.getStructureDerivativeKey();
Holder<Biome> resolved = resolveHolder(registry, derivativeKey); Holder<Biome> resolved = resolveHolder(registry, derivativeKey);
if (resolved == null) { if (resolved == null) {
throw new IllegalStateException("Iris structure biome derivative '" + derivativeKey throw new IllegalStateException("Iris structure biome derivative '" + derivativeKey
@@ -435,7 +435,7 @@ final class IrisModdedBiomeSource extends BiomeSource {
LinkedHashSet<String> possible = new LinkedHashSet<>(); LinkedHashSet<String> possible = new LinkedHashSet<>();
String namespace = engine.getDimension().getLoadKey().toLowerCase(Locale.ROOT); String namespace = engine.getDimension().getLoadKey().toLowerCase(Locale.ROOT);
for (IrisBiome irisBiome : engine.getAllBiomes()) { for (IrisBiome irisBiome : engine.getAllBiomes()) {
String derivative = normalizeKey(irisBiome.getVanillaDerivativeKey()); String derivative = normalizeKey(irisBiome.getStructureDerivativeKey());
if (derivative != null) { if (derivative != null) {
possible.add(derivative); possible.add(derivative);
} }
@@ -28,6 +28,7 @@ import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisBiomeCustom; import art.arcane.iris.engine.object.IrisBiomeCustom;
import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisNativeStructureDecision; import art.arcane.iris.engine.object.IrisNativeStructureDecision;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.engine.object.IrisStructureStiltSettings; import art.arcane.iris.engine.object.IrisStructureStiltSettings;
import art.arcane.iris.engine.object.NativeStructureGenerationStatus; import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
import art.arcane.iris.nativegen.NativeStructureGenerationException; import art.arcane.iris.nativegen.NativeStructureGenerationException;
@@ -611,7 +612,20 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
} }
static Set<String> collectConfiguredBiomeKeys(IrisDimension dimension, IrisData data) { static Set<String> collectConfiguredBiomeKeys(IrisDimension dimension, IrisData data) {
return collectConfiguredBiomeKeys(dimension.getReachableBiomes(() -> data), dimension.getLoadKey()); LinkedHashSet<String> keys = new LinkedHashSet<>(
collectConfiguredBiomeKeys(dimension.getReachableBiomes(() -> data), dimension.getLoadKey()));
for (IrisRegion region : dimension.getAllRegions(() -> data)) {
if (region == null) {
continue;
}
if (!region.getSeaBiomes().isEmpty()) {
keys.add("minecraft:the_void");
}
if (!region.getShoreBiomes().isEmpty()) {
keys.add("minecraft:beach");
}
}
return Set.copyOf(keys);
} }
static Set<String> collectConfiguredBiomeKeys(Iterable<IrisBiome> biomes, String dimensionLoadKey) { static Set<String> collectConfiguredBiomeKeys(Iterable<IrisBiome> biomes, String dimensionLoadKey) {
@@ -621,7 +635,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
if (irisBiome == null) { if (irisBiome == null) {
continue; continue;
} }
Identifier derivative = Identifier.tryParse(irisBiome.getVanillaDerivativeKey()); Identifier derivative = Identifier.tryParse(irisBiome.getStructureDerivativeKey());
if (derivative != null) { if (derivative != null) {
keys.add(derivative.toString().toLowerCase(Locale.ROOT)); keys.add(derivative.toString().toLowerCase(Locale.ROOT));
} }
@@ -4,6 +4,7 @@ import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisBiomeCustom; import art.arcane.iris.engine.object.IrisBiomeCustom;
import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisRange; import art.arcane.iris.engine.object.IrisRange;
import art.arcane.iris.engine.object.InferredType;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.mantle.flag.MantleFlag; import art.arcane.volmlib.util.mantle.flag.MantleFlag;
import net.minecraft.SharedConstants; import net.minecraft.SharedConstants;
@@ -101,11 +102,18 @@ public class IrisModdedStructureParityTest {
IrisBiome custom = new IrisBiome() IrisBiome custom = new IrisBiome()
.setDerivative("forest") .setDerivative("forest")
.setCustomDerivitives(new KList<>(new IrisBiomeCustom().setId("Aurora"))); .setCustomDerivitives(new KList<>(new IrisBiomeCustom().setId("Aurora")));
IrisBiome shore = new IrisBiome()
.setVanillaDerivative("minecraft:desert")
.setInferredType(InferredType.SHORE);
IrisBiome unsafeSea = new IrisBiome()
.setVanillaDerivative("minecraft:plains")
.setInferredType(InferredType.SEA);
Set<String> keys = IrisModdedChunkGenerator.collectConfiguredBiomeKeys( Set<String> keys = IrisModdedChunkGenerator.collectConfiguredBiomeKeys(
List.of(ocean, custom), "OverWorld"); List.of(ocean, custom, shore, unsafeSea), "OverWorld");
assertEquals(Set.of("minecraft:deep_ocean", "minecraft:forest", "overworld:aurora"), keys); assertEquals(Set.of("minecraft:deep_ocean", "minecraft:forest", "minecraft:beach",
"minecraft:the_void", "overworld:aurora"), keys);
assertFalse(keys.contains("minecraft:desert")); assertFalse(keys.contains("minecraft:desert"));
assertFalse(keys.contains("minecraft:plains")); assertFalse(keys.contains("minecraft:plains"));
} }
+2
View File
@@ -113,6 +113,8 @@ nmsBindings.each { key, value ->
rootProject.file("adapters/bukkit/nms/${key}/src/main/java/art/arcane/iris/core/nms/${key}/IrisChunkGenerator.java").absolutePath) rootProject.file("adapters/bukkit/nms/${key}/src/main/java/art/arcane/iris/core/nms/${key}/IrisChunkGenerator.java").absolutePath)
systemProperty('iris.nativeStructurePostProcessorSource', systemProperty('iris.nativeStructurePostProcessorSource',
rootProject.file('adapters/minecraft-common/src/main/java/art/arcane/iris/nativegen/NativeStructurePostProcessor.java').absolutePath) rootProject.file('adapters/minecraft-common/src/main/java/art/arcane/iris/nativegen/NativeStructurePostProcessor.java').absolutePath)
systemProperty('iris.customBiomeSource',
rootProject.file("adapters/bukkit/nms/${key}/src/main/java/art/arcane/iris/core/nms/${key}/CustomBiomeSource.java").absolutePath)
} }
} }
} }
@@ -1,35 +0,0 @@
/*
* Iris is a World Generator for Minecraft Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.core.structure;
public final class NativeStructureLocateCapability {
private static final String MONUMENT_KEY = "minecraft:monument";
private static final String UNAVAILABLE_MESSAGE = "Native monument generation is enabled, but synchronous monument locating is unavailable because a cold search can stall the server thread. The locate request was not run.";
private NativeStructureLocateCapability() {
}
public static boolean isPaperUnavailable(String structureKey) {
return structureKey != null && MONUMENT_KEY.equalsIgnoreCase(structureKey.trim());
}
public static String unavailableMessage() {
return UNAVAILABLE_MESSAGE;
}
}
@@ -41,6 +41,8 @@ import art.arcane.iris.engine.object.IrisStructurePlacement;
import art.arcane.iris.engine.object.IrisStructureStiltSettings; import art.arcane.iris.engine.object.IrisStructureStiltSettings;
import art.arcane.iris.spi.IrisLogging; import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.PlatformBlockState; import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.common.math.IrisBlockVector;
import art.arcane.iris.util.project.matter.TileWrapper; import art.arcane.iris.util.project.matter.TileWrapper;
import art.arcane.iris.util.project.noise.CNG; import art.arcane.iris.util.project.noise.CNG;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
@@ -67,7 +69,6 @@ public class IrisStructureComponent extends IrisMantleComponent {
private static final double OVERBORE_MIN_BOUNDARY_SQUARED = OVERBORE_MIN_BOUNDARY * OVERBORE_MIN_BOUNDARY; private static final double OVERBORE_MIN_BOUNDARY_SQUARED = OVERBORE_MIN_BOUNDARY * OVERBORE_MIN_BOUNDARY;
private static final double OVERBORE_BOUNDARY_SPAN = 0.8; private static final double OVERBORE_BOUNDARY_SPAN = 0.8;
private static final double OVERBORE_MAX_UP_REACH = 1.8; private static final double OVERBORE_MAX_UP_REACH = 1.8;
private static final int DYNAMIC_STRUCTURE_Y_TOLERANCE = 32;
private static final MatterCavern CARVE_CAVERN = new MatterCavern(true, "", (byte) 3); private static final MatterCavern CARVE_CAVERN = new MatterCavern(true, "", (byte) 3);
public IrisStructureComponent(EngineMantle engineMantle) { public IrisStructureComponent(EngineMantle engineMantle) {
@@ -409,18 +410,19 @@ public class IrisStructureComponent extends IrisMantleComponent {
private void clearIntersectingObjectTrees(MantleWriter writer, IrisStructureLocator.ResolvedPlacement resolved) { private void clearIntersectingObjectTrees(MantleWriter writer, IrisStructureLocator.ResolvedPlacement resolved) {
int mantleOffset = getEngineMantle().getEngine().getMinHeight(); int mantleOffset = getEngineMantle().getEngine().getMinHeight();
int worldHeight = getEngineMantle().getEngine().getHeight(); int worldHeight = getEngineMantle().getEngine().getHeight();
int verticalTolerance = resolved.exactY() ? 0 : DYNAMIC_STRUCTURE_Y_TOLERANCE;
Map<String, ObjectMarkerBounds> intersectingObjects = new HashMap<>(); Map<String, ObjectMarkerBounds> intersectingObjects = new HashMap<>();
for (PlacedStructurePiece piece : resolved.pieces()) { for (PlacedStructurePiece piece : resolved.pieces()) {
int minY = Math.max(0, piece.getMinY() - mantleOffset - verticalTolerance); for (IrisBlockVector local : piece.getObject().getBlocks().keys()) {
int maxY = Math.min(worldHeight - 1, piece.getMaxY() - mantleOffset + verticalTolerance); PlatformBlockState structureState = piece.getObject().getBlocks().get(local);
if (minY > maxY) { if (!isOccupiedStructureState(structureState)) {
continue; continue;
} }
for (int x = piece.getMinX(); x <= piece.getMaxX(); x++) { IrisBlockVector rotated = piece.getRotation().rotate(local.clone());
for (int z = piece.getMinZ(); z <= piece.getMaxZ(); z++) { int y = piece.getY() - mantleOffset + rotated.getBlockY();
collectIntersectingObjectTreeMarkers( if (y >= 0 && y < worldHeight) {
writer, x, z, minY, maxY, intersectingObjects); collectIntersectingObjectTreeMarker(
writer, piece.getX() + rotated.getBlockX(), y,
piece.getZ() + rotated.getBlockZ(), intersectingObjects);
} }
} }
} }
@@ -429,19 +431,20 @@ public class IrisStructureComponent extends IrisMantleComponent {
} }
} }
private void collectIntersectingObjectTreeMarkers(MantleWriter writer, int x, int z, static boolean isOccupiedStructureState(PlatformBlockState state) {
int minY, int maxY, return !B.isAir(state);
Map<String, ObjectMarkerBounds> intersectingObjects) { }
for (int y = minY; y <= maxY; y++) {
PlatformBlockState state = writer.getDataIfPresent(x, y, z, PlatformBlockState.class); private void collectIntersectingObjectTreeMarker(MantleWriter writer, int x, int y, int z,
if (state == null || !state.isTreeBlock()) { Map<String, ObjectMarkerBounds> intersectingObjects) {
continue; PlatformBlockState state = writer.getDataIfPresent(x, y, z, PlatformBlockState.class);
} if (state == null || !state.isTreeBlock()) {
String marker = writer.getDataIfPresent(x, y, z, String.class); return;
if (isOrdinaryObjectMarker(marker)) { }
intersectingObjects.computeIfAbsent(marker, ignored -> new ObjectMarkerBounds()) String marker = writer.getDataIfPresent(x, y, z, String.class);
.include(x, y, z); if (isOrdinaryObjectMarker(marker)) {
} intersectingObjects.computeIfAbsent(marker, ignored -> new ObjectMarkerBounds())
.include(x, y, z);
} }
} }
@@ -144,7 +144,7 @@ public class IrisBiome extends IrisRegistrant implements IRare {
@Desc("The raw derivative of this biome. This is required or the terrain will not properly generate. Use any vanilla biome type. Look in examples/biome-list.txt") @Desc("The raw derivative of this biome. This is required or the terrain will not properly generate. Use any vanilla biome type. Look in examples/biome-list.txt")
private String derivative = "minecraft:the_void"; private String derivative = "minecraft:the_void";
@Required @Required
@Desc("Override the derivative when vanilla places structures to this derivative. This is useful for example if you have an ocean biome, but you have set the derivative to desert to get a brown-ish color. To prevent desert structures from spawning on top of your ocean, you can set your vanillaDerivative to ocean, to allow for vanilla structures. Not defining this value will simply select the derivative.") @Desc("Override the derivative used for vanilla structure selection. Iris still enforces the generated terrain role: land-only Minecraft derivatives on sea biomes expose no native structure biome, and land-only derivatives on shore biomes resolve as beach, while exact ocean, river, beach, and shore variants remain eligible. Non-Minecraft namespaces remain authoritative. Not defining this value selects derivative.")
private String vanillaDerivative = null; private String vanillaDerivative = null;
@ArrayType(min = 1, type = String.class) @ArrayType(min = 1, type = String.class)
@Desc("You can instead specify multiple biome derivatives to randomly scatter colors in this biome") @Desc("You can instead specify multiple biome derivatives to randomly scatter colors in this biome")
@@ -315,6 +315,20 @@ public class IrisBiome extends IrisRegistrant implements IRare {
return resolved == null ? namespacedBiomeKey(derivative) : resolved; return resolved == null ? namespacedBiomeKey(derivative) : resolved;
} }
public String getStructureDerivativeKey() {
String key = getVanillaDerivativeKey();
if (key == null || !key.startsWith("minecraft:")) {
return key;
}
if (isSea() && !isVanillaSeaStructureBiome(key)) {
return "minecraft:the_void";
}
if (isShore() && !isVanillaShoreStructureBiome(key)) {
return "minecraft:beach";
}
return key;
}
public String getDerivativeKey() { public String getDerivativeKey() {
return namespacedBiomeKey(derivative); return namespacedBiomeKey(derivative);
} }
@@ -327,6 +341,14 @@ public class IrisBiome extends IrisRegistrant implements IRare {
return trimmed.indexOf(':') >= 0 ? trimmed : "minecraft:" + trimmed; return trimmed.indexOf(':') >= 0 ? trimmed : "minecraft:" + trimmed;
} }
static boolean isVanillaSeaStructureBiome(String key) {
return key != null && (key.contains("ocean") || key.endsWith("river"));
}
static boolean isVanillaShoreStructureBiome(String key) {
return key != null && (key.endsWith("beach") || key.endsWith("shore"));
}
private KList<Biome> getBiomeScatterResolved() { private KList<Biome> getBiomeScatterResolved() {
return biomeScatterResolved.aquire(() -> resolveBiomeKeys(biomeScatter)); return biomeScatterResolved.aquire(() -> resolveBiomeKeys(biomeScatter));
} }
@@ -52,7 +52,7 @@ public class IrisImportedStructureControl {
private boolean datapackOverrides = true; private boolean datapackOverrides = true;
@ArrayType(type = IrisVanillaStructureAdjustment.class, min = 1) @ArrayType(type = IrisVanillaStructureAdjustment.class, min = 1)
@Desc("Per-structure adjustments applied to vanilla, mod, and datapack structures that still generate natively. Vertical shifts from every matching entry stack. Surface-intersecting structures clear logs and leaves automatically; a matching vegetation option can force clearing for unusual placements. The last matching entry with stilt settings controls foundation columns. A structure suppressed by an Iris placement is unaffected.") @Desc("Per-structure adjustments applied to vanilla, mod, and datapack structures that still generate natively. Vertical shifts from every matching entry stack. A matching vegetation option explicitly clears logs and leaves inside structure piece bounds. The last matching entry with stilt settings controls foundation columns. A structure suppressed by an Iris placement is unaffected.")
private KList<IrisVanillaStructureAdjustment> adjustments = new KList<>(); private KList<IrisVanillaStructureAdjustment> adjustments = new KList<>();
public boolean shouldGenerate(String key) { public boolean shouldGenerate(String key) {
@@ -1,48 +0,0 @@
/*
* Iris is a World Generator for Minecraft Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.core.structure;
import art.arcane.iris.engine.object.IrisImportedStructureControl;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class NativeStructureLocateCapabilityTest {
@Test
public void monumentLocateIsUnavailableWithoutChangingGenerationPolicy() {
assertTrue(NativeStructureLocateCapability.isPaperUnavailable("minecraft:monument"));
assertTrue(NativeStructureLocateCapability.isPaperUnavailable(" MINECRAFT:MONUMENT "));
assertTrue(new IrisImportedStructureControl().shouldGenerate("minecraft:monument"));
}
@Test
public void otherStructuresRemainLocatable() {
assertFalse(NativeStructureLocateCapability.isPaperUnavailable(null));
assertFalse(NativeStructureLocateCapability.isPaperUnavailable(""));
assertFalse(NativeStructureLocateCapability.isPaperUnavailable("minecraft:stronghold"));
}
@Test
public void messageStatesThatGenerationRemainsEnabled() {
String message = NativeStructureLocateCapability.unavailableMessage().toLowerCase();
assertTrue(message.contains("generation is enabled"));
assertTrue(message.contains("locat"));
}
}
@@ -41,6 +41,18 @@ public class IrisStructureComponentMarkerTest {
assertNotEquals(first, moved); assertNotEquals(first, moved);
} }
@Test
public void treeCollisionUsesOnlyNonAirStructureBlocks() {
PlatformBlockState solid = mock(PlatformBlockState.class);
PlatformBlockState air = mock(PlatformBlockState.class);
when(solid.isAir()).thenReturn(false);
when(air.isAir()).thenReturn(true);
assertTrue(IrisStructureComponent.isOccupiedStructureState(solid));
assertFalse(IrisStructureComponent.isOccupiedStructureState(air));
assertFalse(IrisStructureComponent.isOccupiedStructureState(null));
}
@Test @Test
public void emptyResolvedPiecesRemainSkippableForNonReplacement() { public void emptyResolvedPiecesRemainSkippableForNonReplacement() {
IrisStructureLocator.ResolvedPlacement resolved = resolvedPlacement( IrisStructureLocator.ResolvedPlacement resolved = resolvedPlacement(
@@ -0,0 +1,67 @@
package art.arcane.iris.engine.object;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class IrisBiomeStructureDerivativeTest {
@Test
public void seaBiomeRejectsLandOnlyVanillaDerivativeForStructures() {
IrisBiome biome = new IrisBiome()
.setDerivative("minecraft:warm_ocean")
.setVanillaDerivative("minecraft:desert")
.setInferredType(InferredType.SEA);
assertEquals("minecraft:desert", biome.getVanillaDerivativeKey());
assertEquals("minecraft:the_void", biome.getStructureDerivativeKey());
}
@Test
public void shoreBiomeRejectsLandOnlyVanillaDerivativeForStructures() {
IrisBiome biome = new IrisBiome()
.setDerivative("minecraft:warm_ocean")
.setVanillaDerivative("minecraft:savanna")
.setInferredType(InferredType.SHORE);
assertEquals("minecraft:beach", biome.getStructureDerivativeKey());
}
@Test
public void waterStructureDerivativesKeepTheirExactVariant() {
IrisBiome deepOcean = new IrisBiome()
.setVanillaDerivative("minecraft:deep_cold_ocean")
.setInferredType(InferredType.SEA);
IrisBiome snowyBeach = new IrisBiome()
.setVanillaDerivative("minecraft:snowy_beach")
.setInferredType(InferredType.SHORE);
assertEquals("minecraft:deep_cold_ocean", deepOcean.getStructureDerivativeKey());
assertEquals("minecraft:snowy_beach", snowyBeach.getStructureDerivativeKey());
}
@Test
public void seaAndShoreDoNotBorrowEachOthersStructureBiomes() {
IrisBiome seaBeach = new IrisBiome()
.setVanillaDerivative("minecraft:beach")
.setInferredType(InferredType.SEA);
IrisBiome shoreOcean = new IrisBiome()
.setVanillaDerivative("minecraft:ocean")
.setInferredType(InferredType.SHORE);
assertEquals("minecraft:the_void", seaBeach.getStructureDerivativeKey());
assertEquals("minecraft:beach", shoreOcean.getStructureDerivativeKey());
}
@Test
public void landAndModdedDerivativesRemainAuthoritative() {
IrisBiome land = new IrisBiome()
.setVanillaDerivative("minecraft:desert")
.setInferredType(InferredType.LAND);
IrisBiome moddedSea = new IrisBiome()
.setVanillaDerivative("example:abyss")
.setInferredType(InferredType.SEA);
assertEquals("minecraft:desert", land.getStructureDerivativeKey());
assertEquals("example:abyss", moddedSea.getStructureDerivativeKey());
}
}