This commit is contained in:
Brian Neumann-Fopiano
2026-08-05 14:30:07 -06:00
parent a66cba6418
commit 70d355621c
26 changed files with 1719 additions and 35 deletions
@@ -606,7 +606,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
world, area, terrainTargets, this::resolvePaletteBlock);
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"terrain carving", nativeStructureBatchContext(placementGroups),
"terrain preparation", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
for (NativePlacementGroup group : placementGroups) {
@@ -44,6 +44,8 @@ public class IrisChunkGeneratorFailureContractTest {
assertFalse(adjustment.contains("IrisLogging.reportError"));
assertTrue(placement.contains("\"resolution\""));
assertTrue(placement.contains("\"terrain integration\""));
assertTrue(placement.contains("\"terrain preparation\""));
assertFalse(placement.contains("\"terrain carving\""));
assertTrue(placement.contains("\"vegetation cleanup\""));
assertTrue(placement.contains("\"placement\""));
assertTrue(placement.contains("because structure generation is disabled outside the pack"));
@@ -16,6 +16,7 @@ import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.levelgen.LegacyRandomSource;
import net.minecraft.world.level.levelgen.RandomSupport;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
@@ -31,6 +32,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class NativeStructureOwnershipRecoveryTest {
@@ -138,6 +140,110 @@ public class NativeStructureOwnershipRecoveryTest {
assertFalse(NativeStructureOwnershipFingerprint.matches(recovered, moved));
}
@Test
public void staleVacuumEnvelopeRefreshesWithoutReplacingOwnershipIdentity() {
String structureKey = "minecraft:monument";
long seed = 648231L;
ChunkPos origin = new ChunkPos(5, -6);
OceanMonumentStructure structure = structure();
StructureStart start = monumentStart(structure, origin, seed);
NativeStructureStartPlan plan = plan(
origin, "vacuum-envelope-refresh", IrisStructureTerrainMode.VACUUM, 0);
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(start);
NativeStructureOwnershipRecord stale = NativeStructureOwnershipFingerprint.capture(
structureKey, start, plan, content);
NativeStructureOwnershipRecord refreshed =
NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
structureKey, structure, start, stale);
assertNotNull(refreshed);
assertNotEquals(stale, refreshed);
assertEquals(stale.schema(), refreshed.schema());
assertEquals(stale.ownershipKey(), refreshed.ownershipKey());
assertEquals(stale.placementIdentity(), refreshed.placementIdentity());
assertEquals(stale.baseY(), refreshed.baseY());
assertEquals(stale.locatorY(), refreshed.locatorY());
assertEquals(stale.contentFingerprint(), refreshed.contentFingerprint());
assertEquals(stale.decision(), refreshed.decision());
assertEquals(IrisStructureTerrainMode.VACUUM,
refreshed.restoredDecision().terrain().resolvedMode());
BoundingBox expected = NativeStructureReferenceEnvelope.referenceBounds(
start, structure, plan.placement().resolvedTerrain(), structureKey);
assertEquals(expected.minX() >> 4, refreshed.referenceMinChunkX());
assertEquals(expected.maxX() >> 4, refreshed.referenceMaxChunkX());
assertEquals(expected.minZ() >> 4, refreshed.referenceMinChunkZ());
assertEquals(expected.maxZ() >> 4, refreshed.referenceMaxChunkZ());
assertTrue(hasExpandedCoverage(stale, refreshed));
assertSame(refreshed, NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
structureKey, structure, start, refreshed));
}
@Test
public void staleEnvelopeCannotRefreshAgainstDifferentContent() {
String structureKey = "minecraft:monument";
long seed = 412987L;
ChunkPos origin = new ChunkPos(-2, 7);
OceanMonumentStructure structure = structure();
StructureStart expected = monumentStart(structure, origin, seed);
NativeStructureStartPlan plan = plan(
origin, "vacuum-content-check", IrisStructureTerrainMode.VACUUM, 0);
NativeStructureOwnershipRecord stale = NativeStructureOwnershipFingerprint.capture(
structureKey, expected, plan,
NativeStructureReferenceEnvelope.contentBounds(expected));
StructureStart moved = monumentStart(structure, origin, seed);
for (StructurePiece piece : moved.getPieces()) {
piece.move(1, 0, 0);
}
assertNull(NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
structureKey, structure, moved, stale));
}
@Test
public void currentNonVacuumEnvelopeRemainsThePersistedAuthority() {
String structureKey = "minecraft:monument";
long seed = 927451L;
ChunkPos origin = new ChunkPos(3, 8);
OceanMonumentStructure structure = structure();
StructureStart start = monumentStart(structure, origin, seed);
NativeStructureStartPlan plan = plan(origin, "current-force-carve", 24);
BoundingBox envelope = NativeStructureReferenceEnvelope.referenceBounds(
start, structure, plan.placement().resolvedTerrain(), structureKey);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
structureKey, start, plan, envelope);
assertSame(ownership, NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
structureKey, structure, start, ownership));
}
@Test
public void clippedVacuumEnvelopeRemainsStableAtTheReferenceLimit() {
String structureKey = "minecraft:monument";
long seed = 381729L;
ChunkPos origin = new ChunkPos(0, 0);
OceanMonumentStructure structure = structure();
StructureStart start = monumentStart(structure, origin, seed);
BoundingBox initial = NativeStructureReferenceEnvelope.contentBounds(start);
int maximumReferenceBlockX = ((origin.x()
+ NativeStructureOwnershipRecord.MAX_REFERENCE_DISTANCE_CHUNKS) << 4) + 15;
int shiftX = maximumReferenceBlockX - initial.maxX();
for (StructurePiece piece : start.getPieces()) {
piece.move(shiftX, 0, 0);
}
NativeStructureStartPlan plan = plan(
origin, "clipped-vacuum", IrisStructureTerrainMode.VACUUM, 0);
BoundingBox envelope = NativeStructureReferenceEnvelope.referenceBounds(
start, structure, plan.placement().resolvedTerrain(), structureKey);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
structureKey, start, plan, envelope);
assertEquals(origin.x() + NativeStructureOwnershipRecord.MAX_REFERENCE_DISTANCE_CHUNKS,
ownership.referenceMaxChunkX());
assertSame(ownership, NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
structureKey, structure, start, ownership));
}
private static OceanMonumentStructure structure() {
return new OceanMonumentStructure(
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
@@ -163,11 +269,19 @@ public class NativeStructureOwnershipRecoveryTest {
private static NativeStructureStartPlan plan(ChunkPos origin,
String placementId,
int horizontalPadding) {
return plan(origin, placementId,
IrisStructureTerrainMode.FORCE_CARVE, horizontalPadding);
}
private static NativeStructureStartPlan plan(ChunkPos origin,
String placementId,
IrisStructureTerrainMode terrainMode,
int horizontalPadding) {
IrisNativeStructure source = new IrisNativeStructure()
.setStructure("minecraft:monument")
.setWeight(1);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setMode(terrainMode)
.setHorizontalPadding(horizontalPadding);
IrisStructurePlacement placement = new IrisStructurePlacement()
.setPlacementId(placementId)
@@ -182,4 +296,19 @@ public class NativeStructureOwnershipRecoveryTest {
monumentStart(structure(), origin, 1L)).minY()
);
}
private static boolean hasExpandedCoverage(
NativeStructureOwnershipRecord stale,
NativeStructureOwnershipRecord refreshed) {
for (int chunkX = refreshed.referenceMinChunkX();
chunkX <= refreshed.referenceMaxChunkX(); chunkX++) {
for (int chunkZ = refreshed.referenceMinChunkZ();
chunkZ <= refreshed.referenceMaxChunkZ(); chunkZ++) {
if (!stale.covers(chunkX, chunkZ)) {
return true;
}
}
}
return false;
}
}
@@ -6,14 +6,18 @@ import art.arcane.iris.engine.object.IrisStructureTerrain;
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.IdMapper;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
@@ -36,13 +40,18 @@ import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.pools.LegacySinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.ListPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockIgnoreProcessor;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.junit.BeforeClass;
@@ -55,8 +64,11 @@ import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -118,6 +130,34 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.PRESERVE))));
}
@Test
public void explicitVacuumForcesThinSurfaceFittingWithoutAuthoredAdaptation() {
StructureStart none = desertStart(TerrainAdjustment.NONE);
StructureStart box = desertStart(TerrainAdjustment.BEARD_BOX);
NativeStructureTerrainIntegrator.TerrainTarget sourceNone =
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:none", none,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE));
NativeStructureTerrainIntegrator.TerrainTarget vacuumNone =
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:none", none,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.VACUUM));
NativeStructureTerrainIntegrator.TerrainTarget vacuumBox =
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:box", box,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.VACUUM));
assertFalse(NativeStructureSurfaceFitter.requiresSurfaceTerrain(sourceNone));
assertTrue(NativeStructureSurfaceFitter.requiresSurfaceTerrain(vacuumNone));
assertTrue(NativeStructureSurfaceFitter.requiresSurfaceTerrain(vacuumBox));
assertEquals(TerrainAdjustment.BEARD_THIN,
NativeStructureSurfaceFitter.effectiveSurfaceAdjustment(vacuumNone));
assertEquals(TerrainAdjustment.BEARD_THIN,
NativeStructureSurfaceFitter.effectiveSurfaceAdjustment(vacuumBox));
assertTrue(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
none, vacuumNone.terrain()));
}
@Test
public void beardBoxUsesTheFullRigidHeightWhileBeardThinUsesTheGroundPlane() {
NativeStructureSurfaceFitter.SurfaceAnchor thin =
@@ -210,11 +250,12 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
}
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(new NativeStructureTerrainIntegrator.TerrainTarget(
List<NativeStructureTerrainIntegrator.TerrainTarget> targets = List.of(
new NativeStructureTerrainIntegrator.TerrainTarget(
"nova_structures:tavern_oak", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))),
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE)));
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area, targets,
(x, z) -> 64);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 1, 71, 1));
@@ -263,6 +304,392 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
List.of(strongTie, weakTie), 2, 2, 64));
}
@Test
public void postClearSupportUsesUpperOccupancyOverLowerLegacyAir() throws Exception {
StructureTemplate lowerTemplate = template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState()),
block(1, 0, 0, Blocks.AIR.defaultBlockState())));
StructureTemplate upperTemplate = template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState()),
block(1, 0, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece lower = rigidTemplatePiece(
new InlineLegacyPoolElement(lowerTemplate),
new BoundingBox(0, 62, 0, 1, 65, 0), 1, Rotation.NONE);
PoolElementStructurePiece upper = rigidTemplatePiece(
new InlineLegacyPoolElement(upperTemplate),
new BoundingBox(0, 64, 0, 1, 70, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(lower, upper));
BoundingBox area = new BoundingBox(0, 58, 0, 1, 72, 0);
Map<BlockPos, BlockState> blocks = new HashMap<>();
for (int x = 0; x <= 1; x++) {
put(blocks, x, 61, 0, Blocks.DIRT.defaultBlockState());
put(blocks, x, 62, 0, Blocks.GRASS_BLOCK.defaultBlockState());
}
put(blocks, 1, 63, 0, Blocks.DIRT.defaultBlockState());
Set<Long> written = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(blocks), area, List.of(surfaceTarget(start)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertEquals(Set.of(BlockPos.asLong(0, 63, 0)), written);
assertEquals(Blocks.DIRT.defaultBlockState(), state(blocks, 0, 63, 0));
assertEquals(Blocks.DIRT.defaultBlockState(), state(blocks, 1, 63, 0));
}
@Test
public void rotatedLowestAuthoredVoidFluidAirAndJigsawColumnsRemainOpen() throws Exception {
List<StructureTemplate.StructureBlockInfo> upperBlocks = new ArrayList<>();
upperBlocks.add(block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState()));
upperBlocks.add(block(1, 0, 0, Blocks.AIR.defaultBlockState()));
upperBlocks.add(block(2, 0, 0, Blocks.STRUCTURE_VOID.defaultBlockState()));
upperBlocks.add(block(3, 0, 0, Blocks.WATER.defaultBlockState()));
upperBlocks.add(block(4, 0, 0, Blocks.JIGSAW.defaultBlockState()));
for (int x = 1; x <= 4; x++) {
upperBlocks.add(block(x, 1, 0, Blocks.STONE.defaultBlockState()));
}
StructureTemplate upperTemplate = template(upperBlocks);
PoolElementStructurePiece lower = rigidTemplatePiece(
new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState())))),
new BoundingBox(10, 62, 10, 10, 65, 14), 1, Rotation.NONE);
PoolElementStructurePiece upper = rigidTemplatePiece(
new InlineSinglePoolElement(upperTemplate),
new BoundingBox(10, 64, 10, 10, 70, 14), 1,
Rotation.CLOCKWISE_90);
StructureStart start = rigidSurfaceStart(List.of(lower, upper));
BoundingBox area = new BoundingBox(10, 58, 10, 10, 72, 14);
Map<BlockPos, BlockState> blocks = new HashMap<>();
for (int z = 10; z <= 14; z++) {
put(blocks, 10, 61, z, Blocks.DIRT.defaultBlockState());
put(blocks, 10, 62, z, Blocks.GRASS_BLOCK.defaultBlockState());
}
Set<Long> written = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(blocks), area, List.of(surfaceTarget(start)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
StructurePlaceSettings settings = new StructurePlaceSettings()
.setRotation(Rotation.CLOCKWISE_90);
BlockPos origin = upper.getPosition();
BlockPos supportedBase = origin.offset(
StructureTemplate.calculateRelativePosition(settings, BlockPos.ZERO));
assertEquals(Set.of(supportedBase.below().asLong()), written);
assertEquals(Blocks.DIRT.defaultBlockState(), state(
blocks, supportedBase.getX(), supportedBase.getY() - 1, supportedBase.getZ()));
for (int localX = 1; localX <= 4; localX++) {
BlockPos vetoedBase = origin.offset(StructureTemplate.calculateRelativePosition(
settings, new BlockPos(localX, 0, 0)));
assertEquals(Blocks.AIR.defaultBlockState(), state(
blocks, vetoedBase.getX(), vetoedBase.getY() - 1, vetoedBase.getZ()));
}
}
@Test
public void listElementsUseDeclaredOverlayOrderForLowestAuthoredCells() throws Exception {
InlineLegacyPoolElement legacy = new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState()),
block(1, 0, 0, Blocks.COBBLESTONE.defaultBlockState()))));
InlineSinglePoolElement single = new InlineSinglePoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState()),
block(1, 0, 0, Blocks.STONE.defaultBlockState()))));
ListPoolElement list = new ListPoolElement(
List.of(legacy, single), StructureTemplatePool.Projection.RIGID);
PoolElementStructurePiece lower = rigidTemplatePiece(
new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState())))),
new BoundingBox(0, 62, 0, 1, 65, 0), 1, Rotation.NONE);
PoolElementStructurePiece upper = rigidTemplatePiece(
list, new BoundingBox(0, 64, 0, 1, 70, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(lower, upper));
BoundingBox area = new BoundingBox(0, 58, 0, 1, 72, 0);
Map<BlockPos, BlockState> blocks = new HashMap<>();
for (int x = 0; x <= 1; x++) {
put(blocks, x, 61, 0, Blocks.DIRT.defaultBlockState());
put(blocks, x, 62, 0, Blocks.GRASS_BLOCK.defaultBlockState());
}
Set<Long> written = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(blocks), area, List.of(surfaceTarget(start)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertEquals(Set.of(BlockPos.asLong(1, 63, 0)), written);
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 0, 63, 0));
assertEquals(Blocks.DIRT.defaultBlockState(), state(blocks, 1, 63, 0));
}
@Test
public void surfaceSupportIsChunkClippedAndStableAcrossSplitAreas() throws Exception {
StructureTemplate upperTemplate = template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState()),
block(1, 0, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece lower = rigidTemplatePiece(
new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState())))),
new BoundingBox(15, 62, 0, 16, 65, 0), 1, Rotation.NONE);
PoolElementStructurePiece upper = rigidTemplatePiece(
new InlineSinglePoolElement(upperTemplate),
new BoundingBox(15, 64, 0, 16, 70, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(lower, upper));
BoundingBox wideArea = new BoundingBox(0, 58, 0, 31, 72, 15);
BoundingBox westArea = new BoundingBox(0, 58, 0, 15, 72, 15);
BoundingBox eastArea = new BoundingBox(16, 58, 0, 31, 72, 15);
Map<BlockPos, BlockState> wideBlocks = supportTerrain(15, 16);
Map<BlockPos, BlockState> splitBlocks = supportTerrain(15, 16);
Set<Long> wide = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(wideBlocks), wideArea, List.of(surfaceTarget(start)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
Set<Long> split = new HashSet<>(
NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(splitBlocks), westArea, List.of(surfaceTarget(start)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager));
assertEquals(Blocks.AIR.defaultBlockState(), state(splitBlocks, 16, 63, 0));
split.addAll(NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(splitBlocks), eastArea, List.of(surfaceTarget(start)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager));
assertEquals(Set.of(
BlockPos.asLong(15, 63, 0), BlockPos.asLong(16, 63, 0)), wide);
assertEquals(wide, split);
assertEquals(wideBlocks, splitBlocks);
}
@Test
public void supportNeverPairsRigidAnchorsAcrossStarts() throws Exception {
PoolElementStructurePiece lower = rigidTemplatePiece(
new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState())))),
new BoundingBox(0, 62, 0, 0, 65, 0), 1, Rotation.NONE);
PoolElementStructurePiece upper = rigidTemplatePiece(
new InlineSinglePoolElement(template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(0, 64, 0, 0, 70, 0), 1, Rotation.NONE);
StructureStart lowerStart = rigidSurfaceStart(List.of(lower));
StructureStart upperStart = rigidSurfaceStart(List.of(upper));
BoundingBox area = new BoundingBox(0, 58, 0, 0, 72, 0);
Map<BlockPos, BlockState> blocks = supportTerrain(0, 0);
assertTrue(NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(blocks), area, List.of(surfaceTarget(lowerStart)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager).isEmpty());
assertTrue(NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(blocks), area, List.of(surfaceTarget(upperStart)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager).isEmpty());
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 0, 63, 0));
}
@Test
public void vacuumSupportsUnauthoredTerrainModesButLongMeetGapsRemainOpen() throws Exception {
StructureTemplate upperTemplate = template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece lower = rigidTemplatePiece(
new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState())))),
new BoundingBox(0, 62, 0, 0, 65, 0), 1, Rotation.NONE);
PoolElementStructurePiece upper = rigidTemplatePiece(
new InlineSinglePoolElement(upperTemplate),
new BoundingBox(0, 64, 0, 0, 70, 0), 1, Rotation.NONE);
StructureStart vacuumStart = rigidSurfaceStart(
List.of(lower, upper), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 58, 0, 0, 72, 0);
Map<BlockPos, BlockState> vacuumBlocks = supportTerrain(0, 0);
Set<Long> vacuumWritten = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(vacuumBlocks), area,
List.of(surfaceTarget(vacuumStart, IrisStructureTerrainMode.VACUUM)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertEquals(Set.of(BlockPos.asLong(0, 63, 0)), vacuumWritten);
assertTrue(NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(supportTerrain(0, 0)), area,
List.of(surfaceTarget(vacuumStart, IrisStructureTerrainMode.SOURCE)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager).isEmpty());
PoolElementStructurePiece distantUpper = rigidTemplatePiece(
new InlineSinglePoolElement(upperTemplate),
new BoundingBox(0, 65, 0, 0, 71, 0), 1, Rotation.NONE);
StructureStart distantStart = rigidSurfaceStart(
List.of(lower, distantUpper), TerrainAdjustment.NONE);
assertTrue(NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(supportTerrain(0, 0)), area,
List.of(surfaceTarget(distantStart, IrisStructureTerrainMode.VACUUM)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager).isEmpty());
}
@Test
public void threeRigidPlanesCannotBuildAnUpwardSupportChain() throws Exception {
PoolElementStructurePiece lower = rigidTemplatePiece(
new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState())))),
new BoundingBox(0, 62, 0, 0, 65, 0), 1, Rotation.NONE);
PoolElementStructurePiece middle = rigidTemplatePiece(
new InlineSinglePoolElement(template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(0, 64, 0, 0, 69, 0), 0, Rotation.NONE);
PoolElementStructurePiece upper = rigidTemplatePiece(
new InlineSinglePoolElement(template(List.of(
block(0, 0, 0, Blocks.STONE.defaultBlockState())))),
new BoundingBox(0, 65, 0, 0, 70, 0), 0, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(lower, middle, upper));
BoundingBox area = new BoundingBox(0, 58, 0, 0, 72, 0);
Map<BlockPos, BlockState> blocks = supportTerrain(0, 0);
Set<Long> written = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(blocks), area, List.of(surfaceTarget(start)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertEquals(Set.of(BlockPos.asLong(0, 63, 0)), written);
assertEquals(Blocks.DIRT.defaultBlockState(), state(blocks, 0, 63, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 0, 64, 0));
}
@Test
public void reversingTargetsCannotCreateCrossStartSupportChains() throws Exception {
PoolElementStructurePiece ground = rigidTemplatePiece(
new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState())))),
new BoundingBox(0, 62, 0, 0, 65, 0), 1, Rotation.NONE);
PoolElementStructurePiece middle = rigidTemplatePiece(
new InlineSinglePoolElement(template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(0, 64, 0, 0, 69, 0), 0, Rotation.NONE);
PoolElementStructurePiece raisedGround = rigidTemplatePiece(
new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState())))),
new BoundingBox(0, 64, 0, 0, 69, 0), 0, Rotation.NONE);
PoolElementStructurePiece upper = rigidTemplatePiece(
new InlineSinglePoolElement(template(List.of(
block(0, 0, 0, Blocks.STONE.defaultBlockState())))),
new BoundingBox(0, 65, 0, 0, 70, 0), 0, Rotation.NONE);
StructureStart lowerStart = rigidSurfaceStart(List.of(ground, middle));
StructureStart upperStart = rigidSurfaceStart(List.of(raisedGround, upper));
BoundingBox area = new BoundingBox(0, 58, 0, 0, 72, 0);
Map<BlockPos, BlockState> forwardBlocks = supportTerrain(0, 0);
Map<BlockPos, BlockState> reverseBlocks = supportTerrain(0, 0);
NativeStructureTerrainIntegrator.TerrainTarget lowerTarget = surfaceTarget(lowerStart);
NativeStructureTerrainIntegrator.TerrainTarget upperTarget = surfaceTarget(upperStart);
Set<Long> forward = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(forwardBlocks), area, List.of(lowerTarget, upperTarget),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
Set<Long> reverse = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(reverseBlocks), area, List.of(upperTarget, lowerTarget),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertEquals(Set.of(BlockPos.asLong(0, 63, 0)), forward);
assertEquals(forward, reverse);
assertEquals(forwardBlocks, reverseBlocks);
assertEquals(Blocks.AIR.defaultBlockState(), state(forwardBlocks, 0, 64, 0));
}
@Test
public void preserveDuplicateCannotSuppressVacuumSupport() throws Exception {
PoolElementStructurePiece lower = rigidTemplatePiece(
new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState())))),
new BoundingBox(0, 62, 0, 0, 65, 0), 1, Rotation.NONE);
PoolElementStructurePiece upper = rigidTemplatePiece(
new InlineSinglePoolElement(template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(0, 64, 0, 0, 70, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(
List.of(lower, upper), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 58, 0, 0, 72, 0);
Map<BlockPos, BlockState> blocks = supportTerrain(0, 0);
Set<Long> written = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(blocks), area, List.of(
surfaceTarget(start, IrisStructureTerrainMode.PRESERVE),
surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertEquals(Set.of(BlockPos.asLong(0, 63, 0)), written);
}
@Test
public void processorCreatedAirAndFluidVetoSurfaceSupport() throws Exception {
StructureTemplate upperTemplate = template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState()),
block(1, 0, 0, Blocks.STONE.defaultBlockState())));
InlineSinglePoolElement upperElement = new InlineSinglePoolElement(
upperTemplate, List.of(
new ReplaceBlockProcessor(
Blocks.COBBLESTONE.defaultBlockState(),
Blocks.AIR.defaultBlockState()),
new ReplaceBlockProcessor(
Blocks.STONE.defaultBlockState(),
Blocks.WATER.defaultBlockState())));
PoolElementStructurePiece lower = rigidTemplatePiece(
new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState())))),
new BoundingBox(0, 62, 0, 1, 65, 0), 1, Rotation.NONE);
PoolElementStructurePiece upper = rigidTemplatePiece(
upperElement, new BoundingBox(0, 64, 0, 1, 70, 0),
1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(lower, upper));
BoundingBox area = new BoundingBox(0, 58, 0, 1, 72, 0);
Map<BlockPos, BlockState> blocks = supportTerrain(0, 1);
Set<Long> written = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(blocks), area, List.of(surfaceTarget(start)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertTrue(written.isEmpty());
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 0, 63, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 1, 63, 0));
}
@Test
public void solidJigsawFinalStateCreatesSurfaceSupport() throws Exception {
CompoundTag jigsawData = new CompoundTag();
jigsawData.putString("final_state", "minecraft:cobblestone");
StructureTemplate upperTemplate = template(List.of(
new StructureTemplate.StructureBlockInfo(
BlockPos.ZERO, Blocks.JIGSAW.defaultBlockState(), jigsawData)));
PoolElementStructurePiece lower = rigidTemplatePiece(
new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState())))),
new BoundingBox(0, 62, 0, 0, 65, 0), 1, Rotation.NONE);
PoolElementStructurePiece upper = rigidTemplatePiece(
new InlineSinglePoolElement(upperTemplate),
new BoundingBox(0, 64, 0, 0, 70, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(lower, upper));
BoundingBox area = new BoundingBox(0, 58, 0, 0, 72, 0);
Map<BlockPos, BlockState> blocks = supportTerrain(0, 0);
Set<Long> written = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(blocks), area, List.of(surfaceTarget(start)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertEquals(Set.of(BlockPos.asLong(0, 63, 0)), written);
assertEquals(Blocks.DIRT.defaultBlockState(), state(blocks, 0, 63, 0));
}
@Test
public void laterLegacyAirDoesNotOverlayEarlierListSolid() throws Exception {
InlineSinglePoolElement solid = new InlineSinglePoolElement(template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState()))));
InlineLegacyPoolElement legacyAir = new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState()))));
ListPoolElement list = new ListPoolElement(
List.of(solid, legacyAir), StructureTemplatePool.Projection.RIGID);
PoolElementStructurePiece lower = rigidTemplatePiece(
new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState())))),
new BoundingBox(0, 62, 0, 0, 65, 0), 1, Rotation.NONE);
PoolElementStructurePiece upper = rigidTemplatePiece(
list, new BoundingBox(0, 64, 0, 0, 70, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(lower, upper));
BoundingBox area = new BoundingBox(0, 58, 0, 0, 72, 0);
Map<BlockPos, BlockState> blocks = supportTerrain(0, 0);
Set<Long> written = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(blocks), area, List.of(surfaceTarget(start)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertEquals(Set.of(BlockPos.asLong(0, 63, 0)), written);
}
@Test
public void stackedRigidPiecesPreserveTheLowerAuthoredSurface() {
Structure structure = new DesertPyramidStructure(
@@ -286,12 +713,11 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
}
WorldGenLevel world = world(blocks);
List<NativeStructureTerrainIntegrator.TerrainTarget> targets =
List.of(surfaceTarget(start));
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(new NativeStructureTerrainIntegrator.TerrainTarget(
"nova_structures:tavern_oak", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))),
(x, z) -> 60);
world, area, targets, (x, z) -> 60);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 2, 62, 2));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 2, 65, 2));
@@ -519,7 +945,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
@Test
public void nativeVacuumClearsEveryPieceEnvelopeBeforePlacement() {
public void nativeVacuumLeavesPieceBlocksForSurfaceFitting() {
StructureStart start = desertStart();
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
Map<BlockPos, BlockState> blocks = new HashMap<>();
@@ -529,7 +955,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
world(blocks), bounds, "minecraft:desert_pyramid", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.VACUUM), null);
assertEquals(Blocks.AIR.defaultBlockState(),
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX(), bounds.minY(), bounds.minZ()));
}
@@ -892,6 +1318,27 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
assertEquals(generated.getPieces().size(), wrapped.getPieces().size());
}
@Test
public void nativeVacuumReservesItsFixedSurfaceFalloff() {
StructureStart generated = desertStart(TerrainAdjustment.NONE);
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.VACUUM)
.setHorizontalPadding(64);
BoundingBox references = NativeStructureReferenceEnvelope.referenceBounds(
generated, generated.getStructure(), terrain);
assertEquals(content.minX() - NativeStructureSurfaceFitter.surfaceTerrainRadius(),
references.minX());
assertEquals(content.maxX() + NativeStructureSurfaceFitter.surfaceTerrainRadius(),
references.maxX());
assertEquals(content.minZ() - NativeStructureSurfaceFitter.surfaceTerrainRadius(),
references.minZ());
assertEquals(content.maxZ() + NativeStructureSurfaceFitter.surfaceTerrainRadius(),
references.maxZ());
}
@Test
public void nativeTerrainEnvelopeClipsOptionalCoverageWithoutDroppingContent() {
StructureStart generated = desertStart();
@@ -999,7 +1446,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
BlockState log = Blocks.OAK_LOG.defaultBlockState();
blocks.put(origin, log);
NativeStructureTerrainIntegrator.clearTemplateAir(world(blocks), template, origin, 80, settings);
NativeStructureTerrainIntegrator.clearTemplateAir(
world(blocks), template, origin, 80, settings);
assertEquals(log, blocks.get(origin));
}
@@ -1018,6 +1466,71 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
LiquidSettings.APPLY_WATERLOGGING);
}
private static PoolElementStructurePiece rigidTemplatePiece(
StructurePoolElement element, BoundingBox bounds,
int groundLevelDelta, Rotation rotation) {
return new PoolElementStructurePiece(
null, element,
new BlockPos(bounds.minX(), bounds.minY(), bounds.minZ()),
groundLevelDelta, rotation, bounds,
LiquidSettings.APPLY_WATERLOGGING);
}
private static StructureStart rigidSurfaceStart(List<PoolElementStructurePiece> pieces) {
return rigidSurfaceStart(pieces, TerrainAdjustment.BEARD_BOX);
}
private static StructureStart rigidSurfaceStart(
List<PoolElementStructurePiece> pieces, TerrainAdjustment adjustment) {
Structure structure = new DesertPyramidStructure(
new Structure.StructureSettings(
HolderSet.empty(), Map.of(),
GenerationStep.Decoration.SURFACE_STRUCTURES,
adjustment));
List<StructurePiece> structurePieces = new ArrayList<>(pieces);
return new StructureStart(
structure, new ChunkPos(0, 0), 0,
new PiecesContainer(structurePieces));
}
private static NativeStructureTerrainIntegrator.TerrainTarget surfaceTarget(
StructureStart start) {
return surfaceTarget(start, IrisStructureTerrainMode.SOURCE);
}
private static NativeStructureTerrainIntegrator.TerrainTarget surfaceTarget(
StructureStart start, IrisStructureTerrainMode mode) {
return new NativeStructureTerrainIntegrator.TerrainTarget(
"nova_structures:tavern_oak", start,
new IrisStructureTerrain().setMode(mode));
}
private static StructureTemplate.StructureBlockInfo block(
int x, int y, int z, BlockState state) {
return new StructureTemplate.StructureBlockInfo(
new BlockPos(x, y, z), state, null);
}
private static Map<BlockPos, BlockState> supportTerrain(int minimumX, int maximumX) {
Map<BlockPos, BlockState> blocks = new HashMap<>();
for (int x = minimumX; x <= maximumX; x++) {
put(blocks, x, 61, 0, Blocks.DIRT.defaultBlockState());
put(blocks, x, 62, 0, Blocks.GRASS_BLOCK.defaultBlockState());
}
return blocks;
}
private static Map<BlockPos, BlockState> flatTerrain(BoundingBox area, int surfaceY) {
Map<BlockPos, BlockState> blocks = new HashMap<>();
for (int x = area.minX(); x <= area.maxX(); x++) {
for (int z = area.minZ(); z <= area.maxZ(); z++) {
put(blocks, x, surfaceY - 1, z, Blocks.DIRT.defaultBlockState());
put(blocks, x, surfaceY, z, Blocks.GRASS_BLOCK.defaultBlockState());
}
}
return blocks;
}
private static NativeStructureTerrainIntegrator.OrganicCarve organicCarve(
StructureStart start, int horizontalPadding) {
return organicCarve(start, horizontalPadding, 0.85D);
@@ -1122,6 +1635,57 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
return template;
}
private static final class InlineSinglePoolElement extends SinglePoolElement {
private InlineSinglePoolElement(StructureTemplate template) {
this(template, List.of());
}
private InlineSinglePoolElement(
StructureTemplate template, List<StructureProcessor> processors) {
super(Either.right(template),
Holder.direct(new StructureProcessorList(processors)),
StructureTemplatePool.Projection.RIGID,
Optional.<LiquidSettings>empty());
}
}
private static final class InlineLegacyPoolElement extends LegacySinglePoolElement {
private InlineLegacyPoolElement(StructureTemplate template) {
super(Either.right(template),
Holder.direct(new StructureProcessorList(List.of())),
StructureTemplatePool.Projection.RIGID,
Optional.<LiquidSettings>empty());
}
}
private static final class ReplaceBlockProcessor implements StructureProcessor {
private final BlockState source;
private final BlockState replacement;
private ReplaceBlockProcessor(BlockState source, BlockState replacement) {
this.source = source;
this.replacement = replacement;
}
@Override
public StructureTemplate.StructureBlockInfo processBlock(
LevelReader level, BlockPos targetPosition, BlockPos referencePos,
BlockPos templateRelativePos,
StructureTemplate.StructureBlockInfo processedBlockInfo,
StructurePlaceSettings settings) {
if (!processedBlockInfo.state().equals(source)) {
return processedBlockInfo;
}
return new StructureTemplate.StructureBlockInfo(
processedBlockInfo.pos(), replacement, processedBlockInfo.nbt());
}
@Override
public MapCodec<? extends StructureProcessor> codec() {
return BlockIgnoreProcessor.STRUCTURE_BLOCK.codec();
}
}
private static StructureStart desertStart() {
return desertStart(TerrainAdjustment.NONE);
}
@@ -1155,6 +1719,9 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
if (methodName.equals("getLevel")) {
return null;
}
if (methodName.equals("holderLookup")) {
return BuiltInRegistries.BLOCK;
}
if (methodName.equals("hashCode")) {
return System.identityHashCode(proxy);
}
@@ -1187,6 +1754,9 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
if (methodName.equals("getLevel")) {
return null;
}
if (methodName.equals("holderLookup")) {
return BuiltInRegistries.BLOCK;
}
if (methodName.equals("hashCode")) {
return System.identityHashCode(proxy);
}
@@ -7,6 +7,8 @@ import art.arcane.iris.engine.framework.NativeStructureOwnershipStore;
import art.arcane.iris.engine.framework.NativeStructurePlacementPlanner;
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
import art.arcane.iris.engine.framework.StructurePlacementGrid;
import art.arcane.iris.engine.object.IrisStructureTerrain;
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
import art.arcane.iris.engine.object.NativeStructureSuppression;
import net.minecraft.core.Holder;
@@ -44,8 +46,13 @@ public final class NativeStructureOwnershipRecovery {
NativeStructureOwnershipRecord persisted = NativeStructureOwnershipStore.findPersisted(
engine, structureKey, origin.x(), origin.z());
if (persisted != null) {
if (NativeStructureOwnershipFingerprint.matches(persisted, start)) {
return persisted;
NativeStructureOwnershipRecord refreshed = refreshReferenceEnvelope(
structureKey, activeStructure, start, persisted);
if (refreshed != null) {
if (refreshed != persisted) {
NativeStructureOwnershipStore.record(engine, refreshed);
}
return refreshed;
}
NativeStructureOwnershipStore.discard(
engine, structureKey, origin.x(), origin.z());
@@ -72,6 +79,37 @@ public final class NativeStructureOwnershipRecovery {
return recovered;
}
static NativeStructureOwnershipRecord refreshReferenceEnvelope(
String structureKey, Structure structure, StructureStart start,
NativeStructureOwnershipRecord ownership) {
if (structure == null || start == null || !start.isValid()
|| start.getStructure() != structure || ownership == null
|| !ownership.structureKey().equals(normalize(structureKey))
|| !NativeStructureOwnershipFingerprint.matches(ownership, start)) {
return null;
}
IrisStructureTerrain terrain = NativeStructureTerrainIntegrator.resolveNativeTerrain(
start, ownership.restoredDecision().terrain());
if (terrain.resolvedMode() != IrisStructureTerrainMode.VACUUM) {
return ownership;
}
BoundingBox expected = NativeStructureReferenceEnvelope.referenceBounds(
start, structure, terrain, structureKey);
int referenceMinChunkX = expected.minX() >> 4;
int referenceMaxChunkX = expected.maxX() >> 4;
int referenceMinChunkZ = expected.minZ() >> 4;
int referenceMaxChunkZ = expected.maxZ() >> 4;
if (ownership.referenceMinChunkX() == referenceMinChunkX
&& ownership.referenceMaxChunkX() == referenceMaxChunkX
&& ownership.referenceMinChunkZ() == referenceMinChunkZ
&& ownership.referenceMaxChunkZ() == referenceMaxChunkZ) {
return ownership;
}
return ownership.withReferenceEnvelope(
referenceMinChunkX, referenceMaxChunkX,
referenceMinChunkZ, referenceMaxChunkZ);
}
static NativeStructureOwnershipRecord proveCandidate(
String structureKey, Structure structure, StructureStart persisted,
NativeStructureStartPlan plan, StructureStart expected,
@@ -55,6 +55,8 @@ public final class NativeStructurePostProcessor {
world, area, target.start(), () -> world.getLevel().getStructureManager());
}
}
NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world, area, targets, () -> world.getLevel().getStructureManager());
}
@FunctionalInterface
@@ -64,7 +64,9 @@ public final class NativeStructureReferenceEnvelope {
|| mode == IrisStructureTerrainMode.FORCE_CARVE
|| mode == IrisStructureTerrainMode.VACUUM
|| mode == IrisStructureTerrainMode.ENCASE;
int horizontalPadding = usesEnvelope ? Math.max(0, terrain.getHorizontalPadding()) : 0;
int horizontalPadding = mode == IrisStructureTerrainMode.VACUUM
? NativeStructureSurfaceFitter.surfaceTerrainRadius()
: usesEnvelope ? Math.max(0, terrain.getHorizontalPadding()) : 0;
BoundingBox content = contentBounds(start);
if (!fitsReferenceRange(start.getChunkPos(), content)) {
throw new UnrepresentableContentException("Native structure content at "
@@ -1,15 +1,27 @@
package art.arcane.iris.nativegen;
import com.mojang.datafixers.util.Either;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.levelgen.structure.ScatteredFeaturePiece;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentPieces;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
@@ -100,6 +112,63 @@ final class NativeStructureReflection {
return resolveTemplateReference(reference, templates);
}
@SuppressWarnings("unchecked")
static List<StructureTemplate.StructureBlockInfo> resolveTemplateBlocks(
StructureTemplate template, StructurePlaceSettings settings,
BlockPos origin) {
Object value;
try {
value = StructureTemplatePalettesAccess.FIELD.get(template);
} catch (IllegalAccessException error) {
throw new IllegalStateException("Cannot read native structure template palettes", error);
}
if (!(value instanceof List<?> paletteValues)) {
throw new IllegalStateException("Native structure template palettes field is not a List");
}
List<StructureTemplate.Palette> palettes =
(List<StructureTemplate.Palette>) paletteValues;
if (palettes.isEmpty()) {
return List.of();
}
return settings.getRandomPalette(palettes, origin).blocks();
}
static StructurePlaceSettings resolvePlacementSettings(
SinglePoolElement element, PoolElementStructurePiece piece,
BoundingBox area) {
LiquidSettings liquidSettings;
try {
liquidSettings = (LiquidSettings) PoolPieceLiquidSettingsAccess.FIELD.get(piece);
} catch (IllegalAccessException error) {
throw new IllegalStateException("Cannot read native structure piece liquid settings", error);
}
try {
return (StructurePlaceSettings) SinglePoolSettingsAccess.METHOD.invoke(
element, piece.getRotation(), area, liquidSettings, false);
} catch (IllegalAccessException | InvocationTargetException error) {
throw new IllegalStateException("Cannot create native structure placement settings", error);
}
}
@SuppressWarnings("unchecked")
static List<StructureTemplate.StructureBlockInfo> processTemplateBlocks(
ServerLevelAccessor world, BlockPos origin, BlockPos referencePos,
StructurePlaceSettings settings,
List<StructureTemplate.StructureBlockInfo> blocks,
StructureTemplate template) {
Method forgeMethod = StructureTemplateProcessingAccess.FORGE_METHOD;
if (forgeMethod == null) {
return StructureTemplate.processBlockInfos(
world, origin, referencePos, settings, blocks);
}
try {
return (List<StructureTemplate.StructureBlockInfo>) forgeMethod.invoke(
null, world, origin, referencePos, settings, blocks, template);
} catch (IllegalAccessException | InvocationTargetException error) {
throw new IllegalStateException("Cannot process native structure template blocks", error);
}
}
static StructureTemplate resolveTemplateReference(Either<?, ?> reference,
Supplier<StructureTemplateManager> templates) {
return reference.map(
@@ -125,6 +194,113 @@ final class NativeStructureReflection {
return template;
}
private static Field resolveStructureTemplatePalettesField() {
Field resolved = null;
for (Field field : StructureTemplate.class.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) || field.getType() != List.class
|| !isPaletteList(field.getGenericType())) {
continue;
}
if (resolved != null) {
throw new IllegalStateException("StructureTemplate has multiple palette List fields");
}
resolved = field;
}
if (resolved == null) {
throw new IllegalStateException("StructureTemplate palette List field is missing");
}
if (!Modifier.isFinal(resolved.getModifiers())) {
throw new IllegalStateException("StructureTemplate palette field has an unexpected access contract");
}
if (!resolved.trySetAccessible()) {
throw new IllegalStateException("StructureTemplate palette field is inaccessible");
}
return resolved;
}
private static boolean isPaletteList(Type type) {
if (!(type instanceof ParameterizedType parameterized)) {
return false;
}
Type[] arguments = parameterized.getActualTypeArguments();
return arguments.length == 1 && arguments[0] == StructureTemplate.Palette.class;
}
private static Method resolveSinglePoolSettingsMethod() {
Method resolved = null;
Class<?>[] parameterTypes = {
Rotation.class, BoundingBox.class, LiquidSettings.class, boolean.class
};
for (Method method : SinglePoolElement.class.getDeclaredMethods()) {
if (Modifier.isStatic(method.getModifiers())
|| method.getReturnType() != StructurePlaceSettings.class
|| !Arrays.equals(method.getParameterTypes(), parameterTypes)) {
continue;
}
if (resolved != null) {
throw new IllegalStateException("SinglePoolElement has multiple placement-settings methods");
}
resolved = method;
}
if (resolved == null) {
throw new IllegalStateException("SinglePoolElement placement-settings method is missing");
}
if (!Modifier.isProtected(resolved.getModifiers())) {
throw new IllegalStateException("SinglePoolElement placement-settings method has an unexpected access contract");
}
if (!resolved.trySetAccessible()) {
throw new IllegalStateException("SinglePoolElement placement-settings method is inaccessible");
}
return resolved;
}
private static Field resolvePoolPieceLiquidSettingsField() {
Field resolved = null;
for (Field field : PoolElementStructurePiece.class.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())
|| field.getType() != LiquidSettings.class) {
continue;
}
if (resolved != null) {
throw new IllegalStateException("PoolElementStructurePiece has multiple liquid-settings fields");
}
resolved = field;
}
if (resolved == null) {
throw new IllegalStateException("PoolElementStructurePiece liquid-settings field is missing");
}
if (!Modifier.isPrivate(resolved.getModifiers())
|| !Modifier.isFinal(resolved.getModifiers())) {
throw new IllegalStateException("PoolElementStructurePiece liquid-settings field has an unexpected access contract");
}
if (!resolved.trySetAccessible()) {
throw new IllegalStateException("PoolElementStructurePiece liquid-settings field is inaccessible");
}
return resolved;
}
private static Method resolveForgeTemplateProcessingMethod() {
Class<?>[] parameterTypes = {
ServerLevelAccessor.class, BlockPos.class, BlockPos.class,
StructurePlaceSettings.class, List.class, StructureTemplate.class
};
Method resolved = null;
for (Method method : StructureTemplate.class.getDeclaredMethods()) {
if (!Modifier.isPublic(method.getModifiers())
|| !Modifier.isStatic(method.getModifiers())
|| method.getReturnType() != List.class
|| !Arrays.equals(method.getParameterTypes(), parameterTypes)) {
continue;
}
if (resolved != null) {
throw new IllegalStateException(
"StructureTemplate has multiple Forge block-processing methods");
}
resolved = method;
}
return resolved;
}
static final class MonumentChildPiecesAccess {
static final Field FIELD = resolveMonumentChildPiecesField();
@@ -145,4 +321,32 @@ final class NativeStructureReflection {
private SinglePoolTemplateAccess() {
}
}
private static final class StructureTemplatePalettesAccess {
private static final Field FIELD = resolveStructureTemplatePalettesField();
private StructureTemplatePalettesAccess() {
}
}
private static final class SinglePoolSettingsAccess {
private static final Method METHOD = resolveSinglePoolSettingsMethod();
private SinglePoolSettingsAccess() {
}
}
private static final class PoolPieceLiquidSettingsAccess {
private static final Field FIELD = resolvePoolPieceLiquidSettingsField();
private PoolPieceLiquidSettingsAccess() {
}
}
private static final class StructureTemplateProcessingAccess {
private static final Method FORGE_METHOD = resolveForgeTemplateProcessingMethod();
private StructureTemplateProcessingAccess() {
}
}
}
@@ -48,6 +48,10 @@ public final class NativeStructureSurfaceFitter {
|| adjustment == TerrainAdjustment.BEARD_BOX;
}
static int surfaceTerrainRadius() {
return SURFACE_TERRAIN_RADIUS;
}
static int resolveSurfaceTarget(List<SurfaceAnchor> anchors, int worldX, int worldZ,
int originalY) {
return resolveSurface(anchors, worldX, worldZ, originalY).targetY();
@@ -160,7 +164,7 @@ public final class NativeStructureSurfaceFitter {
continue;
}
StructureStart start = target.start();
TerrainAdjustment adjustment = start.getStructure().terrainAdaptation();
TerrainAdjustment adjustment = effectiveSurfaceAdjustment(target);
for (StructurePiece piece : start.getPieces()) {
if (piece instanceof PoolElementStructurePiece poolPiece) {
if (poolPiece.getElement().getProjection() == StructureTemplatePool.Projection.RIGID) {
@@ -203,17 +207,28 @@ public final class NativeStructureSurfaceFitter {
}
static boolean requiresSurfaceTerrain(NativeStructureTerrainIntegrator.TerrainTarget target) {
if (target == null || target.terrain() == null
|| target.terrain().resolvedMode() != IrisStructureTerrainMode.SOURCE) {
if (target == null || target.terrain() == null) {
return false;
}
StructureStart start = target.start();
return start != null
&& start.isValid()
if (start == null || !start.isValid()) {
return false;
}
IrisStructureTerrainMode mode = target.terrain().resolvedMode();
return mode == IrisStructureTerrainMode.VACUUM
|| mode == IrisStructureTerrainMode.SOURCE
&& shouldPrepareSurfaceTerrain(
start.getStructure().terrainAdaptation(), start.getStructure().step());
}
static TerrainAdjustment effectiveSurfaceAdjustment(
NativeStructureTerrainIntegrator.TerrainTarget target) {
if (target.terrain().resolvedMode() == IrisStructureTerrainMode.VACUUM) {
return TerrainAdjustment.BEARD_THIN;
}
return target.start().getStructure().terrainAdaptation();
}
private static void fitSurfaceTerrain(WorldGenLevel world, BoundingBox area,
List<SurfaceAnchor> anchors,
IntBinaryOperator surfaceHeight) {
@@ -382,6 +397,7 @@ public final class NativeStructureSurfaceFitter {
int verticalDistance(int y) {
return IrisObjectVacuum.outset(y, minInfluenceY, maxInfluenceY);
}
}
private record SurfaceMaterials(BlockState surface, BlockState subsurface) {
@@ -0,0 +1,405 @@
package art.arcane.iris.nativegen;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pools.EmptyPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.ListPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
final class NativeStructureSurfaceSupportBuilder {
private static final int MAX_BATCH_CELLS = 524_288;
private static final int MAX_BRIDGE_SPAN = 2;
private static final OccupancyCell BLOCKER = new OccupancyCell(null, true);
private NativeStructureSurfaceSupportBuilder() {
}
static Set<Long> bridgeRigidPieceSupport(
WorldGenLevel world, BoundingBox area,
List<NativeStructureTerrainIntegrator.TerrainTarget> targets,
Supplier<StructureTemplateManager> templates) {
if (targets == null || targets.isEmpty()) {
return Set.of();
}
Map<Long, BlockState> planned;
try {
BatchCellBudget budget = new BatchCellBudget();
List<SupportRequest> requests = new ArrayList<>();
Set<StructureStart> seenStarts = Collections.newSetFromMap(
new IdentityHashMap<>());
for (NativeStructureTerrainIntegrator.TerrainTarget target : targets) {
if (target == null
|| !NativeStructureSurfaceFitter.requiresSurfaceTerrain(target)
|| !seenStarts.add(target.start())) {
continue;
}
collectTargetRequests(
world, area, target, templates, budget, requests);
}
planned = planWrites(world, area, requests, budget);
} catch (BatchBudgetExceeded ignored) {
return Set.of();
}
if (planned.isEmpty()) {
return Set.of();
}
List<Map.Entry<Long, BlockState>> ordered = new ArrayList<>(planned.entrySet());
ordered.sort(Map.Entry.comparingByKey());
Set<Long> written = new HashSet<>();
for (Map.Entry<Long, BlockState> entry : ordered) {
BlockPos position = BlockPos.of(entry.getKey());
BlockState existing = world.getBlockState(position);
if (!existing.isSolid()
&& !NativeStructureVegetationClearer.isTreeBlock(existing)
&& existing.getFluidState().isEmpty()
&& world.setBlock(position, entry.getValue(), 2)) {
written.add(entry.getKey());
}
}
return Set.copyOf(written);
}
private static void collectTargetRequests(
WorldGenLevel world, BoundingBox area,
NativeStructureTerrainIntegrator.TerrainTarget target,
Supplier<StructureTemplateManager> templates,
BatchCellBudget budget, List<SupportRequest> requests) {
if (!NativeStructureSurfaceFitter.requiresSurfaceTerrain(target)) {
return;
}
List<RigidAnchor> anchors = rigidAnchors(target.start());
if (anchors.size() < 2) {
return;
}
BlockPos referencePos = referencePosition(target.start());
for (RigidAnchor upper : anchors) {
Map<Long, Integer> lowerMeets = candidateLowerMeets(
area, upper, anchors, budget);
if (lowerMeets.isEmpty()) {
continue;
}
Map<Long, OccupancyCell> occupancy = effectiveOccupancy(
world, area, upper.piece(), lowerMeets,
referencePos, templates, budget);
Map<Long, LowestCell> lowest = lowestCells(occupancy);
for (Map.Entry<Long, LowestCell> entry : lowest.entrySet()) {
LowestCell cell = entry.getValue();
Integer lowerMeet = lowerMeets.get(entry.getKey());
if (lowerMeet == null || cell.occupancy().blocker()
|| !isSolidBase(cell.occupancy().state())
|| cell.y() - lowerMeet < 2
|| cell.y() - lowerMeet > MAX_BRIDGE_SPAN) {
continue;
}
budget.consume(1);
requests.add(new SupportRequest(
cell.x(), cell.z(), lowerMeet, cell.y()));
}
}
}
private static List<RigidAnchor> rigidAnchors(StructureStart start) {
List<RigidAnchor> anchors = new ArrayList<>();
for (StructurePiece piece : start.getPieces()) {
if (!(piece instanceof PoolElementStructurePiece poolPiece)
|| poolPiece.getElement().getProjection()
!= StructureTemplatePool.Projection.RIGID) {
continue;
}
BoundingBox bounds = poolPiece.getBoundingBox();
int meetY = bounds.minY() + poolPiece.getGroundLevelDelta() - 1;
anchors.add(new RigidAnchor(poolPiece, bounds, meetY));
}
return List.copyOf(anchors);
}
private static Map<Long, Integer> candidateLowerMeets(
BoundingBox area, RigidAnchor upper, List<RigidAnchor> anchors,
BatchCellBudget budget) {
Map<Long, Integer> lowerMeets = new HashMap<>();
for (RigidAnchor lower : anchors) {
budget.consume(1);
int span = upper.meetY() - lower.meetY();
if (lower.piece() == upper.piece() || span < 1
|| span > MAX_BRIDGE_SPAN
|| lower.meetY() < area.minY() || lower.meetY() > area.maxY()) {
continue;
}
int minimumX = Math.max(area.minX(),
Math.max(upper.bounds().minX(), lower.bounds().minX()));
int maximumX = Math.min(area.maxX(),
Math.min(upper.bounds().maxX(), lower.bounds().maxX()));
int minimumZ = Math.max(area.minZ(),
Math.max(upper.bounds().minZ(), lower.bounds().minZ()));
int maximumZ = Math.min(area.maxZ(),
Math.min(upper.bounds().maxZ(), lower.bounds().maxZ()));
if (minimumX > maximumX || minimumZ > maximumZ) {
continue;
}
for (int x = minimumX; x <= maximumX; x++) {
for (int z = minimumZ; z <= maximumZ; z++) {
budget.consume(1);
lowerMeets.merge(columnKey(x, z), lower.meetY(), Math::max);
}
}
}
return lowerMeets;
}
private static Map<Long, OccupancyCell> effectiveOccupancy(
WorldGenLevel world, BoundingBox area,
PoolElementStructurePiece piece, Map<Long, Integer> lowerMeets,
BlockPos referencePos, Supplier<StructureTemplateManager> templates,
BatchCellBudget budget) {
List<SinglePoolElement> leaves = new ArrayList<>();
if (!flattenSingles(piece.getElement(), leaves)) {
return Map.of();
}
List<LeafPlacement> placements = new ArrayList<>(leaves.size());
Map<Long, OccupancyCell> occupancy = new HashMap<>();
for (SinglePoolElement leaf : leaves) {
StructurePlaceSettings settings =
NativeStructureReflection.resolvePlacementSettings(leaf, piece, area);
StructureTemplate template = NativeStructureReflection.resolveTemplate(leaf, templates);
List<StructureTemplate.StructureBlockInfo> rawBlocks =
NativeStructureReflection.resolveTemplateBlocks(
template, settings, piece.getPosition());
budget.consume(rawBlocks.size());
placements.add(new LeafPlacement(settings, rawBlocks, template));
for (StructureTemplate.StructureBlockInfo raw : rawBlocks) {
BlockPos position = piece.getPosition().offset(
StructureTemplate.calculateRelativePosition(settings, raw.pos()));
if (retain(position, area, lowerMeets)) {
occupancy.put(position.asLong(), BLOCKER);
}
}
}
for (LeafPlacement placement : placements) {
List<StructureTemplate.StructureBlockInfo> processed =
NativeStructureReflection.processTemplateBlocks(
world, piece.getPosition(), referencePos,
placement.settings(), placement.rawBlocks(),
placement.template());
budget.consume(processed.size());
for (StructureTemplate.StructureBlockInfo block : processed) {
BlockPos position = block.pos();
if (!retain(position, area, lowerMeets)) {
continue;
}
BlockState state = block.state()
.mirror(placement.settings().getMirror())
.rotate(placement.settings().getRotation());
occupancy.put(position.asLong(), new OccupancyCell(state, false));
}
}
return occupancy;
}
private static boolean flattenSingles(
StructurePoolElement element, List<SinglePoolElement> leaves) {
if (element instanceof ListPoolElement listElement) {
for (StructurePoolElement child : listElement.getElements()) {
if (!flattenSingles(child, leaves)) {
return false;
}
}
return true;
}
if (element == EmptyPoolElement.INSTANCE) {
return true;
}
if (element instanceof SinglePoolElement singleElement) {
leaves.add(singleElement);
return true;
}
return false;
}
private static boolean retain(
BlockPos position, BoundingBox area, Map<Long, Integer> lowerMeets) {
if (!area.isInside(position)) {
return false;
}
Integer lowerMeet = lowerMeets.get(columnKey(position.getX(), position.getZ()));
return lowerMeet != null && position.getY() <= lowerMeet + MAX_BRIDGE_SPAN;
}
private static Map<Long, LowestCell> lowestCells(
Map<Long, OccupancyCell> occupancy) {
Map<Long, LowestCell> lowest = new HashMap<>();
for (Map.Entry<Long, OccupancyCell> entry : occupancy.entrySet()) {
BlockPos position = BlockPos.of(entry.getKey());
long column = columnKey(position.getX(), position.getZ());
LowestCell current = lowest.get(column);
if (current == null || position.getY() < current.y()) {
lowest.put(column, new LowestCell(
position.getX(), position.getY(), position.getZ(),
entry.getValue()));
}
}
return lowest;
}
private static Map<Long, BlockState> planWrites(
WorldGenLevel world, BoundingBox area,
List<SupportRequest> requests, BatchCellBudget budget) {
List<SupportRequest> eligible = new ArrayList<>();
Set<MaterialKey> materialKeys = new HashSet<>();
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
for (SupportRequest request : requests) {
BlockState lowerTerrain = world.getBlockState(position.set(
request.x(), request.lowerMeetY(), request.z()));
if (!isTerrainSupport(lowerTerrain)
|| !gapIsClear(world, area, position, request, budget)) {
continue;
}
eligible.add(request);
materialKeys.add(new MaterialKey(
request.x(), request.z(), request.lowerMeetY()));
}
Map<MaterialKey, BlockState> materials = new HashMap<>();
for (MaterialKey key : materialKeys) {
materials.put(key, resolveSupportState(
world, position, key.x(), key.z(), key.lowerMeetY(), budget));
}
Map<Long, BlockState> planned = new HashMap<>();
Set<Long> conflicts = new HashSet<>();
eligible.sort(Comparator.comparingInt(SupportRequest::x)
.thenComparingInt(SupportRequest::z)
.thenComparingInt(SupportRequest::lowerMeetY)
.thenComparingInt(SupportRequest::baseY));
for (SupportRequest request : eligible) {
MaterialKey materialKey = new MaterialKey(
request.x(), request.z(), request.lowerMeetY());
BlockState material = materials.get(materialKey);
for (int y = request.lowerMeetY() + 1; y < request.baseY(); y++) {
budget.consume(1);
long positionKey = BlockPos.asLong(request.x(), y, request.z());
if (conflicts.contains(positionKey)) {
continue;
}
BlockState existing = planned.putIfAbsent(positionKey, material);
if (existing != null && !existing.equals(material)) {
planned.remove(positionKey);
conflicts.add(positionKey);
}
}
}
return planned;
}
private static boolean gapIsClear(
WorldGenLevel world, BoundingBox area, BlockPos.MutableBlockPos position,
SupportRequest request, BatchCellBudget budget) {
for (int y = request.lowerMeetY() + 1; y < request.baseY(); y++) {
budget.consume(1);
if (!area.isInside(position.set(request.x(), y, request.z()))) {
return false;
}
BlockState state = world.getBlockState(position);
if (state.isSolid() || NativeStructureVegetationClearer.isTreeBlock(state)
|| !state.getFluidState().isEmpty()) {
return false;
}
}
return true;
}
private static BlockState resolveSupportState(
WorldGenLevel world, BlockPos.MutableBlockPos position,
int x, int z, int lowerMeetY, BatchCellBudget budget) {
budget.consume(1);
BlockState below = world.getBlockState(position.set(x, lowerMeetY - 1, z));
if (isTerrainSupport(below)) {
return below;
}
return world.getBlockState(position.set(x, lowerMeetY, z));
}
private static boolean isSolidBase(BlockState state) {
return state != null && state.isSolid()
&& !state.is(Blocks.STRUCTURE_VOID)
&& !state.is(Blocks.JIGSAW)
&& state.getFluidState().isEmpty();
}
private static boolean isTerrainSupport(BlockState state) {
return state.isSolid()
&& !NativeStructureVegetationClearer.isTreeBlock(state)
&& state.getFluidState().isEmpty();
}
private static BlockPos referencePosition(StructureStart start) {
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
BlockPos center = bounds.getCenter();
return new BlockPos(center.getX(), bounds.minY(), center.getZ());
}
private static long columnKey(int x, int z) {
return (long) x << 32 ^ z & 0xffffffffL;
}
private record RigidAnchor(
PoolElementStructurePiece piece, BoundingBox bounds, int meetY) {
}
private record LeafPlacement(
StructurePlaceSettings settings,
List<StructureTemplate.StructureBlockInfo> rawBlocks,
StructureTemplate template) {
}
private record OccupancyCell(BlockState state, boolean blocker) {
}
private record LowestCell(
int x, int y, int z, OccupancyCell occupancy) {
}
private record SupportRequest(int x, int z, int lowerMeetY, int baseY) {
}
private record MaterialKey(int x, int z, int lowerMeetY) {
}
private static final class BatchCellBudget {
private int consumed;
private void consume(int amount) {
if (amount < 0 || consumed > MAX_BATCH_CELLS - amount) {
throw BatchBudgetExceeded.INSTANCE;
}
consumed += amount;
}
}
private static final class BatchBudgetExceeded extends RuntimeException {
private static final BatchBudgetExceeded INSTANCE = new BatchBudgetExceeded();
private BatchBudgetExceeded() {
super(null, null, false, false);
}
}
}
@@ -114,7 +114,6 @@ public final class NativeStructureTerrainIntegrator {
return;
}
if (mode == IrisStructureTerrainMode.VACUUM) {
carvePieceBoxes(world, area, start, terrain);
return;
}
if (mode == IrisStructureTerrainMode.ENCASE) {
@@ -267,6 +266,7 @@ public final class NativeStructureTerrainIntegrator {
}
IrisStructureTerrainMode mode = terrain.resolvedMode();
return mode == IrisStructureTerrainMode.ENCASE
|| mode == IrisStructureTerrainMode.VACUUM
|| mode == IrisStructureTerrainMode.SOURCE
&& start.getStructure().terrainAdaptation() != TerrainAdjustment.NONE;
}
@@ -360,7 +360,7 @@ final class ModdedNativeStructureStage {
world, area, terrainTargets, this::resolvePaletteBlock);
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"terrain carving", nativeStructureBatchContext(placementGroups),
"terrain preparation", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
for (NativePlacementGroup group : placementGroups) {
@@ -63,6 +63,8 @@ public class NativeStructureFailureContractTest {
String placement = source.substring(placementStart, placementEnd);
assertTrue(placement.contains("\"terrain integration\""));
assertTrue(placement.contains("\"terrain preparation\""));
assertFalse(placement.contains("\"terrain carving\""));
assertTrue(placement.contains("prepareSurfaceStructures"));
assertTrue(placement.contains("clearIntersectingVegetation"));
assertTrue(placement.indexOf("clearIntersectingVegetation")
@@ -0,0 +1,51 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.engine.object.IrisStructureTerrain;
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
import net.minecraft.SharedConstants;
import net.minecraft.core.HolderSet;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class NativeStructureVacuumParityTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void explicitVacuumUsesSharedThinSurfaceFittingOnModdedLoaders() {
Structure structure = new DesertPyramidStructure(
new Structure.StructureSettings(
HolderSet.empty(), Map.of(),
GenerationStep.Decoration.SURFACE_STRUCTURES, TerrainAdjustment.NONE));
DesertPyramidPiece piece = new DesertPyramidPiece(RandomSource.create(7L), 0, 0);
StructureStart start = new StructureStart(
structure, new ChunkPos(0, 0), 0, new PiecesContainer(List.of(piece)));
NativeStructureTerrainIntegrator.TerrainTarget target =
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:vacuum", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.VACUUM));
assertTrue(NativeStructureSurfaceFitter.requiresSurfaceTerrain(target));
assertEquals(TerrainAdjustment.BEARD_THIN,
NativeStructureSurfaceFitter.effectiveSurfaceAdjustment(target));
}
}