diff --git a/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/nativegen/NativeStructurePostProcessorSurfaceTerrainTest.java b/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/nativegen/NativeStructurePostProcessorSurfaceTerrainTest.java index 07eb4ba53..d111389f1 100644 --- a/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/nativegen/NativeStructurePostProcessorSurfaceTerrainTest.java +++ b/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/nativegen/NativeStructurePostProcessorSurfaceTerrainTest.java @@ -145,6 +145,62 @@ public class NativeStructurePostProcessorSurfaceTerrainTest { } } + @Test + public void flattenBlendsAnElevatedRoadJunctionWithNearbyFoundations() { + List anchors = new ArrayList<>(); + for (int x : List.of(-3, 3)) { + for (int z = -1; z <= 1; z++) { + anchors.add(new NativeStructureSurfaceFitter.FlattenAnchor( + new NativeStructureSurfaceFitter.SurfaceAnchor(x, x, z, z, 64, 2), 24, 64)); + } + } + anchors.add(new NativeStructureSurfaceFitter.FlattenAnchor( + new NativeStructureSurfaceFitter.SurfaceAnchor(0, 0, 0, 0, 105, 1), 24, 64)); + for (int x = -1; x <= 1; x++) { + for (int z = -1; z <= 1; z++) { + NativeStructureSurfaceFitter.FlattenResolution grade = + NativeStructureSurfaceFitter.resolveFlattenSurface(anchors, x, z, 96); + assertTrue(grade.targetY() >= 64 && grade.targetY() <= 72); + assertEquals(grade, NativeStructureSurfaceFitter.resolveFlattenSurface( + anchors.reversed(), x, z, 96)); + if (x < 1) { + assertTrue(Math.abs(grade.targetY() - NativeStructureSurfaceFitter.resolveFlattenSurface( + anchors, x + 1, z, 96).targetY()) <= 1); + } + if (z < 1) { + assertTrue(Math.abs(grade.targetY() - NativeStructureSurfaceFitter.resolveFlattenSurface( + anchors, x, z + 1, 96).targetY()) <= 1); + } + } + } + } + + @Test + public void flattenPreservesAnExactRigidFloorBesideAnElevatedJunction() { + NativeStructureSurfaceFitter.FlattenAnchor rigid = new NativeStructureSurfaceFitter.FlattenAnchor( + new NativeStructureSurfaceFitter.SurfaceAnchor(0, 0, 0, 0, 64, 2), 24, 64); + NativeStructureSurfaceFitter.FlattenAnchor junction = new NativeStructureSurfaceFitter.FlattenAnchor( + new NativeStructureSurfaceFitter.SurfaceAnchor(0, 0, 0, 0, 105, 1), 24, 64); + assertEquals(64, NativeStructureSurfaceFitter.resolveFlattenSurface( + List.of(rigid, junction), 0, 0, 96).targetY()); + assertEquals(64, NativeStructureSurfaceFitter.resolveFlattenSurface( + List.of(junction, rigid), 0, 0, 96).targetY()); + } + + @Test + public void flattenRetainsIsolatedJunctionSupportWithinItsConfiguredBounds() { + for (int radius : List.of(0, 24)) { + NativeStructureSurfaceFitter.FlattenAnchor junction = new NativeStructureSurfaceFitter.FlattenAnchor( + new NativeStructureSurfaceFitter.SurfaceAnchor(0, 0, 0, 0, 105, 1), radius, 8); + List anchors = List.of(junction); + assertEquals(104, NativeStructureSurfaceFitter.resolveFlattenSurface(anchors, 0, 0, 96).targetY()); + assertEquals(8, NativeStructureSurfaceFitter.resolveFlattenSurface(anchors, 0, 0, 96).range()); + int outside = Math.max(1, radius); + assertEquals(96, NativeStructureSurfaceFitter.resolveFlattenSurface(anchors, outside, 0, 96).targetY()); + assertEquals(0, NativeStructureSurfaceFitter.resolveFlattenSurface(anchors, outside, 0, 96).range()); + } + } + @Test public void flattenLevelsNonJigsawFoundationsAndRemovesDetachedRoofs() { StructureStart start = desertStart(); diff --git a/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/nativegen/NativeStructurePostProcessorVegetationTest.java b/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/nativegen/NativeStructurePostProcessorVegetationTest.java index 0c464fc4d..08014b250 100644 --- a/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/nativegen/NativeStructurePostProcessorVegetationTest.java +++ b/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/nativegen/NativeStructurePostProcessorVegetationTest.java @@ -188,6 +188,69 @@ public class NativeStructurePostProcessorVegetationTest { bounds, 0, 58, 320, (x, z) -> x == 1 ? 76 : 100)); } + @Test + public void loweringSurfaceRemovesCactusStacksAndTheirFlowers() { + ProtoChunk chunk = chunk(); + for (int y = 80; y <= 102; y++) { + write(chunk, 4, y, 4, Blocks.SAND.defaultBlockState()); + } + for (int y = 103; y <= 105; y++) { + write(chunk, 4, y, 4, Blocks.CACTUS.defaultBlockState()); + } + write(chunk, 4, 106, 4, Blocks.CACTUS_FLOWER.defaultBlockState()); + + NativeStructureSurfaceFitter.applySurfaceColumn( + world(chunk), new BlockPos.MutableBlockPos(), 4, 4, 102, 93, MIN_Y, MAX_Y); + + assertEquals(Blocks.SAND.defaultBlockState(), chunk.getBlockState(new BlockPos(4, 93, 4))); + for (int y = 94; y <= 106; y++) { + assertTrue("Unsupported decoration at " + y, chunk.getBlockState(new BlockPos(4, y, 4)).isAir()); + } + } + + @Test + public void loweringSurfacePreservesCactusOnUnchangedColumns() { + ProtoChunk chunk = chunk(); + for (int x : List.of(4, 5)) { + for (int y = 60; y <= 64; y++) { + write(chunk, x, y, 4, Blocks.SAND.defaultBlockState()); + } + write(chunk, x, 65, 4, Blocks.CACTUS.defaultBlockState()); + write(chunk, x, 66, 4, Blocks.CACTUS_FLOWER.defaultBlockState()); + } + WorldGenLevel world = world(chunk); + + NativeStructureSurfaceFitter.applySurfaceColumn( + world, new BlockPos.MutableBlockPos(), 4, 4, 64, 60, MIN_Y, MAX_Y); + NativeStructureSurfaceFitter.applySurfaceColumn( + world, new BlockPos.MutableBlockPos(), 5, 4, 64, 64, MIN_Y, MAX_Y); + + assertTrue(chunk.getBlockState(new BlockPos(4, 65, 4)).isAir()); + assertTrue(chunk.getBlockState(new BlockPos(4, 66, 4)).isAir()); + assertEquals(Blocks.SAND.defaultBlockState(), chunk.getBlockState(new BlockPos(5, 64, 4))); + assertEquals(Blocks.CACTUS.defaultBlockState(), chunk.getBlockState(new BlockPos(5, 65, 4))); + assertEquals(Blocks.CACTUS_FLOWER.defaultBlockState(), chunk.getBlockState(new BlockPos(5, 66, 4))); + } + + @Test + public void loweringSurfaceKeepsTreeAndSolidObjectBoundaries() { + for (BlockState boundary : List.of(Blocks.OAK_LOG.defaultBlockState(), Blocks.OAK_PLANKS.defaultBlockState())) { + ProtoChunk chunk = chunk(); + write(chunk, 4, 63, 4, Blocks.SAND.defaultBlockState()); + write(chunk, 4, 64, 4, Blocks.SAND.defaultBlockState()); + write(chunk, 4, 65, 4, boundary); + write(chunk, 4, 66, 4, Blocks.CACTUS.defaultBlockState()); + write(chunk, 4, 67, 4, Blocks.CACTUS_FLOWER.defaultBlockState()); + + NativeStructureSurfaceFitter.applySurfaceColumn( + world(chunk), new BlockPos.MutableBlockPos(), 4, 4, 64, 60, MIN_Y, MAX_Y); + + assertEquals(boundary, chunk.getBlockState(new BlockPos(4, 65, 4))); + assertEquals(Blocks.CACTUS.defaultBlockState(), chunk.getBlockState(new BlockPos(4, 66, 4))); + assertEquals(Blocks.CACTUS_FLOWER.defaultBlockState(), chunk.getBlockState(new BlockPos(4, 67, 4))); + } + } + private static void clearVegetation(ProtoChunk chunk, StructureStart start) { NativeStructureVegetationClearer.clearIntersectingVegetation( world(chunk), chunk, AREA, List.of(start)); diff --git a/adapters/minecraft-common/src/main/java/art/arcane/iris/nativegen/NativeStructureSurfaceFitter.java b/adapters/minecraft-common/src/main/java/art/arcane/iris/nativegen/NativeStructureSurfaceFitter.java index d4cc7ebab..7e7feb5c2 100644 --- a/adapters/minecraft-common/src/main/java/art/arcane/iris/nativegen/NativeStructureSurfaceFitter.java +++ b/adapters/minecraft-common/src/main/java/art/arcane/iris/nativegen/NativeStructureSurfaceFitter.java @@ -727,17 +727,18 @@ public final class NativeStructureSurfaceFitter { SurfaceAnchor source = anchor.source(); int outX = IrisObjectVacuum.outset(x, source.minX(), source.maxX()); int outZ = IrisObjectVacuum.outset(z, source.minZ(), source.maxZ()); - if (outX == 0 && outZ == 0) { + boolean containsColumn = outX == 0 && outZ == 0; + if (containsColumn && source.strength() > 1) { if (selected == null || precedes(source, selected.source())) { selected = anchor; } continue; } double distance = Math.sqrt((double) outX * outX + (double) outZ * outZ); - if (anchor.radius() == 0 || distance >= anchor.radius()) { + if (!containsColumn && (anchor.radius() == 0 || distance >= anchor.radius())) { continue; } - double progress = distance / anchor.radius(); + double progress = containsColumn ? 0D : distance / anchor.radius(); double factor = 1D - progress * progress * (3D - 2D * progress); long influence = Math.round(factor * SURFACE_TERRAIN_INFLUENCE_SCALE); long weight = influence * source.strength(); @@ -1062,7 +1063,8 @@ public final class NativeStructureSurfaceFitter { } return fluid; } - if (state.isSolid() || NativeStructureVegetationClearer.isTreeBlock(state)) { + if (state.isSolid() && !state.is(Blocks.CACTUS) + || NativeStructureVegetationClearer.isTreeBlock(state)) { return air; } world.setBlock(position, air, 2);