mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 04:37:47 +00:00
Temp vag
This commit is contained in:
+1
-1
@@ -606,7 +606,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
|||||||
world, area, terrainTargets, this::resolvePaletteBlock);
|
world, area, terrainTargets, this::resolvePaletteBlock);
|
||||||
} catch (Throwable error) {
|
} catch (Throwable error) {
|
||||||
throw NativeStructureGenerationException.failure(
|
throw NativeStructureGenerationException.failure(
|
||||||
"terrain carving", nativeStructureBatchContext(placementGroups),
|
"terrain preparation", nativeStructureBatchContext(placementGroups),
|
||||||
chunkPos.x(), chunkPos.z(), error);
|
chunkPos.x(), chunkPos.z(), error);
|
||||||
}
|
}
|
||||||
for (NativePlacementGroup group : placementGroups) {
|
for (NativePlacementGroup group : placementGroups) {
|
||||||
|
|||||||
+2
@@ -44,6 +44,8 @@ public class IrisChunkGeneratorFailureContractTest {
|
|||||||
assertFalse(adjustment.contains("IrisLogging.reportError"));
|
assertFalse(adjustment.contains("IrisLogging.reportError"));
|
||||||
assertTrue(placement.contains("\"resolution\""));
|
assertTrue(placement.contains("\"resolution\""));
|
||||||
assertTrue(placement.contains("\"terrain integration\""));
|
assertTrue(placement.contains("\"terrain integration\""));
|
||||||
|
assertTrue(placement.contains("\"terrain preparation\""));
|
||||||
|
assertFalse(placement.contains("\"terrain carving\""));
|
||||||
assertTrue(placement.contains("\"vegetation cleanup\""));
|
assertTrue(placement.contains("\"vegetation cleanup\""));
|
||||||
assertTrue(placement.contains("\"placement\""));
|
assertTrue(placement.contains("\"placement\""));
|
||||||
assertTrue(placement.contains("because structure generation is disabled outside the pack"));
|
assertTrue(placement.contains("because structure generation is disabled outside the pack"));
|
||||||
|
|||||||
+130
-1
@@ -16,6 +16,7 @@ import net.minecraft.world.level.ChunkPos;
|
|||||||
import net.minecraft.world.level.levelgen.LegacyRandomSource;
|
import net.minecraft.world.level.levelgen.LegacyRandomSource;
|
||||||
import net.minecraft.world.level.levelgen.RandomSupport;
|
import net.minecraft.world.level.levelgen.RandomSupport;
|
||||||
import net.minecraft.world.level.levelgen.WorldgenRandom;
|
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.StructurePiece;
|
||||||
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
||||||
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
|
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.assertNotEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class NativeStructureOwnershipRecoveryTest {
|
public class NativeStructureOwnershipRecoveryTest {
|
||||||
@@ -138,6 +140,110 @@ public class NativeStructureOwnershipRecoveryTest {
|
|||||||
assertFalse(NativeStructureOwnershipFingerprint.matches(recovered, moved));
|
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() {
|
private static OceanMonumentStructure structure() {
|
||||||
return new OceanMonumentStructure(
|
return new OceanMonumentStructure(
|
||||||
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
|
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
|
||||||
@@ -163,11 +269,19 @@ public class NativeStructureOwnershipRecoveryTest {
|
|||||||
private static NativeStructureStartPlan plan(ChunkPos origin,
|
private static NativeStructureStartPlan plan(ChunkPos origin,
|
||||||
String placementId,
|
String placementId,
|
||||||
int horizontalPadding) {
|
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()
|
IrisNativeStructure source = new IrisNativeStructure()
|
||||||
.setStructure("minecraft:monument")
|
.setStructure("minecraft:monument")
|
||||||
.setWeight(1);
|
.setWeight(1);
|
||||||
IrisStructureTerrain terrain = new IrisStructureTerrain()
|
IrisStructureTerrain terrain = new IrisStructureTerrain()
|
||||||
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
|
.setMode(terrainMode)
|
||||||
.setHorizontalPadding(horizontalPadding);
|
.setHorizontalPadding(horizontalPadding);
|
||||||
IrisStructurePlacement placement = new IrisStructurePlacement()
|
IrisStructurePlacement placement = new IrisStructurePlacement()
|
||||||
.setPlacementId(placementId)
|
.setPlacementId(placementId)
|
||||||
@@ -182,4 +296,19 @@ public class NativeStructureOwnershipRecoveryTest {
|
|||||||
monumentStart(structure(), origin, 1L)).minY()
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+582
-12
@@ -6,14 +6,18 @@ import art.arcane.iris.engine.object.IrisStructureTerrain;
|
|||||||
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
|
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
|
||||||
import com.mojang.datafixers.util.Either;
|
import com.mojang.datafixers.util.Either;
|
||||||
import com.mojang.serialization.Codec;
|
import com.mojang.serialization.Codec;
|
||||||
|
import com.mojang.serialization.MapCodec;
|
||||||
import net.minecraft.SharedConstants;
|
import net.minecraft.SharedConstants;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.Holder;
|
import net.minecraft.core.Holder;
|
||||||
import net.minecraft.core.HolderSet;
|
import net.minecraft.core.HolderSet;
|
||||||
import net.minecraft.core.IdMapper;
|
import net.minecraft.core.IdMapper;
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.server.Bootstrap;
|
import net.minecraft.server.Bootstrap;
|
||||||
import net.minecraft.util.RandomSource;
|
import net.minecraft.util.RandomSource;
|
||||||
import net.minecraft.world.level.ChunkPos;
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.LevelReader;
|
||||||
import net.minecraft.world.level.LevelHeightAccessor;
|
import net.minecraft.world.level.LevelHeightAccessor;
|
||||||
import net.minecraft.world.level.WorldGenLevel;
|
import net.minecraft.world.level.WorldGenLevel;
|
||||||
import net.minecraft.world.level.biome.Biome;
|
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.StructureStart;
|
||||||
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
|
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
|
||||||
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
|
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.SinglePoolElement;
|
||||||
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
|
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.pools.StructureTemplatePool;
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
|
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.DesertPyramidPiece;
|
||||||
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
|
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.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.StructureTemplate;
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
@@ -55,8 +64,11 @@ import java.lang.reflect.Modifier;
|
|||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@@ -118,6 +130,34 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
|||||||
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.PRESERVE))));
|
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
|
@Test
|
||||||
public void beardBoxUsesTheFullRigidHeightWhileBeardThinUsesTheGroundPlane() {
|
public void beardBoxUsesTheFullRigidHeightWhileBeardThinUsesTheGroundPlane() {
|
||||||
NativeStructureSurfaceFitter.SurfaceAnchor thin =
|
NativeStructureSurfaceFitter.SurfaceAnchor thin =
|
||||||
@@ -210,11 +250,12 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeStructureSurfaceFitter.prepareSurfaceStructures(
|
List<NativeStructureTerrainIntegrator.TerrainTarget> targets = List.of(
|
||||||
world(blocks), area,
|
new NativeStructureTerrainIntegrator.TerrainTarget(
|
||||||
List.of(new NativeStructureTerrainIntegrator.TerrainTarget(
|
|
||||||
"nova_structures:tavern_oak", start,
|
"nova_structures:tavern_oak", start,
|
||||||
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))),
|
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE)));
|
||||||
|
NativeStructureSurfaceFitter.prepareSurfaceStructures(
|
||||||
|
world(blocks), area, targets,
|
||||||
(x, z) -> 64);
|
(x, z) -> 64);
|
||||||
|
|
||||||
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 1, 71, 1));
|
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 1, 71, 1));
|
||||||
@@ -263,6 +304,392 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
|||||||
List.of(strongTie, weakTie), 2, 2, 64));
|
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
|
@Test
|
||||||
public void stackedRigidPiecesPreserveTheLowerAuthoredSurface() {
|
public void stackedRigidPiecesPreserveTheLowerAuthoredSurface() {
|
||||||
Structure structure = new DesertPyramidStructure(
|
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(
|
NativeStructureSurfaceFitter.prepareSurfaceStructures(
|
||||||
world(blocks), area,
|
world, area, targets, (x, z) -> 60);
|
||||||
List.of(new NativeStructureTerrainIntegrator.TerrainTarget(
|
|
||||||
"nova_structures:tavern_oak", start,
|
|
||||||
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))),
|
|
||||||
(x, z) -> 60);
|
|
||||||
|
|
||||||
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 2, 62, 2));
|
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 2, 62, 2));
|
||||||
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 2, 65, 2));
|
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 2, 65, 2));
|
||||||
@@ -519,7 +945,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void nativeVacuumClearsEveryPieceEnvelopeBeforePlacement() {
|
public void nativeVacuumLeavesPieceBlocksForSurfaceFitting() {
|
||||||
StructureStart start = desertStart();
|
StructureStart start = desertStart();
|
||||||
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
|
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
|
||||||
Map<BlockPos, BlockState> blocks = new HashMap<>();
|
Map<BlockPos, BlockState> blocks = new HashMap<>();
|
||||||
@@ -529,7 +955,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
|||||||
world(blocks), bounds, "minecraft:desert_pyramid", start,
|
world(blocks), bounds, "minecraft:desert_pyramid", start,
|
||||||
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.VACUUM), null);
|
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.VACUUM), null);
|
||||||
|
|
||||||
assertEquals(Blocks.AIR.defaultBlockState(),
|
assertEquals(Blocks.STONE.defaultBlockState(),
|
||||||
state(blocks, bounds.minX(), bounds.minY(), bounds.minZ()));
|
state(blocks, bounds.minX(), bounds.minY(), bounds.minZ()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -892,6 +1318,27 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
|||||||
assertEquals(generated.getPieces().size(), wrapped.getPieces().size());
|
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
|
@Test
|
||||||
public void nativeTerrainEnvelopeClipsOptionalCoverageWithoutDroppingContent() {
|
public void nativeTerrainEnvelopeClipsOptionalCoverageWithoutDroppingContent() {
|
||||||
StructureStart generated = desertStart();
|
StructureStart generated = desertStart();
|
||||||
@@ -999,7 +1446,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
|||||||
BlockState log = Blocks.OAK_LOG.defaultBlockState();
|
BlockState log = Blocks.OAK_LOG.defaultBlockState();
|
||||||
blocks.put(origin, log);
|
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));
|
assertEquals(log, blocks.get(origin));
|
||||||
}
|
}
|
||||||
@@ -1018,6 +1466,71 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
|||||||
LiquidSettings.APPLY_WATERLOGGING);
|
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(
|
private static NativeStructureTerrainIntegrator.OrganicCarve organicCarve(
|
||||||
StructureStart start, int horizontalPadding) {
|
StructureStart start, int horizontalPadding) {
|
||||||
return organicCarve(start, horizontalPadding, 0.85D);
|
return organicCarve(start, horizontalPadding, 0.85D);
|
||||||
@@ -1122,6 +1635,57 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
|||||||
return template;
|
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() {
|
private static StructureStart desertStart() {
|
||||||
return desertStart(TerrainAdjustment.NONE);
|
return desertStart(TerrainAdjustment.NONE);
|
||||||
}
|
}
|
||||||
@@ -1155,6 +1719,9 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
|||||||
if (methodName.equals("getLevel")) {
|
if (methodName.equals("getLevel")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (methodName.equals("holderLookup")) {
|
||||||
|
return BuiltInRegistries.BLOCK;
|
||||||
|
}
|
||||||
if (methodName.equals("hashCode")) {
|
if (methodName.equals("hashCode")) {
|
||||||
return System.identityHashCode(proxy);
|
return System.identityHashCode(proxy);
|
||||||
}
|
}
|
||||||
@@ -1187,6 +1754,9 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
|||||||
if (methodName.equals("getLevel")) {
|
if (methodName.equals("getLevel")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (methodName.equals("holderLookup")) {
|
||||||
|
return BuiltInRegistries.BLOCK;
|
||||||
|
}
|
||||||
if (methodName.equals("hashCode")) {
|
if (methodName.equals("hashCode")) {
|
||||||
return System.identityHashCode(proxy);
|
return System.identityHashCode(proxy);
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-2
@@ -7,6 +7,8 @@ import art.arcane.iris.engine.framework.NativeStructureOwnershipStore;
|
|||||||
import art.arcane.iris.engine.framework.NativeStructurePlacementPlanner;
|
import art.arcane.iris.engine.framework.NativeStructurePlacementPlanner;
|
||||||
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
|
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
|
||||||
import art.arcane.iris.engine.framework.StructurePlacementGrid;
|
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.NativeStructureGenerationStatus;
|
||||||
import art.arcane.iris.engine.object.NativeStructureSuppression;
|
import art.arcane.iris.engine.object.NativeStructureSuppression;
|
||||||
import net.minecraft.core.Holder;
|
import net.minecraft.core.Holder;
|
||||||
@@ -44,8 +46,13 @@ public final class NativeStructureOwnershipRecovery {
|
|||||||
NativeStructureOwnershipRecord persisted = NativeStructureOwnershipStore.findPersisted(
|
NativeStructureOwnershipRecord persisted = NativeStructureOwnershipStore.findPersisted(
|
||||||
engine, structureKey, origin.x(), origin.z());
|
engine, structureKey, origin.x(), origin.z());
|
||||||
if (persisted != null) {
|
if (persisted != null) {
|
||||||
if (NativeStructureOwnershipFingerprint.matches(persisted, start)) {
|
NativeStructureOwnershipRecord refreshed = refreshReferenceEnvelope(
|
||||||
return persisted;
|
structureKey, activeStructure, start, persisted);
|
||||||
|
if (refreshed != null) {
|
||||||
|
if (refreshed != persisted) {
|
||||||
|
NativeStructureOwnershipStore.record(engine, refreshed);
|
||||||
|
}
|
||||||
|
return refreshed;
|
||||||
}
|
}
|
||||||
NativeStructureOwnershipStore.discard(
|
NativeStructureOwnershipStore.discard(
|
||||||
engine, structureKey, origin.x(), origin.z());
|
engine, structureKey, origin.x(), origin.z());
|
||||||
@@ -72,6 +79,37 @@ public final class NativeStructureOwnershipRecovery {
|
|||||||
return recovered;
|
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(
|
static NativeStructureOwnershipRecord proveCandidate(
|
||||||
String structureKey, Structure structure, StructureStart persisted,
|
String structureKey, Structure structure, StructureStart persisted,
|
||||||
NativeStructureStartPlan plan, StructureStart expected,
|
NativeStructureStartPlan plan, StructureStart expected,
|
||||||
|
|||||||
+2
@@ -55,6 +55,8 @@ public final class NativeStructurePostProcessor {
|
|||||||
world, area, target.start(), () -> world.getLevel().getStructureManager());
|
world, area, target.start(), () -> world.getLevel().getStructureManager());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
|
||||||
|
world, area, targets, () -> world.getLevel().getStructureManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
|
|||||||
+3
-1
@@ -64,7 +64,9 @@ public final class NativeStructureReferenceEnvelope {
|
|||||||
|| mode == IrisStructureTerrainMode.FORCE_CARVE
|
|| mode == IrisStructureTerrainMode.FORCE_CARVE
|
||||||
|| mode == IrisStructureTerrainMode.VACUUM
|
|| mode == IrisStructureTerrainMode.VACUUM
|
||||||
|| mode == IrisStructureTerrainMode.ENCASE;
|
|| 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);
|
BoundingBox content = contentBounds(start);
|
||||||
if (!fitsReferenceRange(start.getChunkPos(), content)) {
|
if (!fitsReferenceRange(start.getChunkPos(), content)) {
|
||||||
throw new UnrepresentableContentException("Native structure content at "
|
throw new UnrepresentableContentException("Native structure content at "
|
||||||
|
|||||||
+204
@@ -1,15 +1,27 @@
|
|||||||
package art.arcane.iris.nativegen;
|
package art.arcane.iris.nativegen;
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Either;
|
import com.mojang.datafixers.util.Either;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.resources.Identifier;
|
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.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.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.structures.OceanMonumentPieces;
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
||||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
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.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
@@ -100,6 +112,63 @@ final class NativeStructureReflection {
|
|||||||
return resolveTemplateReference(reference, templates);
|
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,
|
static StructureTemplate resolveTemplateReference(Either<?, ?> reference,
|
||||||
Supplier<StructureTemplateManager> templates) {
|
Supplier<StructureTemplateManager> templates) {
|
||||||
return reference.map(
|
return reference.map(
|
||||||
@@ -125,6 +194,113 @@ final class NativeStructureReflection {
|
|||||||
return template;
|
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 class MonumentChildPiecesAccess {
|
||||||
static final Field FIELD = resolveMonumentChildPiecesField();
|
static final Field FIELD = resolveMonumentChildPiecesField();
|
||||||
|
|
||||||
@@ -145,4 +321,32 @@ final class NativeStructureReflection {
|
|||||||
private SinglePoolTemplateAccess() {
|
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() {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-5
@@ -48,6 +48,10 @@ public final class NativeStructureSurfaceFitter {
|
|||||||
|| adjustment == TerrainAdjustment.BEARD_BOX;
|
|| adjustment == TerrainAdjustment.BEARD_BOX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int surfaceTerrainRadius() {
|
||||||
|
return SURFACE_TERRAIN_RADIUS;
|
||||||
|
}
|
||||||
|
|
||||||
static int resolveSurfaceTarget(List<SurfaceAnchor> anchors, int worldX, int worldZ,
|
static int resolveSurfaceTarget(List<SurfaceAnchor> anchors, int worldX, int worldZ,
|
||||||
int originalY) {
|
int originalY) {
|
||||||
return resolveSurface(anchors, worldX, worldZ, originalY).targetY();
|
return resolveSurface(anchors, worldX, worldZ, originalY).targetY();
|
||||||
@@ -160,7 +164,7 @@ public final class NativeStructureSurfaceFitter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
StructureStart start = target.start();
|
StructureStart start = target.start();
|
||||||
TerrainAdjustment adjustment = start.getStructure().terrainAdaptation();
|
TerrainAdjustment adjustment = effectiveSurfaceAdjustment(target);
|
||||||
for (StructurePiece piece : start.getPieces()) {
|
for (StructurePiece piece : start.getPieces()) {
|
||||||
if (piece instanceof PoolElementStructurePiece poolPiece) {
|
if (piece instanceof PoolElementStructurePiece poolPiece) {
|
||||||
if (poolPiece.getElement().getProjection() == StructureTemplatePool.Projection.RIGID) {
|
if (poolPiece.getElement().getProjection() == StructureTemplatePool.Projection.RIGID) {
|
||||||
@@ -203,17 +207,28 @@ public final class NativeStructureSurfaceFitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static boolean requiresSurfaceTerrain(NativeStructureTerrainIntegrator.TerrainTarget target) {
|
static boolean requiresSurfaceTerrain(NativeStructureTerrainIntegrator.TerrainTarget target) {
|
||||||
if (target == null || target.terrain() == null
|
if (target == null || target.terrain() == null) {
|
||||||
|| target.terrain().resolvedMode() != IrisStructureTerrainMode.SOURCE) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
StructureStart start = target.start();
|
StructureStart start = target.start();
|
||||||
return start != null
|
if (start == null || !start.isValid()) {
|
||||||
&& start.isValid()
|
return false;
|
||||||
|
}
|
||||||
|
IrisStructureTerrainMode mode = target.terrain().resolvedMode();
|
||||||
|
return mode == IrisStructureTerrainMode.VACUUM
|
||||||
|
|| mode == IrisStructureTerrainMode.SOURCE
|
||||||
&& shouldPrepareSurfaceTerrain(
|
&& shouldPrepareSurfaceTerrain(
|
||||||
start.getStructure().terrainAdaptation(), start.getStructure().step());
|
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,
|
private static void fitSurfaceTerrain(WorldGenLevel world, BoundingBox area,
|
||||||
List<SurfaceAnchor> anchors,
|
List<SurfaceAnchor> anchors,
|
||||||
IntBinaryOperator surfaceHeight) {
|
IntBinaryOperator surfaceHeight) {
|
||||||
@@ -382,6 +397,7 @@ public final class NativeStructureSurfaceFitter {
|
|||||||
int verticalDistance(int y) {
|
int verticalDistance(int y) {
|
||||||
return IrisObjectVacuum.outset(y, minInfluenceY, maxInfluenceY);
|
return IrisObjectVacuum.outset(y, minInfluenceY, maxInfluenceY);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private record SurfaceMaterials(BlockState surface, BlockState subsurface) {
|
private record SurfaceMaterials(BlockState surface, BlockState subsurface) {
|
||||||
|
|||||||
+405
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -114,7 +114,6 @@ public final class NativeStructureTerrainIntegrator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mode == IrisStructureTerrainMode.VACUUM) {
|
if (mode == IrisStructureTerrainMode.VACUUM) {
|
||||||
carvePieceBoxes(world, area, start, terrain);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mode == IrisStructureTerrainMode.ENCASE) {
|
if (mode == IrisStructureTerrainMode.ENCASE) {
|
||||||
@@ -267,6 +266,7 @@ public final class NativeStructureTerrainIntegrator {
|
|||||||
}
|
}
|
||||||
IrisStructureTerrainMode mode = terrain.resolvedMode();
|
IrisStructureTerrainMode mode = terrain.resolvedMode();
|
||||||
return mode == IrisStructureTerrainMode.ENCASE
|
return mode == IrisStructureTerrainMode.ENCASE
|
||||||
|
|| mode == IrisStructureTerrainMode.VACUUM
|
||||||
|| mode == IrisStructureTerrainMode.SOURCE
|
|| mode == IrisStructureTerrainMode.SOURCE
|
||||||
&& start.getStructure().terrainAdaptation() != TerrainAdjustment.NONE;
|
&& start.getStructure().terrainAdaptation() != TerrainAdjustment.NONE;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -360,7 +360,7 @@ final class ModdedNativeStructureStage {
|
|||||||
world, area, terrainTargets, this::resolvePaletteBlock);
|
world, area, terrainTargets, this::resolvePaletteBlock);
|
||||||
} catch (Throwable error) {
|
} catch (Throwable error) {
|
||||||
throw NativeStructureGenerationException.failure(
|
throw NativeStructureGenerationException.failure(
|
||||||
"terrain carving", nativeStructureBatchContext(placementGroups),
|
"terrain preparation", nativeStructureBatchContext(placementGroups),
|
||||||
chunkPos.x(), chunkPos.z(), error);
|
chunkPos.x(), chunkPos.z(), error);
|
||||||
}
|
}
|
||||||
for (NativePlacementGroup group : placementGroups) {
|
for (NativePlacementGroup group : placementGroups) {
|
||||||
|
|||||||
+2
@@ -63,6 +63,8 @@ public class NativeStructureFailureContractTest {
|
|||||||
String placement = source.substring(placementStart, placementEnd);
|
String placement = source.substring(placementStart, placementEnd);
|
||||||
|
|
||||||
assertTrue(placement.contains("\"terrain integration\""));
|
assertTrue(placement.contains("\"terrain integration\""));
|
||||||
|
assertTrue(placement.contains("\"terrain preparation\""));
|
||||||
|
assertFalse(placement.contains("\"terrain carving\""));
|
||||||
assertTrue(placement.contains("prepareSurfaceStructures"));
|
assertTrue(placement.contains("prepareSurfaceStructures"));
|
||||||
assertTrue(placement.contains("clearIntersectingVegetation"));
|
assertTrue(placement.contains("clearIntersectingVegetation"));
|
||||||
assertTrue(placement.indexOf("clearIntersectingVegetation")
|
assertTrue(placement.indexOf("clearIntersectingVegetation")
|
||||||
|
|||||||
+51
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3205,7 +3205,7 @@ public final class DatapackIngestService {
|
|||||||
}
|
}
|
||||||
int transactionCount = 0;
|
int transactionCount = 0;
|
||||||
for (Path transactionRoot : transactionRoots) {
|
for (Path transactionRoot : transactionRoots) {
|
||||||
if (isHarmlessTransactionArtifact(transactionRoot)) {
|
if (isHarmlessRecoveryArtifact(transactionRoot)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
transactionCount++;
|
transactionCount++;
|
||||||
@@ -3214,7 +3214,7 @@ public final class DatapackIngestService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Path transactionRoot : transactionRoots) {
|
for (Path transactionRoot : transactionRoots) {
|
||||||
if (isHarmlessTransactionArtifact(transactionRoot)) {
|
if (isHarmlessRecoveryArtifact(transactionRoot)) {
|
||||||
Files.deleteIfExists(transactionRoot);
|
Files.deleteIfExists(transactionRoot);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -3242,15 +3242,25 @@ public final class DatapackIngestService {
|
|||||||
verifyDirectoryContainerIfPresent(scratchRoot.toFile(), "datapack install scratch");
|
verifyDirectoryContainerIfPresent(scratchRoot.toFile(), "datapack install scratch");
|
||||||
List<Path> children;
|
List<Path> children;
|
||||||
try (Stream<Path> paths = Files.list(scratchRoot)) {
|
try (Stream<Path> paths = Files.list(scratchRoot)) {
|
||||||
children = paths.limit(MAX_MANAGED_PATHS + 1L).sorted().toList();
|
children = paths.limit(MAX_MANAGED_PATHS + 2L).sorted().toList();
|
||||||
}
|
}
|
||||||
if (children.size() > MAX_MANAGED_PATHS) {
|
int managedEntries = 0;
|
||||||
|
for (Path child : children) {
|
||||||
|
if (!isHarmlessRecoveryArtifact(child)) {
|
||||||
|
managedEntries++;
|
||||||
|
}
|
||||||
|
if (managedEntries > MAX_MANAGED_PATHS) {
|
||||||
throw new IOException("Datapack install scratch contains too many entries");
|
throw new IOException("Datapack install scratch contains too many entries");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<StagingScratch> pending = new ArrayList<>();
|
List<StagingScratch> pending = new ArrayList<>();
|
||||||
List<StagingScratch> backups = new ArrayList<>();
|
List<StagingScratch> backups = new ArrayList<>();
|
||||||
for (Path child : children) {
|
for (Path child : children) {
|
||||||
|
if (isHarmlessRecoveryArtifact(child)) {
|
||||||
|
Files.deleteIfExists(child);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
StagingScratch scratch = parseInstallScratch(scratchRoot, child);
|
StagingScratch scratch = parseInstallScratch(scratchRoot, child);
|
||||||
if (scratch == null) {
|
if (scratch == null) {
|
||||||
throw new IOException("Unexpected datapack install scratch artifact " + child);
|
throw new IOException("Unexpected datapack install scratch artifact " + child);
|
||||||
@@ -3429,12 +3439,12 @@ public final class DatapackIngestService {
|
|||||||
return ownership;
|
return ownership;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isHarmlessTransactionArtifact(Path path) throws IOException {
|
private static boolean isHarmlessRecoveryArtifact(Path path) throws IOException {
|
||||||
if (!".DS_Store".equals(path.getFileName().toString())) {
|
if (!".DS_Store".equals(path.getFileName().toString())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (Files.isSymbolicLink(path) || !Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS)) {
|
if (Files.isSymbolicLink(path) || !Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
throw new IOException("Suspicious datapack transaction artifact " + path);
|
throw new IOException("Suspicious datapack recovery artifact " + path);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ final class PackStructurePlacementValidator {
|
|||||||
registries.hooks(), blockingErrors);
|
registries.hooks(), blockingErrors);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
validateEditableStructureTerrain(placementPath, placement, blockingErrors);
|
||||||
Set<String> editableStructureKeys = new HashSet<>();
|
Set<String> editableStructureKeys = new HashSet<>();
|
||||||
for (int referenceIndex = 0; referenceIndex < references.length(); referenceIndex++) {
|
for (int referenceIndex = 0; referenceIndex < references.length(); referenceIndex++) {
|
||||||
Object rawReference = references.opt(referenceIndex);
|
Object rawReference = references.opt(referenceIndex);
|
||||||
@@ -650,6 +651,20 @@ final class PackStructurePlacementValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void validateEditableStructureTerrain(
|
||||||
|
String path, JSONObject placement, List<String> blockingErrors) {
|
||||||
|
JSONObject terrain = placement.optJSONObject("terrain");
|
||||||
|
if (terrain == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String mode = terrain.optString("mode", "SOURCE");
|
||||||
|
if ("VACUUM".equals(mode) || "ENCASE".equals(mode)) {
|
||||||
|
blockingErrors.add(path + ".terrain.mode " + mode
|
||||||
|
+ " cannot target editable Iris structures; use nativeStructures or "
|
||||||
|
+ "importedStructures.adjustments for native terrain preparation.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void validateStructureStartPools(File structuresFolder,
|
static void validateStructureStartPools(File structuresFolder,
|
||||||
Set<String> poolKeys,
|
Set<String> poolKeys,
|
||||||
List<String> blockingErrors) {
|
List<String> blockingErrors) {
|
||||||
|
|||||||
+26
@@ -118,6 +118,32 @@ public record NativeStructureOwnershipRecord(
|
|||||||
&& chunkZ >= referenceMinChunkZ && chunkZ <= referenceMaxChunkZ;
|
&& chunkZ >= referenceMinChunkZ && chunkZ <= referenceMaxChunkZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NativeStructureOwnershipRecord withReferenceEnvelope(
|
||||||
|
int referenceMinChunkX, int referenceMaxChunkX,
|
||||||
|
int referenceMinChunkZ, int referenceMaxChunkZ) {
|
||||||
|
return new NativeStructureOwnershipRecord(
|
||||||
|
schema,
|
||||||
|
structureKey,
|
||||||
|
originChunkX,
|
||||||
|
originChunkZ,
|
||||||
|
placementIdentity,
|
||||||
|
baseY,
|
||||||
|
contentMinX,
|
||||||
|
contentMinY,
|
||||||
|
contentMinZ,
|
||||||
|
contentMaxX,
|
||||||
|
contentMaxY,
|
||||||
|
contentMaxZ,
|
||||||
|
locatorY,
|
||||||
|
referenceMinChunkX,
|
||||||
|
referenceMaxChunkX,
|
||||||
|
referenceMinChunkZ,
|
||||||
|
referenceMaxChunkZ,
|
||||||
|
contentFingerprint,
|
||||||
|
decision
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public OwnershipKey ownershipKey() {
|
public OwnershipKey ownershipKey() {
|
||||||
return new OwnershipKey(structureKey, originChunkX, originChunkZ);
|
return new OwnershipKey(structureKey, originChunkX, originChunkZ);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public class IrisStructurePlacement {
|
|||||||
@Desc("If true, the structure starts at a deterministic random world Y inside [minHeight, maxHeight]. Terrain integration is then controlled independently by terrain.")
|
@Desc("If true, the structure starts at a deterministic random world Y inside [minHeight, maxHeight]. Terrain integration is then controlled independently by terrain.")
|
||||||
private boolean underground = false;
|
private boolean underground = false;
|
||||||
|
|
||||||
@Desc("Terrain integration shared by editable Iris assemblies and live registered structures.")
|
@Desc("Terrain integration for this placement. The editable structures backend supports SOURCE, PRESERVE, BORE, and FORCE_CARVE. The nativeStructures backend supports every terrain mode.")
|
||||||
private IrisStructureTerrain terrain = new IrisStructureTerrain();
|
private IrisStructureTerrain terrain = new IrisStructureTerrain();
|
||||||
|
|
||||||
@Desc("Optional foundation columns placed beneath the assembled structure's occupied bottom cells. Columns pass through air and fluids until they reach solid ground, up to maxDepth.")
|
@Desc("Optional foundation columns placed beneath the assembled structure's occupied bottom cells. Columns pass through air and fluids until they reach solid ground, up to maxDepth.")
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ public class IrisStructureTerrain {
|
|||||||
private static final double MAX_EROSION_FREQUENCY = 1D;
|
private static final double MAX_EROSION_FREQUENCY = 1D;
|
||||||
private static final double MAX_LOBE_FREQUENCY = 1D;
|
private static final double MAX_LOBE_FREQUENCY = 1D;
|
||||||
|
|
||||||
@Desc("Terrain operation. SOURCE applies the registered native structure's authored terrain adaptation and is a no-op for editable Iris structures. PRESERVE disables terrain integration. FORCE_CARVE clears the requested envelope, while ENCASE fills it before placement so native shells are not lost to pre-carved air.")
|
@Desc("Terrain operation. SOURCE applies the registered native structure's authored terrain adaptation and is a no-op for editable Iris structures. PRESERVE disables terrain integration. VACUUM forces a 12-block surface bend to every rigid native piece base. BORE and FORCE_CARVE clear the requested envelope, while ENCASE fills it before placement so native shells are not lost to pre-carved air.")
|
||||||
private IrisStructureTerrainMode mode = IrisStructureTerrainMode.SOURCE;
|
private IrisStructureTerrainMode mode = IrisStructureTerrainMode.SOURCE;
|
||||||
|
|
||||||
@MinNumber(0)
|
@MinNumber(0)
|
||||||
@MaxNumber(128)
|
@MaxNumber(128)
|
||||||
@Desc("Horizontal clearance around the assembled pieces.")
|
@Desc("Horizontal clearance around the assembled pieces for BORE, FORCE_CARVE, and ENCASE. VACUUM uses its fixed 12-block terrain falloff.")
|
||||||
private int horizontalPadding = 0;
|
private int horizontalPadding = 0;
|
||||||
|
|
||||||
@MinNumber(0)
|
@MinNumber(0)
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ public enum IrisStructureTerrainMode {
|
|||||||
PRESERVE,
|
PRESERVE,
|
||||||
BORE,
|
BORE,
|
||||||
FORCE_CARVE,
|
FORCE_CARVE,
|
||||||
|
|
||||||
|
@Desc("Bends surface terrain to every rigid piece base with a 12-block falloff, even when the registered structure has no authored terrain adaptation. Terrain is raised or lowered rather than carved away.")
|
||||||
VACUUM,
|
VACUUM,
|
||||||
|
|
||||||
@Desc("Fills the padded piece volume with solid blocks before any piece is placed so shells, walls, and floors land in solid ground instead of pre-carved air. Only air and liquid cells are filled; existing terrain and structures are never overwritten. Native pieces then carve their own interiors.")
|
@Desc("Fills the padded piece volume with solid blocks before any piece is placed so shells, walls, and floors land in solid ground instead of pre-carved air. Only air and liquid cells are filled; existing terrain and structures are never overwritten. Native pieces then carve their own interiors.")
|
||||||
|
|||||||
+1
-1
@@ -57,7 +57,7 @@ public class IrisVanillaStructureAdjustment {
|
|||||||
@Desc("Optional foundation columns placed beneath the native structure piece bases after placement.")
|
@Desc("Optional foundation columns placed beneath the native structure piece bases after placement.")
|
||||||
private IrisStructureStiltSettings stilt = null;
|
private IrisStructureStiltSettings stilt = null;
|
||||||
|
|
||||||
@Desc("Optional terrain integration override. FORCE_CARVE clears every intersecting chunk before native pieces are placed, ENCASE fills it with solid blocks instead. Left unset, SOURCE replays the registered structure's authored terrain adaptation, including surface fitting, burial, and encapsulation.")
|
@Desc("Optional terrain integration override. VACUUM forces surface terrain to bend to every rigid native piece base even when the structure did not author terrain adaptation. BORE and FORCE_CARVE clear every intersecting chunk before native pieces are placed, while ENCASE fills it with solid blocks instead. Left unset, SOURCE replays the registered structure's authored terrain adaptation, including surface fitting, burial, and encapsulation.")
|
||||||
private IrisStructureTerrain terrain = null;
|
private IrisStructureTerrain terrain = null;
|
||||||
|
|
||||||
public boolean matches(String key) {
|
public boolean matches(String key) {
|
||||||
|
|||||||
@@ -1714,6 +1714,41 @@ public class DatapackIngestServiceTest {
|
|||||||
assertFalse(scratch.exists());
|
assertFalse(scratch.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void recoveryRemovesFinderMetadataFromInstallScratch() throws Exception {
|
||||||
|
File root = temporaryFolder.newFolder("orphan-install-finder-metadata-root");
|
||||||
|
File scratch = new File(root, ".iris-datapack-install");
|
||||||
|
assertTrue(scratch.mkdirs());
|
||||||
|
File pending = new File(scratch, "managed-" + UUID.randomUUID());
|
||||||
|
assertTrue(pending.mkdirs());
|
||||||
|
Files.writeString(new File(pending, "partial.dat").toPath(), "partial", StandardCharsets.UTF_8);
|
||||||
|
File metadata = new File(scratch, ".DS_Store");
|
||||||
|
Files.writeString(metadata.toPath(), "finder", StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
DatapackIngestService.recoverTransactions(root, List.of());
|
||||||
|
|
||||||
|
assertFalse(metadata.exists());
|
||||||
|
assertFalse(pending.exists());
|
||||||
|
assertFalse(scratch.exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void recoveryRejectsFinderMetadataDirectoryInInstallScratch() throws Exception {
|
||||||
|
File root = temporaryFolder.newFolder("orphan-install-finder-directory-root");
|
||||||
|
File scratch = new File(root, ".iris-datapack-install");
|
||||||
|
assertTrue(scratch.mkdirs());
|
||||||
|
File metadata = new File(scratch, ".DS_Store");
|
||||||
|
assertTrue(metadata.mkdirs());
|
||||||
|
|
||||||
|
try {
|
||||||
|
DatapackIngestService.recoverTransactions(root, List.of());
|
||||||
|
fail("Expected suspicious Finder metadata to block recovery");
|
||||||
|
} catch (IOException expected) {
|
||||||
|
assertTrue(expected.getMessage().contains("Suspicious datapack recovery artifact"));
|
||||||
|
}
|
||||||
|
assertTrue(metadata.isDirectory());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void recoveryPreservesAndBlocksOnAnUnjournaledInstallBackup() throws Exception {
|
public void recoveryPreservesAndBlocksOnAnUnjournaledInstallBackup() throws Exception {
|
||||||
File root = temporaryFolder.newFolder("orphan-install-backup-root");
|
File root = temporaryFolder.newFolder("orphan-install-backup-root");
|
||||||
|
|||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
package art.arcane.iris.core.pack;
|
||||||
|
|
||||||
|
import art.arcane.volmlib.util.json.JSONArray;
|
||||||
|
import art.arcane.volmlib.util.json.JSONObject;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class PackValidatorStructureTerrainBackendTest {
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rejectsNativeOnlyTerrainModesForEditablePlacementsAcrossEveryHost() throws Exception {
|
||||||
|
for (String mode : List.of("VACUUM", "ENCASE")) {
|
||||||
|
String folderName = "editable-" + mode.toLowerCase(Locale.ROOT);
|
||||||
|
File pack = temporaryFolder.newFolder(folderName);
|
||||||
|
writePlacement(pack, "dimensions/main.json", "dimension-" + folderName,
|
||||||
|
"dimension_city", false, mode);
|
||||||
|
writePlacement(pack, "regions/forest.json", "region-" + folderName,
|
||||||
|
"forest_tower", false, mode);
|
||||||
|
writePlacement(pack, "biomes/plains.json", "biome-" + folderName,
|
||||||
|
"plains_farm", false, mode);
|
||||||
|
List<String> errors = new ArrayList<>();
|
||||||
|
|
||||||
|
PackStructurePlacementValidator.validateStructurePlacements(
|
||||||
|
pack, Set.of("dimension_city", "forest_tower", "plains_farm"), false, errors);
|
||||||
|
|
||||||
|
String suffix = ".terrain.mode " + mode + " cannot target editable Iris structures; "
|
||||||
|
+ "use nativeStructures or importedStructures.adjustments for native terrain preparation.";
|
||||||
|
assertEquals(List.of(
|
||||||
|
"Dimension 'main' structures[0]" + suffix,
|
||||||
|
"Region 'forest' structures[0]" + suffix,
|
||||||
|
"Biome 'plains' structures[0]" + suffix
|
||||||
|
), errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void acceptsNativeOnlyTerrainModesForNativeStructurePlacements() throws Exception {
|
||||||
|
for (String mode : List.of("VACUUM", "ENCASE")) {
|
||||||
|
File pack = temporaryFolder.newFolder("native-" + mode.toLowerCase(Locale.ROOT));
|
||||||
|
writePlacement(pack, "dimensions/main.json", "native-" + mode,
|
||||||
|
"minecraft:village_plains", true, mode);
|
||||||
|
List<String> errors = new ArrayList<>();
|
||||||
|
|
||||||
|
PackStructurePlacementValidator.validateStructurePlacements(pack, Set.of(), false, errors);
|
||||||
|
|
||||||
|
assertTrue(mode + ": " + errors, errors.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void acceptsSupportedTerrainModesForEditablePlacements() throws Exception {
|
||||||
|
for (String mode : List.of("SOURCE", "PRESERVE", "BORE", "FORCE_CARVE")) {
|
||||||
|
File pack = temporaryFolder.newFolder("editable-" + mode.toLowerCase(Locale.ROOT));
|
||||||
|
writePlacement(pack, "dimensions/main.json", "editable-" + mode,
|
||||||
|
"dimension_city", false, mode);
|
||||||
|
List<String> errors = new ArrayList<>();
|
||||||
|
|
||||||
|
PackStructurePlacementValidator.validateStructurePlacements(
|
||||||
|
pack, Set.of("dimension_city"), false, errors);
|
||||||
|
|
||||||
|
assertTrue(mode + ": " + errors, errors.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void acceptsNativeOnlyTerrainModesForImportedStructureAdjustments() {
|
||||||
|
for (String mode : List.of("VACUUM", "ENCASE")) {
|
||||||
|
JSONObject policy = new JSONObject().put("adjustments", new JSONArray().put(
|
||||||
|
new JSONObject()
|
||||||
|
.put("match", new JSONArray().put("towns_and_towers:"))
|
||||||
|
.put("terrain", new JSONObject().put("mode", mode))));
|
||||||
|
List<String> errors = new ArrayList<>();
|
||||||
|
|
||||||
|
PackDimensionValidator.validateImportedStructurePolicy(
|
||||||
|
"overworld", new JSONObject().put("importedStructures", policy), errors);
|
||||||
|
|
||||||
|
assertTrue(mode + ": " + errors, errors.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writePlacement(File pack, String relativePath, String placementId,
|
||||||
|
String structureKey, boolean nativeStructure,
|
||||||
|
String terrainMode) throws Exception {
|
||||||
|
String source = nativeStructure
|
||||||
|
? "\"nativeStructures\":[{\"structure\":\"" + structureKey + "\"}]"
|
||||||
|
: "\"structures\":[\"" + structureKey + "\"]";
|
||||||
|
write(pack, relativePath, "{\"structures\":[{\"placementId\":\"" + placementId
|
||||||
|
+ "\"," + source + ",\"terrain\":{\"mode\":\"" + terrainMode + "\"}}]}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void write(File root, String relativePath, String content) throws Exception {
|
||||||
|
Path path = root.toPath().resolve(relativePath);
|
||||||
|
Files.createDirectories(path.getParent());
|
||||||
|
Files.writeString(path, content, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -156,6 +156,7 @@ public class SchemaBuilderParityTest {
|
|||||||
JSONObject terrainDefinition = schema.getJSONObject("definitions")
|
JSONObject terrainDefinition = schema.getJSONObject("definitions")
|
||||||
.getJSONObject(terrain.getString("$ref").substring("#/definitions/".length()));
|
.getJSONObject(terrain.getString("$ref").substring("#/definitions/".length()));
|
||||||
JSONObject terrainProperties = terrainDefinition.getJSONObject("properties");
|
JSONObject terrainProperties = terrainDefinition.getJSONObject("properties");
|
||||||
|
JSONObject terrainMode = terrainProperties.getJSONObject("mode");
|
||||||
JSONObject carveShape = terrainProperties.getJSONObject("shape");
|
JSONObject carveShape = terrainProperties.getJSONObject("shape");
|
||||||
JSONObject erosionStrength = terrainProperties.getJSONObject("erosionStrength");
|
JSONObject erosionStrength = terrainProperties.getJSONObject("erosionStrength");
|
||||||
JSONObject erosionFrequency = terrainProperties.getJSONObject("erosionFrequency");
|
JSONObject erosionFrequency = terrainProperties.getJSONObject("erosionFrequency");
|
||||||
@@ -163,10 +164,17 @@ public class SchemaBuilderParityTest {
|
|||||||
JSONObject lobeStrength = terrainProperties.getJSONObject("lobeStrength");
|
JSONObject lobeStrength = terrainProperties.getJSONObject("lobeStrength");
|
||||||
String carveShapeDefinition = carveShape.getString("$ref")
|
String carveShapeDefinition = carveShape.getString("$ref")
|
||||||
.substring("#/definitions/".length());
|
.substring("#/definitions/".length());
|
||||||
|
String terrainModeDefinition = terrainMode.getString("$ref")
|
||||||
|
.substring("#/definitions/".length());
|
||||||
|
|
||||||
assertTrue(properties.has("structures"));
|
assertTrue(properties.has("structures"));
|
||||||
assertTrue(properties.has("nativeStructures"));
|
assertTrue(properties.has("nativeStructures"));
|
||||||
assertTrue(properties.has("distribution"));
|
assertTrue(properties.has("distribution"));
|
||||||
|
assertTrue(terrain.getString("description").contains(
|
||||||
|
"The editable structures backend supports SOURCE, PRESERVE, BORE, and FORCE_CARVE. "
|
||||||
|
+ "The nativeStructures backend supports every terrain mode."));
|
||||||
|
assertEquals(List.of("SOURCE", "PRESERVE", "BORE", "FORCE_CARVE", "VACUUM", "ENCASE"),
|
||||||
|
oneOfValues(schema.getJSONObject("definitions"), terrainModeDefinition));
|
||||||
assertEquals(List.of("BOX", "ROUNDED", "ERODED"), oneOfValues(
|
assertEquals(List.of("BOX", "ROUNDED", "ERODED"), oneOfValues(
|
||||||
schema.getJSONObject("definitions"), carveShapeDefinition));
|
schema.getJSONObject("definitions"), carveShapeDefinition));
|
||||||
assertEquals(0D, erosionStrength.getDouble("minimum"), 0D);
|
assertEquals(0D, erosionStrength.getDouble("minimum"), 0D);
|
||||||
|
|||||||
+27
@@ -100,6 +100,33 @@ public class NativeStructureOwnershipRecordTest {
|
|||||||
assertEquals(83L, record.placementIdentity());
|
assertEquals(83L, record.placementIdentity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void referenceEnvelopeRefreshPreservesPersistedAuthority() {
|
||||||
|
NativeStructureOwnershipRecord original = record(
|
||||||
|
"nova_structures:tavern_oak", -3, 9, 83L);
|
||||||
|
|
||||||
|
NativeStructureOwnershipRecord refreshed = original.withReferenceEnvelope(
|
||||||
|
-6, 0, 6, 12);
|
||||||
|
|
||||||
|
assertEquals(original.schema(), refreshed.schema());
|
||||||
|
assertEquals(original.ownershipKey(), refreshed.ownershipKey());
|
||||||
|
assertEquals(original.placementIdentity(), refreshed.placementIdentity());
|
||||||
|
assertEquals(original.baseY(), refreshed.baseY());
|
||||||
|
assertEquals(original.contentMinX(), refreshed.contentMinX());
|
||||||
|
assertEquals(original.contentMinY(), refreshed.contentMinY());
|
||||||
|
assertEquals(original.contentMinZ(), refreshed.contentMinZ());
|
||||||
|
assertEquals(original.contentMaxX(), refreshed.contentMaxX());
|
||||||
|
assertEquals(original.contentMaxY(), refreshed.contentMaxY());
|
||||||
|
assertEquals(original.contentMaxZ(), refreshed.contentMaxZ());
|
||||||
|
assertEquals(original.locatorY(), refreshed.locatorY());
|
||||||
|
assertEquals(original.contentFingerprint(), refreshed.contentFingerprint());
|
||||||
|
assertEquals(original.decision(), refreshed.decision());
|
||||||
|
assertEquals(-6, refreshed.referenceMinChunkX());
|
||||||
|
assertEquals(0, refreshed.referenceMaxChunkX());
|
||||||
|
assertEquals(6, refreshed.referenceMinChunkZ());
|
||||||
|
assertEquals(12, refreshed.referenceMaxChunkZ());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void ownershipRoundTripPreservesExactBoundsAndPriority() throws Exception {
|
public void ownershipRoundTripPreservesExactBoundsAndPriority() throws Exception {
|
||||||
NativeStructureOwnershipRecord ownership = record(
|
NativeStructureOwnershipRecord ownership = record(
|
||||||
|
|||||||
+29
@@ -202,6 +202,35 @@ public class IrisImportedStructureControlTest {
|
|||||||
assertNull(control.resolve("minecraft:trial_chambers", true).yBand());
|
assertNull(control.resolve("minecraft:trial_chambers", true).yBand());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void namespaceVacuumCanPreserveSpecificStructuresLater() {
|
||||||
|
IrisVanillaStructureAdjustment namespace = new IrisVanillaStructureAdjustment()
|
||||||
|
.setMatch(keys("towns_and_towers:"))
|
||||||
|
.setTerrain(new IrisStructureTerrain().setMode(IrisStructureTerrainMode.VACUUM));
|
||||||
|
IrisVanillaStructureAdjustment preserveShips = new IrisVanillaStructureAdjustment()
|
||||||
|
.setMatch(keys(
|
||||||
|
"towns_and_towers:mimic_desert",
|
||||||
|
"towns_and_towers:pillager_outpost_ocean",
|
||||||
|
"towns_and_towers:village_ocean",
|
||||||
|
"towns_and_towers:wreckage_ocean"))
|
||||||
|
.setTerrain(new IrisStructureTerrain().setMode(IrisStructureTerrainMode.PRESERVE));
|
||||||
|
KList<IrisVanillaStructureAdjustment> adjustments = new KList<>();
|
||||||
|
adjustments.add(namespace);
|
||||||
|
adjustments.add(preserveShips);
|
||||||
|
IrisImportedStructureControl control = new IrisImportedStructureControl()
|
||||||
|
.setAdjustments(adjustments);
|
||||||
|
|
||||||
|
assertEquals(IrisStructureTerrainMode.VACUUM,
|
||||||
|
control.resolve("towns_and_towers:village_forest", false)
|
||||||
|
.terrain().resolvedMode());
|
||||||
|
assertEquals(IrisStructureTerrainMode.PRESERVE,
|
||||||
|
control.resolve("towns_and_towers:village_ocean", false)
|
||||||
|
.terrain().resolvedMode());
|
||||||
|
assertEquals(IrisStructureTerrainMode.PRESERVE,
|
||||||
|
control.resolve("towns_and_towers:mimic_desert", true)
|
||||||
|
.terrain().resolvedMode());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preserveSourceYMatchesTheStructureFamilyAndKeepsExplicitShifts() {
|
public void preserveSourceYMatchesTheStructureFamilyAndKeepsExplicitShifts() {
|
||||||
IrisVanillaStructureAdjustment adjustment = new IrisVanillaStructureAdjustment()
|
IrisVanillaStructureAdjustment adjustment = new IrisVanillaStructureAdjustment()
|
||||||
|
|||||||
Reference in New Issue
Block a user