This commit is contained in:
Brian Neumann-Fopiano committed 2026-07-16 18:18:23 -04:00
1 parent 699247b1de
commit e8dd4e1127
10 files changed
+229 -36

No files matched your search

+2
View File
@@ -42,3 +42,5 @@ service-account*.json
docs/ docs/
CROSSPLATFORM_PLAN.md CROSSPLATFORM_PLAN.md
.qa/
@@ -465,7 +465,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
BoundingBox area, ChunkPos chunkPos, String structureId, StructureStart start, BoundingBox area, ChunkPos chunkPos, String structureId, StructureStart start,
IrisNativeStructureDecision decision) { IrisNativeStructureDecision decision) {
NativeStructurePostProcessor.place(world, structureManager, this, random, area, chunkPos, NativeStructurePostProcessor.place(world, structureManager, this, random, area, chunkPos,
structureId, start, decision, this::resolveStiltBlock); structureId, start, decision, this::resolveStiltBlock,
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
} }
private List<List<Structure>> structuresByStep(Registry<Structure> registry) { private List<List<Structure>> structuresByStep(Registry<Structure> registry) {
@@ -85,10 +85,12 @@ public class IrisChunkGeneratorMonumentLocateContractTest {
int placement = source.indexOf("start.placeInChunk(world, structureManager, generator"); int placement = source.indexOf("start.placeInChunk(world, structureManager, generator");
int stiltPlacement = source.indexOf("placeStilts(world, area, structureId, start", placement); int stiltPlacement = source.indexOf("placeStilts(world, area, structureId, start", placement);
int occupancyCheck = source.indexOf("if (state.isSolid())", stiltPlacement); int occupancyCheck = source.indexOf("if (state.isSolid())", stiltPlacement);
int terrainFloor = source.indexOf("y > terrainY", stiltPlacement);
assertTrue(placement >= 0); assertTrue(placement >= 0);
assertTrue(stiltPlacement > placement); assertTrue(stiltPlacement > placement);
assertTrue(occupancyCheck > stiltPlacement); assertTrue(occupancyCheck > stiltPlacement);
assertTrue(terrainFloor > stiltPlacement);
assertFalse(source.contains("state.equals(")); assertFalse(source.contains("state.equals("));
assertFalse(source.contains("snapshot.states")); assertFalse(source.contains("snapshot.states"));
} }
@@ -60,13 +60,15 @@ public final class NativeStructurePostProcessor {
public static void place(WorldGenLevel world, StructureManager structureManager, ChunkGenerator generator, public static void place(WorldGenLevel world, StructureManager structureManager, ChunkGenerator generator,
WorldgenRandom random, BoundingBox area, ChunkPos chunkPos, String structureId, WorldgenRandom random, BoundingBox area, ChunkPos chunkPos, String structureId,
StructureStart start, IrisNativeStructureDecision decision, StructureStart start, IrisNativeStructureDecision decision,
StiltBlockResolver stiltBlockResolver) { StiltBlockResolver stiltBlockResolver,
IntBinaryOperator surfaceHeight) {
IrisStructureStiltSettings stilt = decision.stilt(); IrisStructureStiltSettings stilt = decision.stilt();
ensureMonumentSeaLevelAlignment(start, structureId, decision.yShift(), generator.getSeaLevel(), ensureMonumentSeaLevelAlignment(start, structureId, decision.yShift(), generator.getSeaLevel(),
area.minY(), area.maxY() + 1); area.minY(), area.maxY() + 1);
start.placeInChunk(world, structureManager, generator, random, area, chunkPos); start.placeInChunk(world, structureManager, generator, random, area, chunkPos);
if (stilt != null) { if (stilt != null) {
placeStilts(world, area, structureId, start, stilt, stiltBlockResolver); placeStilts(world, area, structureId, start, stilt, stiltBlockResolver, surfaceHeight,
!isUndergroundStep(start.getStructure().step()));
} }
} }
@@ -798,7 +800,10 @@ public final class NativeStructurePostProcessor {
private static void placeStilts(WorldGenLevel world, BoundingBox area, String structureId, private static void placeStilts(WorldGenLevel world, BoundingBox area, String structureId,
StructureStart start, IrisStructureStiltSettings settings, StructureStart start, IrisStructureStiltSettings settings,
StiltBlockResolver stiltBlockResolver) { StiltBlockResolver stiltBlockResolver,
IntBinaryOperator surfaceHeight,
boolean surfaceStructure) {
Objects.requireNonNull(surfaceHeight, "Structure stilts require an Iris terrain height resolver");
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos(); BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
int structureHash = structureId == null ? 0 : structureId.hashCode(); int structureHash = structureId == null ? 0 : structureId.hashCode();
RNG rng = new RNG(world.getSeed() ^ structureHash); RNG rng = new RNG(world.getSeed() ^ structureHash);
@@ -807,8 +812,12 @@ public final class NativeStructurePostProcessor {
if (foundationY == Integer.MIN_VALUE) { if (foundationY == Integer.MIN_VALUE) {
continue; continue;
} }
int terrainY = surfaceStructure
? Math.max(area.minY(), Math.min(
area.maxY(), surfaceHeight.applyAsInt(column.x(), column.z())))
: area.minY() - 1;
for (int depth = 0, y = foundationY - 1; for (int depth = 0, y = foundationY - 1;
depth < settings.getMaxDepth() && y >= area.minY(); depth++, y--) { depth < settings.getMaxDepth() && y > terrainY; depth++, y--) {
position.set(column.x(), y, column.z()); position.set(column.x(), y, column.z());
BlockState existingState = world.getBlockState(position); BlockState existingState = world.getBlockState(position);
boolean vegetation = existingState.is(BlockTags.LOGS) || existingState.is(BlockTags.LEAVES); boolean vegetation = existingState.is(BlockTags.LOGS) || existingState.is(BlockTags.LEAVES);
@@ -825,8 +834,10 @@ public final class NativeStructurePostProcessor {
} }
public static StiltSupportAudit auditStiltSupport(WorldGenLevel world, BoundingBox area, public static StiltSupportAudit auditStiltSupport(WorldGenLevel world, BoundingBox area,
StructureStart start, BlockState expectedStilt) { StructureStart start, BlockState expectedStilt,
IntBinaryOperator surfaceHeight) {
Objects.requireNonNull(expectedStilt, "Expected stilt state must not be null"); Objects.requireNonNull(expectedStilt, "Expected stilt state must not be null");
Objects.requireNonNull(surfaceHeight, "Structure stilt audit requires an Iris terrain height resolver");
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos(); BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
int baseColumns = 0; int baseColumns = 0;
int stiltBlocks = 0; int stiltBlocks = 0;
@@ -838,9 +849,15 @@ public final class NativeStructurePostProcessor {
continue; continue;
} }
baseColumns++; baseColumns++;
boolean grounded = foundationY == area.minY(); int terrainY = Math.max(area.minY(), Math.min(
area.maxY(), surfaceHeight.applyAsInt(column.x(), column.z())));
boolean grounded = foundationY <= terrainY + 1;
boolean stiltColumn = false; boolean stiltColumn = false;
for (int y = foundationY - 1; y >= area.minY(); y--) { for (int y = foundationY - 1; y >= area.minY(); y--) {
if (y <= terrainY) {
grounded = true;
break;
}
BlockState state = world.getBlockState(position.set(column.x(), y, column.z())); BlockState state = world.getBlockState(position.set(column.x(), y, column.z()));
if (state.is(expectedStilt.getBlock())) { if (state.is(expectedStilt.getBlock())) {
stiltBlocks++; stiltBlocks++;
@@ -1041,7 +1041,8 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
String structureId, StructureStart start, String structureId, StructureStart start,
IrisNativeStructureDecision decision) { IrisNativeStructureDecision decision) {
NativeStructurePostProcessor.place(world, structureManager, this, random, area, chunkPos, NativeStructurePostProcessor.place(world, structureManager, this, random, area, chunkPos,
structureId, start, decision, this::resolveStiltBlock); structureId, start, decision, this::resolveStiltBlock,
(x, z) -> engine().getHeight(x, z, true) + engine().getMinHeight());
} }
private List<List<Structure>> structuresByStep(Registry<Structure> registry) { private List<List<Structure>> structuresByStep(Registry<Structure> registry) {
@@ -19,6 +19,7 @@
package art.arcane.iris.modded; package art.arcane.iris.modded;
import art.arcane.iris.core.nms.datapack.DataVersion; import art.arcane.iris.core.nms.datapack.DataVersion;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy; import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
import art.arcane.iris.engine.framework.StructureVerticalBounds; import art.arcane.iris.engine.framework.StructureVerticalBounds;
import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisDimension;
@@ -44,6 +45,7 @@ import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.Heightmap;
@@ -71,6 +73,7 @@ import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import java.util.function.IntBinaryOperator;
public final class ModdedWorldCheck { public final class ModdedWorldCheck {
private static final int EXIT_PASS = 0; private static final int EXIT_PASS = 0;
@@ -872,9 +875,16 @@ public final class ModdedWorldCheck {
BoundingBox area = new BoundingBox( BoundingBox area = new BoundingBox(
minimumChunkX, minimumWorldY, minimumChunkZ, minimumChunkX, minimumWorldY, minimumChunkZ,
maximumChunkX, maximumWorldY, maximumChunkZ); maximumChunkX, maximumWorldY, maximumChunkZ);
ChunkGenerator chunkGenerator = level.getChunkSource().getGenerator();
if (!(chunkGenerator instanceof IrisModdedChunkGenerator irisGenerator)) {
throw new IllegalStateException("Iris structure audit requires the Iris chunk generator");
}
Engine engine = irisGenerator.commandEngine();
IntBinaryOperator surfaceHeight = (x, z) ->
engine.getHeight(x, z, true) + engine.getMinHeight();
NativeStructurePostProcessor.StiltSupportAudit foundation = NativeStructurePostProcessor.StiltSupportAudit foundation =
NativeStructurePostProcessor.auditStiltSupport( NativeStructurePostProcessor.auditStiltSupport(
level, area, start, Blocks.COBBLESTONE.defaultBlockState()); level, area, start, Blocks.COBBLESTONE.defaultBlockState(), surfaceHeight);
foundationBaseColumns = foundation.baseColumns(); foundationBaseColumns = foundation.baseColumns();
foundationBlocks = foundation.stiltBlocks(); foundationBlocks = foundation.stiltBlocks();
foundationColumns = foundation.stiltColumns(); foundationColumns = foundation.stiltColumns();
@@ -41,19 +41,23 @@ 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.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;
import art.arcane.iris.util.project.context.ChunkContext; import art.arcane.iris.util.project.context.ChunkContext;
import art.arcane.volmlib.util.documentation.ChunkCoordinates; import art.arcane.volmlib.util.documentation.ChunkCoordinates;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterCavern; import art.arcane.volmlib.util.matter.MatterCavern;
import art.arcane.volmlib.util.matter.MatterStructurePOI;
import art.arcane.volmlib.util.mantle.flag.ReservedFlag; import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
import art.arcane.volmlib.util.math.RNG; import art.arcane.volmlib.util.math.RNG;
import it.unimi.dsi.fastutil.longs.Long2IntMap; import it.unimi.dsi.fastutil.longs.Long2IntMap;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import java.util.HashSet; import java.util.HashMap;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
@ComponentFlag(ReservedFlag.JIGSAW) @ComponentFlag(ReservedFlag.JIGSAW)
public class IrisStructureComponent extends IrisMantleComponent { public class IrisStructureComponent extends IrisMantleComponent {
@@ -159,12 +163,13 @@ public class IrisStructureComponent extends IrisMantleComponent {
} }
requireAppliedPieces(resolved, cx, cz, failedPieces); requireAppliedPieces(resolved, cx, cz, failedPieces);
if (failedPieces == 0 && placement.getStilt() != null) { if (failedPieces == 0 && placement.getStilt() != null) {
placeFoundation(writer, foundationColumns, placement.getStilt(), rng); placeFoundation(writer, foundationColumns, placement.getStilt(), rng, !placement.isUnderground());
} }
} }
private void placeFoundation(MantleWriter writer, Long2IntOpenHashMap columns, private void placeFoundation(MantleWriter writer, Long2IntOpenHashMap columns,
IrisStructureStiltSettings settings, RNG rng) { IrisStructureStiltSettings settings, RNG rng,
boolean surfaceStructure) {
IrisMaterialPalette palette = Objects.requireNonNull( IrisMaterialPalette palette = Objects.requireNonNull(
settings.getPalette(), "Structure stilt palette must not be null"); settings.getPalette(), "Structure stilt palette must not be null");
int mantleOffset = getEngineMantle().getEngine().getMinHeight(); int mantleOffset = getEngineMantle().getEngine().getMinHeight();
@@ -181,10 +186,17 @@ public class IrisStructureComponent extends IrisMantleComponent {
int terrainHeight = getEngineMantle().trueHeight(worldX, worldZ); int terrainHeight = getEngineMantle().trueHeight(worldX, worldZ);
int groundY = StructureFoundationPlanner.findGroundY( int groundY = StructureFoundationPlanner.findGroundY(
foundationY, maxDepth, 0, foundationY, maxDepth, 0,
y -> StructureFoundationPlanner.isGroundSolid( y -> {
writer.getDataIfPresent(worldX, y, worldZ, PlatformBlockState.class), PlatformBlockState overlay = writer.getDataIfPresent(
writer.getDataIfPresent(worldX, y, worldZ, MatterCavern.class) != null, worldX, y, worldZ, PlatformBlockState.class);
y, terrainHeight)); boolean carved = writer.getDataIfPresent(
worldX, y, worldZ, MatterCavern.class) != null;
return surfaceStructure
? StructureFoundationPlanner.isSurfaceSupportBoundary(
overlay, carved, y, terrainHeight)
: StructureFoundationPlanner.isGroundSolid(
overlay, carved, y, terrainHeight);
});
if (groundY == StructureFoundationPlanner.NO_GROUND) { if (groundY == StructureFoundationPlanner.NO_GROUND) {
continue; continue;
} }
@@ -398,6 +410,7 @@ public class IrisStructureComponent extends IrisMantleComponent {
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; int verticalTolerance = resolved.exactY() ? 0 : DYNAMIC_STRUCTURE_Y_TOLERANCE;
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); int minY = Math.max(0, piece.getMinY() - mantleOffset - verticalTolerance);
int maxY = Math.min(worldHeight - 1, piece.getMaxY() - mantleOffset + verticalTolerance); int maxY = Math.min(worldHeight - 1, piece.getMaxY() - mantleOffset + verticalTolerance);
@@ -406,15 +419,19 @@ public class IrisStructureComponent extends IrisMantleComponent {
} }
for (int x = piece.getMinX(); x <= piece.getMaxX(); x++) { for (int x = piece.getMinX(); x <= piece.getMaxX(); x++) {
for (int z = piece.getMinZ(); z <= piece.getMaxZ(); z++) { for (int z = piece.getMinZ(); z <= piece.getMaxZ(); z++) {
clearIntersectingObjectTreeColumn(writer, x, z, minY, maxY, worldHeight); collectIntersectingObjectTreeMarkers(
writer, x, z, minY, maxY, intersectingObjects);
} }
} }
} }
for (Map.Entry<String, ObjectMarkerBounds> entry : intersectingObjects.entrySet()) {
clearMarkerOwnedObject(writer, entry.getKey(), entry.getValue(), worldHeight);
}
} }
private void clearIntersectingObjectTreeColumn(MantleWriter writer, int x, int z, private void collectIntersectingObjectTreeMarkers(MantleWriter writer, int x, int z,
int minY, int maxY, int worldHeight) { int minY, int maxY,
Set<String> intersectingMarkers = null; Map<String, ObjectMarkerBounds> intersectingObjects) {
for (int y = minY; y <= maxY; y++) { for (int y = minY; y <= maxY; y++) {
PlatformBlockState state = writer.getDataIfPresent(x, y, z, PlatformBlockState.class); PlatformBlockState state = writer.getDataIfPresent(x, y, z, PlatformBlockState.class);
if (state == null || !state.isTreeBlock()) { if (state == null || !state.isTreeBlock()) {
@@ -422,26 +439,52 @@ public class IrisStructureComponent extends IrisMantleComponent {
} }
String marker = writer.getDataIfPresent(x, y, z, String.class); String marker = writer.getDataIfPresent(x, y, z, String.class);
if (isOrdinaryObjectMarker(marker)) { if (isOrdinaryObjectMarker(marker)) {
if (intersectingMarkers == null) { intersectingObjects.computeIfAbsent(marker, ignored -> new ObjectMarkerBounds())
intersectingMarkers = new HashSet<>(); .include(x, y, z);
}
intersectingMarkers.add(marker);
} }
} }
if (intersectingMarkers == null) { }
private void clearMarkerOwnedObject(MantleWriter writer, String marker,
ObjectMarkerBounds intersections, int worldHeight) {
StructurePlacementMarker.Decoded decoded = StructurePlacementMarker.decode(marker);
if (decoded == null) {
return; return;
} }
for (int y = 0; y < worldHeight; y++) { IrisObject object = getData().load(IrisObject.class, decoded.objectKey(), false);
String marker = writer.getDataIfPresent(x, y, z, String.class); if (object == null) {
if (!intersectingMarkers.contains(marker)) { return;
continue; }
int horizontalSpan = Math.max(1, Math.max(object.getW(), object.getD()));
int verticalSpan = Math.max(1, object.getH());
ObjectMarkerBounds search = intersections.expand(horizontalSpan - 1, verticalSpan - 1, worldHeight);
KList<ObjectBlockPosition> positions = new KList<>();
int minChunkX = Math.floorDiv(search.minX, 16);
int maxChunkX = Math.floorDiv(search.maxX, 16);
int minChunkZ = Math.floorDiv(search.minZ, 16);
int maxChunkZ = Math.floorDiv(search.maxZ, 16);
for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) {
for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) {
MantleChunk<Matter> chunk = writer.acquireChunk(chunkX, chunkZ);
if (chunk == null) {
continue;
}
int blockX = chunkX << 4;
int blockZ = chunkZ << 4;
chunk.iterate(String.class, (localX, y, localZ, value) -> {
int x = blockX + localX;
int z = blockZ + localZ;
if (marker.equals(value) && search.contains(x, y, z)) {
positions.add(new ObjectBlockPosition(x, y, z));
}
});
} }
PlatformBlockState state = writer.getDataIfPresent(x, y, z, PlatformBlockState.class); }
if (state == null || !state.isTreeBlock()) { for (ObjectBlockPosition position : positions) {
continue; writer.clearBlock(position.x(), position.y(), position.z());
} writer.clearData(position.x(), position.y(), position.z(), String.class);
writer.clearBlock(x, y, z); writer.clearData(position.x(), position.y(), position.z(), TileWrapper.class);
writer.clearData(x, y, z, String.class); writer.clearData(position.x(), position.y(), position.z(), MatterStructurePOI.class);
} }
} }
@@ -450,6 +493,21 @@ public class IrisStructureComponent extends IrisMantleComponent {
return decoded != null && !decoded.structureAware(); return decoded != null && !decoded.structureAware();
} }
static ObjectMarkerBounds markerSearchBounds(int minX, int minY, int minZ,
int maxX, int maxY, int maxZ,
int horizontalSpan, int verticalSpan,
int worldHeight) {
ObjectMarkerBounds bounds = new ObjectMarkerBounds();
bounds.minX = minX;
bounds.minY = minY;
bounds.minZ = minZ;
bounds.maxX = maxX;
bounds.maxY = maxY;
bounds.maxZ = maxZ;
return bounds.expand(
Math.max(0, horizontalSpan - 1), Math.max(0, verticalSpan - 1), worldHeight);
}
private int[] computePieceBounds(KList<PlacedStructurePiece> pieces) { private int[] computePieceBounds(KList<PlacedStructurePiece> pieces) {
if (pieces == null || pieces.isEmpty()) { if (pieces == null || pieces.isEmpty()) {
return null; return null;
@@ -548,4 +606,66 @@ public class IrisStructureComponent extends IrisMantleComponent {
} }
return max; return max;
} }
static final class ObjectMarkerBounds {
private int minX = Integer.MAX_VALUE;
private int minY = Integer.MAX_VALUE;
private int minZ = Integer.MAX_VALUE;
private int maxX = Integer.MIN_VALUE;
private int maxY = Integer.MIN_VALUE;
private int maxZ = Integer.MIN_VALUE;
void include(int x, int y, int z) {
minX = Math.min(minX, x);
minY = Math.min(minY, y);
minZ = Math.min(minZ, z);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
maxZ = Math.max(maxZ, z);
}
ObjectMarkerBounds expand(int horizontal, int vertical, int worldHeight) {
ObjectMarkerBounds expanded = new ObjectMarkerBounds();
expanded.minX = minX - horizontal;
expanded.minY = Math.max(0, minY - vertical);
expanded.minZ = minZ - horizontal;
expanded.maxX = maxX + horizontal;
expanded.maxY = Math.min(worldHeight - 1, maxY + vertical);
expanded.maxZ = maxZ + horizontal;
return expanded;
}
boolean contains(int x, int y, int z) {
return x >= minX && x <= maxX
&& y >= minY && y <= maxY
&& z >= minZ && z <= maxZ;
}
int minX() {
return minX;
}
int minY() {
return minY;
}
int minZ() {
return minZ;
}
int maxX() {
return maxX;
}
int maxY() {
return maxY;
}
int maxZ() {
return maxZ;
}
}
private record ObjectBlockPosition(int x, int y, int z) {
}
} }
@@ -47,6 +47,11 @@ final class StructureFoundationPlanner {
return !carved && mantleY <= terrainHeight; return !carved && mantleY <= terrainHeight;
} }
static boolean isSurfaceSupportBoundary(PlatformBlockState overlay, boolean carved,
int mantleY, int terrainHeight) {
return carved || isGroundSolid(overlay, false, mantleY, terrainHeight);
}
static int fillSupportColumn(int foundationY, int groundY, IntConsumer supportAtY) { static int fillSupportColumn(int foundationY, int groundY, IntConsumer supportAtY) {
if (groundY == NO_GROUND || groundY >= foundationY - 1) { if (groundY == NO_GROUND || groundY >= foundationY - 1) {
return 0; return 0;
@@ -58,4 +58,26 @@ public class IrisStructureComponentOverboreTest {
"@iris-structure:v1:dHJlZXMvb2Fr:42:bWluZWNyYWZ0Om1hbnNpb24")); "@iris-structure:v1:dHJlZXMvb2Fr:42:bWluZWNyYWZ0Om1hbnNpb24"));
assertFalse(IrisStructureComponent.isOrdinaryObjectMarker("not-a-marker")); assertFalse(IrisStructureComponent.isOrdinaryObjectMarker("not-a-marker"));
} }
@Test
public void collidingObjectSearchCoversTheWholeMarkerOwnedObject() {
IrisStructureComponent.ObjectMarkerBounds bounds = IrisStructureComponent.markerSearchBounds(
20, 64, -8, 21, 66, -7, 7, 12, 384);
assertEquals(14, bounds.minX());
assertEquals(53, bounds.minY());
assertEquals(-14, bounds.minZ());
assertEquals(27, bounds.maxX());
assertEquals(77, bounds.maxY());
assertEquals(-1, bounds.maxZ());
}
@Test
public void collidingObjectSearchClampsToTheWorldHeight() {
IrisStructureComponent.ObjectMarkerBounds bounds = IrisStructureComponent.markerSearchBounds(
0, 2, 0, 0, 379, 0, 1, 12, 384);
assertEquals(0, bounds.minY());
assertEquals(383, bounds.maxY());
}
} }
@@ -59,6 +59,19 @@ public class StructureFoundationPlannerTest {
assertTrue(StructureFoundationPlanner.isGroundSolid(null, true, 0, -20)); assertTrue(StructureFoundationPlanner.isGroundSolid(null, true, 0, -20));
} }
@Test
public void surfaceSupportStopsAtCarvedCaveBoundary() {
PlatformBlockState solid = mock(PlatformBlockState.class);
when(solid.isSolid()).thenReturn(true);
PlatformBlockState air = mock(PlatformBlockState.class);
when(air.isSolid()).thenReturn(false);
assertTrue(StructureFoundationPlanner.isSurfaceSupportBoundary(null, true, 18, 24));
assertTrue(StructureFoundationPlanner.isSurfaceSupportBoundary(solid, false, 30, 24));
assertFalse(StructureFoundationPlanner.isSurfaceSupportBoundary(air, false, 30, 24));
assertTrue(StructureFoundationPlanner.isSurfaceSupportBoundary(null, false, 24, 24));
}
@Test @Test
public void fillsOnlyTheGapBetweenTheFoundationAndGround() { public void fillsOnlyTheGapBetweenTheFoundationAndGround() {
List<Integer> written = new ArrayList<>(); List<Integer> written = new ArrayList<>();