mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-29 05:20:40 +00:00
Vwop
This commit is contained in:
+111
-56
@@ -4,7 +4,7 @@ import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.GenerationSessionException;
|
||||
import art.arcane.iris.engine.framework.GenerationSessionLease;
|
||||
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
||||
import art.arcane.iris.engine.framework.NativeStructurePlacementPlanner;
|
||||
import art.arcane.iris.engine.framework.NativeStructureOwnershipRecord;
|
||||
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
|
||||
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
@@ -14,11 +14,15 @@ import art.arcane.iris.nativegen.NativeStructureGenerationException;
|
||||
import art.arcane.iris.nativegen.NativeStructureStartInjector;
|
||||
import art.arcane.iris.nativegen.NativeStructureReferenceEnvelope;
|
||||
import art.arcane.iris.nativegen.NativeStructureLocateResults;
|
||||
import art.arcane.iris.nativegen.NativeStructureLocatePersistence;
|
||||
import art.arcane.iris.nativegen.NativeStructureOwnershipRecovery;
|
||||
import art.arcane.iris.nativegen.NativeStructurePostProcessor;
|
||||
import art.arcane.iris.nativegen.NativeStructureReferenceRepair;
|
||||
import art.arcane.iris.nativegen.NativeStructureSurfaceFitter;
|
||||
import art.arcane.iris.nativegen.NativeStructureTerrainIntegrator;
|
||||
import art.arcane.iris.nativegen.NativeStructureVegetationClearer;
|
||||
import art.arcane.iris.nativegen.NativeStructureVerticalPlacer;
|
||||
import art.arcane.iris.nativegen.NativeStructureVanillaLocator;
|
||||
import art.arcane.iris.nativegen.WorldgenTerrainHeightmaps;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.common.data.IrisCustomData;
|
||||
@@ -87,6 +91,7 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -131,55 +136,99 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
|
||||
try (GenerationSessionLease lease = requireGenerationLease("bukkit_nms_structure_locate");
|
||||
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
|
||||
Pair<BlockPos, Holder<Structure>> irisPlaced = findNearestIrisStructure(
|
||||
level, holders, pos, Math.max(1, radius), findUnexplored);
|
||||
HolderSet<Structure> reachable = filterReachableStructures(level, holders);
|
||||
Pair<BlockPos, Holder<Structure>> nativeLocated = reachable == null || reachable.size() == 0
|
||||
? null
|
||||
: delegate.findNearestMapStructure(level, reachable, pos, radius, findUnexplored);
|
||||
return NativeStructureLocateResults.nearest(pos, irisPlaced, nativeLocated);
|
||||
NativeStructureVanillaLocator.Candidate nativeCandidate =
|
||||
reachable == null || reachable.size() == 0 ? null
|
||||
: NativeStructureVanillaLocator.predict(
|
||||
level, reachable, pos, radius, findUnexplored);
|
||||
return findNearestIrisStructure(
|
||||
level, holders, pos, Math.max(0, radius),
|
||||
findUnexplored, nativeCandidate);
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<BlockPos, Holder<Structure>> findNearestIrisStructure(ServerLevel level,
|
||||
HolderSet<Structure> holders,
|
||||
BlockPos pos, int radius,
|
||||
boolean findUnexplored) {
|
||||
if (findUnexplored) {
|
||||
return null;
|
||||
}
|
||||
boolean findUnexplored,
|
||||
NativeStructureVanillaLocator.Candidate nativeCandidate) {
|
||||
Pair<BlockPos, Holder<Structure>> nativeLocated =
|
||||
nativeCandidate == null ? null : nativeCandidate.result();
|
||||
Runnable nativeReference = () -> {
|
||||
if (nativeCandidate != null) {
|
||||
nativeCandidate.reference(level.structureManager());
|
||||
}
|
||||
};
|
||||
Registry<Structure> registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||
BlockPos best = null;
|
||||
Holder<Structure> bestHolder = null;
|
||||
long bestDist = Long.MAX_VALUE;
|
||||
List<IrisNativeLocateSearch> searches = new ArrayList<>(holders.size());
|
||||
NativeStructureLocatePersistence.ProbeBudget budget = NativeStructureLocatePersistence.probeBudget();
|
||||
for (Holder<Structure> holder : holders) {
|
||||
Object id = registry.getKey(holder.value());
|
||||
if (id == null) {
|
||||
throw new IllegalStateException("Native structure locate received an unregistered structure holder");
|
||||
}
|
||||
String structureId = id.toString();
|
||||
if (!IrisStructureLocator.isPlaced(engine, structureId)) {
|
||||
if (!IrisStructureLocator.hasNativePlacement(engine, structureId)) {
|
||||
continue;
|
||||
}
|
||||
IrisStructureLocator.LocateResult result = IrisStructureLocator.locate(
|
||||
engine, structureId, pos.getX(), pos.getZ(), radius);
|
||||
if (result.status() == IrisStructureLocator.LocateStatus.SEARCH_LIMIT_REACHED) {
|
||||
throw new IllegalStateException("Iris structure locate reached its safety limit for "
|
||||
+ structureId + " within " + radius + " chunks");
|
||||
}
|
||||
if (!result.found()) {
|
||||
continue;
|
||||
}
|
||||
long dx = (long) result.originX() - pos.getX();
|
||||
long dz = (long) result.originZ() - pos.getZ();
|
||||
long d = dx * dx + dz * dz;
|
||||
if (d < bestDist) {
|
||||
bestDist = d;
|
||||
best = new BlockPos(result.originX(), result.baseY(), result.originZ());
|
||||
bestHolder = holder;
|
||||
}
|
||||
NativeStructureLocatePersistence.Probe probe = NativeStructureLocatePersistence.probe(
|
||||
level, holder.value(), findUnexplored, budget);
|
||||
searches.add(new IrisNativeLocateSearch(
|
||||
holder, structureId, NativeStructureLocatePersistence.search(
|
||||
engine, structureId, pos.getX(), pos.getZ(), radius, probe)));
|
||||
}
|
||||
return best == null ? null : Pair.of(best, bestHolder);
|
||||
searches.sort(Comparator.comparing(IrisNativeLocateSearch::structureId));
|
||||
for (int attempt = 0; attempt < NativeStructureLocatePersistence.MAX_SELECTED_CANDIDATE_RETRIES; attempt++) {
|
||||
IrisNativeLocateSearch bestSearch = null;
|
||||
IrisStructureLocator.LocateResult bestResult = null;
|
||||
long bestDistance = Long.MAX_VALUE;
|
||||
for (IrisNativeLocateSearch search : searches) {
|
||||
IrisStructureLocator.LocateResult result = search.search().predict();
|
||||
if (result.status() == IrisStructureLocator.LocateStatus.SEARCH_LIMIT_REACHED) {
|
||||
throw new IllegalStateException("Iris structure locate reached its safety limit for "
|
||||
+ search.structureId() + " within " + radius + " placement rings");
|
||||
}
|
||||
if (!result.found()) {
|
||||
continue;
|
||||
}
|
||||
long dx = (long) result.originX() - pos.getX();
|
||||
long dz = (long) result.originZ() - pos.getZ();
|
||||
long distance = dx * dx + dz * dz;
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance;
|
||||
bestSearch = search;
|
||||
bestResult = result;
|
||||
}
|
||||
}
|
||||
if (bestSearch == null) {
|
||||
return NativeStructureLocateResults.selectAndReference(
|
||||
pos, null, () -> { }, nativeLocated, nativeReference);
|
||||
}
|
||||
Pair<BlockPos, Holder<Structure>> predicted = Pair.of(
|
||||
new BlockPos(bestResult.originX(), bestResult.baseY(), bestResult.originZ()),
|
||||
bestSearch.holder());
|
||||
if (NativeStructureLocateResults.nearest(pos, predicted, nativeLocated) != predicted) {
|
||||
return NativeStructureLocateResults.selectAndReference(
|
||||
pos, predicted, () -> { }, nativeLocated, nativeReference);
|
||||
}
|
||||
NativeStructureLocatePersistence.VerifiedStart verified =
|
||||
bestSearch.search().verify(bestResult);
|
||||
if (verified == null) {
|
||||
bestSearch.search().reject(bestResult);
|
||||
continue;
|
||||
}
|
||||
BlockPos located = new BlockPos(
|
||||
bestResult.originX(), verified.ownership().locatorY(),
|
||||
bestResult.originZ());
|
||||
Pair<BlockPos, Holder<Structure>> irisLocated = Pair.of(located, bestSearch.holder());
|
||||
IrisNativeLocateSearch selectedSearch = bestSearch;
|
||||
NativeStructureLocatePersistence.VerifiedStart selectedStart = verified;
|
||||
return NativeStructureLocateResults.selectAndReference(
|
||||
pos, irisLocated, () -> selectedSearch.search().reference(selectedStart),
|
||||
nativeLocated, nativeReference);
|
||||
}
|
||||
throw new IllegalStateException("Iris structure locate rejected too many selected candidates within "
|
||||
+ radius + " placement rings");
|
||||
}
|
||||
|
||||
private HolderSet<Structure> filterReachableStructures(ServerLevel level, HolderSet<Structure> holders) {
|
||||
@@ -323,9 +372,10 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
decision.preserveSourceY(),
|
||||
decision.yBand(),
|
||||
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
|
||||
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
|
||||
start, structure, start.getReferences(), templateManager,
|
||||
NativeStructureTerrainIntegrator.resolveNativeTerrain(start, decision.terrain()));
|
||||
StructureStart wrapped = NativeStructureReferenceEnvelope.wrapForPublication(
|
||||
start, structure, start.getReferences(),
|
||||
NativeStructureTerrainIntegrator.resolveNativeTerrain(start, decision.terrain()),
|
||||
structureId);
|
||||
access.setStartForStructure(structure, wrapped);
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
@@ -341,7 +391,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
@Override
|
||||
public void createReferences(WorldGenLevel generatoraccessseed, StructureManager structuremanager, ChunkAccess ichunkaccess) {
|
||||
delegate.createReferences(generatoraccessseed, structuremanager, ichunkaccess);
|
||||
try (GenerationSessionLease lease = requireGenerationLease("bukkit_nms_create_references");
|
||||
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
|
||||
NativeStructureReferenceRepair.createReferences(
|
||||
engine, generatoraccessseed, structuremanager, ichunkaccess);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -474,7 +528,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
int steps = GenerationStep.Decoration.values().length;
|
||||
List<NativePlacementGroup> placementGroups = new ArrayList<>();
|
||||
List<StructureStart> heightmapStarts = new ArrayList<>();
|
||||
List<StructureStart> nativeStarts = new ArrayList<>();
|
||||
List<NativeStructureVegetationClearer.VegetationTarget> vegetationTargets = new ArrayList<>();
|
||||
List<NativeStructureTerrainIntegrator.TerrainTarget> terrainTargets = new ArrayList<>();
|
||||
for (int step = 0; step < steps; step++) {
|
||||
@@ -492,10 +545,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
List<StructureStart> starts = structureManager.startsForStructure(sectionPos, structure);
|
||||
List<NativePlacement> resolvedPlacements = new ArrayList<>(starts.size());
|
||||
for (StructureStart start : starts) {
|
||||
NativeStructureStartPlan plan = NativeStructurePlacementPlanner.matchingPlan(
|
||||
engine, structureId, start.getChunkPos().x(), start.getChunkPos().z());
|
||||
IrisNativeStructureDecision decision = plan == null
|
||||
? sourceDecision : NativeStructurePlacementPlanner.decisionFor(plan);
|
||||
NativeStructureOwnershipRecord ownership =
|
||||
NativeStructureOwnershipRecovery.resolve(
|
||||
engine, world.getLevel(), structureId, structure, start);
|
||||
IrisNativeStructureDecision decision =
|
||||
ownership == null ? sourceDecision : ownership.restoredDecision();
|
||||
if (!decision.generate()) {
|
||||
continue;
|
||||
}
|
||||
@@ -505,9 +559,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
structureId, start,
|
||||
NativeStructureTerrainIntegrator.resolveNativeTerrain(
|
||||
start, decision.terrain())));
|
||||
if (plan == null || !plan.placement().isUnderground()) {
|
||||
nativeStarts.add(start);
|
||||
}
|
||||
boolean clearEntireFootprint = NativeStructureVegetationClearer
|
||||
.shouldClearEntireVegetationFootprint(
|
||||
structure.step(), decision.clearVegetation());
|
||||
@@ -533,15 +584,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
"heightmap priming", nativeStructureBatchContext(placementGroups),
|
||||
chunkPos.x(), chunkPos.z(), error);
|
||||
}
|
||||
try {
|
||||
NativeStructureSurfaceFitter.prepareSurfaceStructures(
|
||||
world, area, nativeStarts,
|
||||
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"terrain integration", nativeStructureBatchContext(placementGroups),
|
||||
chunkPos.x(), chunkPos.z(), error);
|
||||
}
|
||||
try {
|
||||
NativeStructureVegetationClearer.clearIntersectingVegetation(
|
||||
world, chunk, area, vegetationTargets);
|
||||
@@ -550,6 +592,15 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
"vegetation cleanup", nativeStructureBatchContext(placementGroups),
|
||||
chunkPos.x(), chunkPos.z(), error);
|
||||
}
|
||||
try {
|
||||
NativeStructureSurfaceFitter.prepareSurfaceStructures(
|
||||
world, area, terrainTargets,
|
||||
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"terrain integration", nativeStructureBatchContext(placementGroups),
|
||||
chunkPos.x(), chunkPos.z(), error);
|
||||
}
|
||||
try {
|
||||
NativeStructurePostProcessor.prepareTerrain(
|
||||
world, area, terrainTargets, this::resolvePaletteBlock);
|
||||
@@ -641,8 +692,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
ChunkPos cp = chunk.getPos();
|
||||
int i = cp.getMinBlockX();
|
||||
int j = cp.getMinBlockZ();
|
||||
int minY = chunk.getMinY();
|
||||
int maxY = minY + chunk.getHeight() - 1;
|
||||
int minY = chunk.getMinY() + 1;
|
||||
int maxY = chunk.getMinY() + chunk.getHeight() - 1;
|
||||
return new BoundingBox(i, minY, j, i + 15, maxY, j + 15);
|
||||
}
|
||||
|
||||
@@ -827,4 +878,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
private record NativeLocateCandidate(Holder<Structure> holder, String key) {
|
||||
}
|
||||
|
||||
private record IrisNativeLocateSearch(Holder<Structure> holder, String structureId,
|
||||
NativeStructureLocatePersistence.Search search) {
|
||||
}
|
||||
}
|
||||
|
||||
+61
-2
@@ -15,7 +15,9 @@ import art.arcane.iris.engine.data.chunk.TerrainChunk;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisDimensionRuntimeContract;
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
import art.arcane.iris.nativegen.NativeStructureFactory;
|
||||
import art.arcane.iris.nativegen.NativeStructureGenerationException;
|
||||
import art.arcane.iris.spi.PlatformStructureHooks.JigsawSourceMetadata;
|
||||
import art.arcane.iris.util.project.agent.Agent;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
@@ -102,6 +104,7 @@ import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.WorldgenRandom;
|
||||
import net.minecraft.world.level.levelgen.XoroshiroRandomSource;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureCheck;
|
||||
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.JigsawStructure;
|
||||
import net.minecraft.world.level.levelgen.feature.AbstractHugeMushroomFeature;
|
||||
@@ -557,6 +560,62 @@ public class NMSBinding implements INMSBinding {
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JigsawSourceMetadata getJigsawSourceMetadata(String structureKey) {
|
||||
try {
|
||||
Identifier identifier = Identifier.tryParse(structureKey);
|
||||
if (identifier == null) {
|
||||
throw new IllegalArgumentException("Invalid registered structure key: " + structureKey);
|
||||
}
|
||||
Registry<Structure> structures = registry().lookupOrThrow(Registries.STRUCTURE);
|
||||
Structure structure = structures.getValue(identifier);
|
||||
if (structure == null) {
|
||||
throw new IllegalArgumentException("Registered structure does not exist: " + structureKey);
|
||||
}
|
||||
if (!(structure instanceof JigsawStructure jigsaw)) {
|
||||
throw new IllegalArgumentException("Registered structure is not a jigsaw: " + structureKey);
|
||||
}
|
||||
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getHandle().getServer();
|
||||
return NativeStructureFactory.sourceMetadata(
|
||||
registry(), server.getStructureManager(), jigsaw);
|
||||
} catch (RuntimeException error) {
|
||||
throw new IllegalStateException("Iris failed to resolve live jigsaw metadata for registered structure '"
|
||||
+ structureKey + "'", error);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTemplatePoolHorizontalSpan(String templatePoolKey) {
|
||||
try {
|
||||
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getHandle().getServer();
|
||||
return NativeStructureFactory.templatePoolHorizontalSpan(
|
||||
registry(), server.getStructureManager(), templatePoolKey);
|
||||
} catch (RuntimeException error) {
|
||||
throw new IllegalStateException("Iris failed to resolve the live horizontal span for registered "
|
||||
+ "template pool '" + templatePoolKey + "'", error);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getJigsawStartPoolHorizontalSpan(String structureKey, String templatePoolKey) {
|
||||
try {
|
||||
Identifier identifier = Identifier.tryParse(structureKey);
|
||||
if (identifier == null) {
|
||||
throw new IllegalArgumentException("Invalid registered structure key: " + structureKey);
|
||||
}
|
||||
Structure structure = registry().lookupOrThrow(Registries.STRUCTURE).getValue(identifier);
|
||||
if (!(structure instanceof JigsawStructure jigsaw)) {
|
||||
throw new IllegalArgumentException("Registered structure is not a jigsaw: " + structureKey);
|
||||
}
|
||||
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getHandle().getServer();
|
||||
return NativeStructureFactory.jigsawStartPoolHorizontalSpan(
|
||||
registry(), server.getStructureManager(), jigsaw, templatePoolKey);
|
||||
} catch (RuntimeException error) {
|
||||
throw new IllegalStateException("Iris failed to resolve the effective start-pool span for registered "
|
||||
+ "jigsaw structure '" + structureKey + "' and pool '" + templatePoolKey + "'", error);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public KList<String> getStructureSetKeys() {
|
||||
KList<String> keys = new KList<>();
|
||||
@@ -700,7 +759,7 @@ public class NMSBinding implements INMSBinding {
|
||||
return null;
|
||||
}
|
||||
|
||||
net.minecraft.world.level.levelgen.structure.BoundingBox box = start.getBoundingBox();
|
||||
BoundingBox box = start.getBoundingBox();
|
||||
int spanX = box.maxX() - box.minX() + 1;
|
||||
int spanY = box.maxY() - box.minY() + 1;
|
||||
int spanZ = box.maxZ() - box.minZ() + 1;
|
||||
@@ -717,7 +776,7 @@ public class NMSBinding implements INMSBinding {
|
||||
for (int cz = minCZ; cz <= maxCZ; cz++) {
|
||||
level.getChunk(cx, cz);
|
||||
net.minecraft.world.level.ChunkPos cp = new net.minecraft.world.level.ChunkPos(cx, cz);
|
||||
net.minecraft.world.level.levelgen.structure.BoundingBox chunkBox = new net.minecraft.world.level.levelgen.structure.BoundingBox(
|
||||
BoundingBox chunkBox = new BoundingBox(
|
||||
cp.getMinBlockX(), box.minY(), cp.getMinBlockZ(),
|
||||
cp.getMaxBlockX(), box.maxY(), cp.getMaxBlockZ());
|
||||
start.placeInChunk(level, structureManager, generator, random, chunkBox, cp);
|
||||
|
||||
+49
-2
@@ -49,9 +49,9 @@ public class IrisChunkGeneratorFailureContractTest {
|
||||
assertTrue(placement.contains("because structure generation is disabled outside the pack"));
|
||||
assertTrue(placement.contains("prepareSurfaceStructures"));
|
||||
assertTrue(placement.contains("clearIntersectingVegetation"));
|
||||
assertTrue(placement.indexOf("prepareSurfaceStructures")
|
||||
< placement.indexOf("clearIntersectingVegetation"));
|
||||
assertTrue(placement.indexOf("clearIntersectingVegetation")
|
||||
< placement.indexOf("prepareSurfaceStructures"));
|
||||
assertTrue(placement.indexOf("prepareSurfaceStructures")
|
||||
< placement.indexOf("for (NativePlacementGroup group"));
|
||||
assertFalse(placement.contains("IrisLogging.reportError"));
|
||||
}
|
||||
@@ -66,6 +66,20 @@ public class IrisChunkGeneratorFailureContractTest {
|
||||
assertTrue(source.contains("catch (GenerationSessionException e)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void structureReferenceRepairIsGenerationLeased() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
|
||||
int referencesStart = source.indexOf("public void createReferences");
|
||||
int referencesEnd = source.indexOf("public CompletableFuture<ChunkAccess> createBiomes", referencesStart);
|
||||
String references = source.substring(referencesStart, referencesEnd);
|
||||
|
||||
assertTrue(references.contains("requireGenerationLease(\"bukkit_nms_create_references\")"));
|
||||
assertTrue(references.contains("IrisContext.open(engine, lease.sessionId(), null)"));
|
||||
assertTrue(references.contains("NativeStructureReferenceRepair.createReferences("));
|
||||
assertFalse(references.contains("delegate.createReferences("));
|
||||
assertFalse(references.contains("catch ("));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void terrainWritesPrimeTheWorldgenHeightmapsForEveryChunk() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
|
||||
@@ -89,6 +103,7 @@ public class IrisChunkGeneratorFailureContractTest {
|
||||
assertTrue(placement.indexOf("WorldgenTerrainHeightmaps.primeStructurePlacement(")
|
||||
< placement.indexOf("prepareSurfaceStructures"));
|
||||
assertTrue(source.contains("engine.acquireGenerationLease(\"bukkit_nms_worldgen_heightmaps\")"));
|
||||
assertTrue(source.contains("int minY = chunk.getMinY() + 1;"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -112,6 +127,38 @@ public class IrisChunkGeneratorFailureContractTest {
|
||||
assertTrue(source.contains("import art.arcane.iris.nativegen.WorldgenTerrainHeightmaps;"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onlySidecarOwnedInjectedStartsUsePersistedPlacementPolicy() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
|
||||
int placementStart = source.indexOf("private void placeVanillaStructures");
|
||||
int placementEnd = source.indexOf("private static String nativeStructureBatchContext", placementStart);
|
||||
String placement = source.substring(placementStart, placementEnd);
|
||||
int ownershipResolution = placement.indexOf(
|
||||
"NativeStructureOwnershipRecovery.resolve(");
|
||||
int persistedDecision = placement.indexOf("ownership.restoredDecision()", ownershipResolution);
|
||||
int adjustmentStart = source.indexOf("private void adjustGeneratedStructures");
|
||||
int adjustmentEnd = source.indexOf("public ChunkGeneratorStructureState createState", adjustmentStart);
|
||||
String adjustment = source.substring(adjustmentStart, adjustmentEnd);
|
||||
Path nativegen = Path.of(System.getProperty("iris.nativeStructurePostProcessorSource")).getParent();
|
||||
String factory = Files.readString(nativegen.resolve("NativeStructureFactory.java"));
|
||||
String injector = Files.readString(nativegen.resolve("NativeStructureStartInjector.java"));
|
||||
String recovery = Files.readString(nativegen.resolve("NativeStructureOwnershipRecovery.java"));
|
||||
int ownershipRecord = injector.indexOf("NativeStructureOwnershipStore.record(");
|
||||
int startPublication = injector.indexOf(
|
||||
"context.structureManager().setStartForStructure(", ownershipRecord);
|
||||
|
||||
assertTrue(ownershipResolution >= 0);
|
||||
assertTrue(persistedDecision > ownershipResolution);
|
||||
assertTrue(recovery.contains("NativeStructureOwnershipStore.findPersisted("));
|
||||
assertTrue(recovery.contains("NativeStructureOwnershipStore.record("));
|
||||
assertTrue(ownershipRecord >= 0);
|
||||
assertTrue(startPublication > ownershipRecord);
|
||||
assertTrue(factory.contains("NativeStructureReferenceEnvelope.wrapForPublication("));
|
||||
assertTrue(adjustment.contains("NativeStructureReferenceEnvelope.wrapForPublication("));
|
||||
assertFalse(source.contains("wrapManaged("));
|
||||
assertFalse(source.contains("isIrisManagedStart("));
|
||||
}
|
||||
|
||||
private static int occurrences(String source, String needle) {
|
||||
int count = 0;
|
||||
int index = source.indexOf(needle);
|
||||
|
||||
+67
-13
@@ -10,7 +10,9 @@ import org.junit.Test;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
@@ -27,17 +29,19 @@ public class IrisChunkGeneratorMonumentLocateContractTest {
|
||||
assertTrue(filterStart > irisHelperStart);
|
||||
String outerMethod = source.substring(findStart, irisHelperStart);
|
||||
String irisHelper = source.substring(irisHelperStart, filterStart);
|
||||
int unexploredGuard = irisHelper.indexOf("if (findUnexplored)");
|
||||
int registryLookup = irisHelper.indexOf("level.registryAccess().lookupOrThrow(Registries.STRUCTURE)");
|
||||
int placedCheck = irisHelper.indexOf(
|
||||
"if (!IrisStructureLocator.isPlaced(engine, structureId))");
|
||||
int irisLocate = irisHelper.indexOf("IrisStructureLocator.locate(", placedCheck);
|
||||
"if (!IrisStructureLocator.hasNativePlacement(engine, structureId))");
|
||||
int irisLocate = irisHelper.indexOf("NativeStructureLocatePersistence.search(", placedCheck);
|
||||
int searchLimit = irisHelper.indexOf("LocateStatus.SEARCH_LIMIT_REACHED", irisLocate);
|
||||
int limitSkip = irisHelper.indexOf("continue;", searchLimit);
|
||||
int nativeFilter = outerMethod.indexOf("filterReachableStructures(level, holders)");
|
||||
int delegateLocate = outerMethod.indexOf("delegate.findNearestMapStructure(level, reachable");
|
||||
int nearestSelection = outerMethod.indexOf(
|
||||
"NativeStructureLocateResults.nearest(pos, irisPlaced, nativeLocated)");
|
||||
int nativePrediction = outerMethod.indexOf("NativeStructureVanillaLocator.predict(", nativeFilter);
|
||||
int irisResolution = outerMethod.indexOf("return findNearestIrisStructure(", nativePrediction);
|
||||
int nearestSelection = irisHelper.indexOf(
|
||||
"NativeStructureLocateResults.nearest(pos, predicted, nativeLocated)");
|
||||
int selectedVerification = irisHelper.indexOf("bestSearch.search().verify(bestResult)", nearestSelection);
|
||||
int selectedReference = irisHelper.indexOf(
|
||||
"selectedSearch.search().reference(selectedStart)", selectedVerification);
|
||||
int reachabilityStart = source.indexOf("private Set<String> reachableStructureKeys", filterStart);
|
||||
assertTrue(reachabilityStart > filterStart);
|
||||
String filterMethod = source.substring(filterStart, reachabilityStart);
|
||||
@@ -46,22 +50,28 @@ public class IrisChunkGeneratorMonumentLocateContractTest {
|
||||
int emptyNativePartition = filterMethod.indexOf("if (candidates.isEmpty())", filterContinue);
|
||||
int reachabilityLookup = filterMethod.indexOf("reachableStructureKeys(level)", emptyNativePartition);
|
||||
|
||||
assertTrue(unexploredGuard >= 0);
|
||||
assertTrue(registryLookup > unexploredGuard);
|
||||
assertTrue(registryLookup >= 0);
|
||||
assertTrue(placedCheck > registryLookup);
|
||||
assertTrue(irisLocate > placedCheck);
|
||||
assertTrue(searchLimit > irisLocate);
|
||||
assertTrue(limitSkip > searchLimit);
|
||||
assertTrue(nativeFilter >= 0);
|
||||
assertTrue(delegateLocate > nativeFilter);
|
||||
assertTrue(nearestSelection > delegateLocate);
|
||||
assertTrue(nativePrediction > nativeFilter);
|
||||
assertTrue(irisResolution > nativePrediction);
|
||||
assertTrue(nearestSelection > irisLocate);
|
||||
assertTrue(selectedVerification > nearestSelection);
|
||||
assertTrue(selectedReference > selectedVerification);
|
||||
assertTrue(policyFilter >= 0);
|
||||
assertTrue(filterContinue > policyFilter);
|
||||
assertTrue(emptyNativePartition > filterContinue);
|
||||
assertTrue(reachabilityLookup > emptyNativePartition);
|
||||
assertFalse(filterMethod.contains("NativeStructureLocateCapability"));
|
||||
assertFalse(irisHelper.contains("NativeStructureLocateCapability.isPaperUnavailable(structureId)"));
|
||||
assertTrue(irisHelper.contains("new BlockPos(result.originX(), result.baseY(), result.originZ())"));
|
||||
assertTrue(irisHelper.contains("NativeStructureLocatePersistence.probe("));
|
||||
assertTrue(irisHelper.contains("NativeStructureLocateResults.selectAndReference("));
|
||||
assertTrue(irisHelper.contains("selectedSearch.search().reference(selectedStart)"));
|
||||
assertTrue(irisHelper.contains("findUnexplored"));
|
||||
assertTrue(irisHelper.contains("verified.ownership().locatorY()"));
|
||||
assertFalse(outerMethod.contains("delegate.findNearestMapStructure("));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -77,6 +87,50 @@ public class IrisChunkGeneratorMonumentLocateContractTest {
|
||||
assertSame(nativeTie, NativeStructureLocateResults.nearest(origin, irisNear, nativeTie));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mixedUnexploredLocateReferencesOnlyTheSelectedProvider() {
|
||||
BlockPos origin = BlockPos.ZERO;
|
||||
Pair<BlockPos, Holder<Structure>> irisNear = Pair.of(new BlockPos(4, 70, 0), null);
|
||||
Pair<BlockPos, Holder<Structure>> nativeFar = Pair.of(new BlockPos(8, 70, 0), null);
|
||||
AtomicInteger irisReferences = new AtomicInteger();
|
||||
AtomicInteger nativeReferences = new AtomicInteger();
|
||||
|
||||
Pair<BlockPos, Holder<Structure>> irisSelected =
|
||||
NativeStructureLocateResults.selectAndReference(
|
||||
origin,
|
||||
irisNear, () -> irisReferences.incrementAndGet(),
|
||||
nativeFar, () -> nativeReferences.incrementAndGet());
|
||||
|
||||
assertSame(irisNear, irisSelected);
|
||||
assertEquals(1, irisReferences.get());
|
||||
assertEquals(0, nativeReferences.get());
|
||||
|
||||
Pair<BlockPos, Holder<Structure>> nativeNear = Pair.of(new BlockPos(2, 70, 0), null);
|
||||
Pair<BlockPos, Holder<Structure>> nativeSelected =
|
||||
NativeStructureLocateResults.selectAndReference(
|
||||
origin,
|
||||
irisNear, () -> irisReferences.incrementAndGet(),
|
||||
nativeNear, () -> nativeReferences.incrementAndGet());
|
||||
|
||||
assertSame(nativeNear, nativeSelected);
|
||||
assertEquals(1, irisReferences.get());
|
||||
assertEquals(1, nativeReferences.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nativePredictionIsReadOnlyUntilTheWinnerIsCommitted() throws IOException {
|
||||
Path nativegen = Path.of(System.getProperty("iris.nativeStructurePostProcessorSource")).getParent();
|
||||
String source = Files.readString(nativegen.resolve("NativeStructureVanillaLocator.java"));
|
||||
int predictionStart = source.indexOf("private static Candidate predictAt(");
|
||||
int candidateStart = source.indexOf("public static final class Candidate", predictionStart);
|
||||
String prediction = source.substring(predictionStart, candidateStart);
|
||||
String candidate = source.substring(candidateStart);
|
||||
|
||||
assertFalse(prediction.contains("addReference("));
|
||||
assertTrue(candidate.contains("structureManager.addReference(referenceStart)"));
|
||||
assertTrue(candidate.contains("committed.compareAndSet(false, true)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stiltSupportUsesPlacedSolidOccupancyWithoutSnapshotDifferenceRequirement() throws IOException {
|
||||
Path processor = Path.of(System.getProperty("iris.nativeStructurePostProcessorSource"));
|
||||
|
||||
+339
-5
@@ -1,11 +1,23 @@
|
||||
package art.arcane.iris.nativegen;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisJigsawConfiguration;
|
||||
import art.arcane.iris.engine.object.IrisStructureTerrain;
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderOwner;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.IntTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
import net.minecraft.util.RandomSource;
|
||||
@@ -13,10 +25,12 @@ import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.NoiseColumn;
|
||||
import net.minecraft.world.level.StructureManager;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.BiomeManager;
|
||||
import net.minecraft.world.level.biome.FixedBiomeSource;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.Rotation;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
@@ -25,19 +39,40 @@ import net.minecraft.world.level.levelgen.RandomState;
|
||||
import net.minecraft.world.level.levelgen.blending.Blender;
|
||||
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.BoundingBox;
|
||||
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
|
||||
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.StructurePoolElementType;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.alias.PoolAliasBinding;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.SwampHutPiece;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.SwampHutStructure;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
|
||||
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 org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NativeStructureFactoryTest {
|
||||
@BeforeClass
|
||||
@@ -67,20 +102,142 @@ public class NativeStructureFactoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forcedGeneratorSuppliesDeterministicPermissiveEnvironment() {
|
||||
public void sourceMetadataReadsScalarAndAxisSpecificJigsawRanges() {
|
||||
CompoundTag scalar = new CompoundTag();
|
||||
scalar.putInt("max_distance_from_center", 80);
|
||||
CompoundTag axisSpecific = new CompoundTag();
|
||||
CompoundTag distance = new CompoundTag();
|
||||
distance.putInt("horizontal", 96);
|
||||
distance.putInt("vertical", 64);
|
||||
axisSpecific.put("max_distance_from_center", distance);
|
||||
|
||||
assertEquals(80, NativeStructureFactory.sourceDistance(scalar, "horizontal"));
|
||||
assertEquals(80, NativeStructureFactory.sourceDistance(scalar, "vertical"));
|
||||
assertEquals(96, NativeStructureFactory.sourceDistance(axisSpecific, "horizontal"));
|
||||
assertEquals(64, NativeStructureFactory.sourceDistance(axisSpecific, "vertical"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceMetadataIncludesAdjustBoundingBoxExpansion() {
|
||||
Structure none = new SwampHutStructure(new Structure.StructureSettings(HolderSet.empty()));
|
||||
Structure beardBox = new SwampHutStructure(new Structure.StructureSettings.Builder(HolderSet.empty())
|
||||
.terrainAdapation(TerrainAdjustment.BEARD_BOX)
|
||||
.build());
|
||||
|
||||
assertEquals(0, NativeStructureFactory.horizontalReferenceExpansion(none));
|
||||
assertEquals(12, NativeStructureFactory.horizontalReferenceExpansion(beardBox));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void templatePoolSpanIncludesWeightedListsFallbacksAndRotations() {
|
||||
StructurePoolElement narrow = inlineElement(30, 8, 70);
|
||||
StructurePoolElement wide = inlineElement(96, 8, 12);
|
||||
ListPoolElement list = new ListPoolElement(
|
||||
List.of(narrow, wide, EmptyPoolElement.INSTANCE),
|
||||
StructureTemplatePool.Projection.RIGID);
|
||||
BoundPoolHolder fallbackHolder = new BoundPoolHolder("test:fallback");
|
||||
StructureTemplatePool fallback = new StructureTemplatePool(
|
||||
fallbackHolder,
|
||||
List.of(Pair.of(inlineElement(110, 8, 20), 1)));
|
||||
fallbackHolder.bind(fallback);
|
||||
BoundPoolHolder startHolder = new BoundPoolHolder("test:start");
|
||||
StructureTemplatePool start = new StructureTemplatePool(
|
||||
fallbackHolder,
|
||||
List.of(Pair.of(list, 5), Pair.of(EmptyPoolElement.INSTANCE, 1)));
|
||||
startHolder.bind(start);
|
||||
|
||||
assertEquals(110, NativeStructureTemplatePoolBounds.horizontalSpan(start, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oversizedInlineStartTemplateReportsItsFullSpan() {
|
||||
BoundPoolHolder holder = new BoundPoolHolder("test:oversized");
|
||||
StructureTemplatePool pool = new StructureTemplatePool(
|
||||
holder,
|
||||
List.of(Pair.of(inlineElement(160, 8, 9), 1)));
|
||||
holder.bind(pool);
|
||||
|
||||
assertEquals(160, NativeStructureTemplatePoolBounds.horizontalSpan(pool, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceSpanIncludesOnlyAliasesThatCanReplaceTheStartPool() {
|
||||
BoundPoolHolder emptyHolder = new BoundPoolHolder("test:empty");
|
||||
StructureTemplatePool empty = new StructureTemplatePool(emptyHolder, List.of());
|
||||
emptyHolder.bind(empty);
|
||||
BoundPoolHolder startHolder = new BoundPoolHolder("test:start");
|
||||
StructureTemplatePool start = new StructureTemplatePool(
|
||||
emptyHolder, List.of(Pair.of(inlineElement(20, 8, 12), 1)));
|
||||
startHolder.bind(start);
|
||||
BoundPoolHolder replacementHolder = new BoundPoolHolder("test:replacement");
|
||||
StructureTemplatePool replacement = new StructureTemplatePool(
|
||||
emptyHolder, List.of(Pair.of(inlineElement(30, 8, 12), 1)));
|
||||
replacementHolder.bind(replacement);
|
||||
BoundPoolHolder unrelatedHolder = new BoundPoolHolder("test:unrelated");
|
||||
StructureTemplatePool unrelated = new StructureTemplatePool(
|
||||
emptyHolder, List.of(Pair.of(inlineElement(200, 8, 12), 1)));
|
||||
unrelatedHolder.bind(unrelated);
|
||||
ResourceKey<StructureTemplatePool> startKey = startHolder.unwrapKey().orElseThrow();
|
||||
ResourceKey<StructureTemplatePool> replacementKey = replacementHolder.unwrapKey().orElseThrow();
|
||||
ResourceKey<StructureTemplatePool> unrelatedKey = unrelatedHolder.unwrapKey().orElseThrow();
|
||||
ResourceKey<StructureTemplatePool> childAlias = ResourceKey.create(
|
||||
Registries.TEMPLATE_POOL, Identifier.parse("test:child_alias"));
|
||||
List<PoolAliasBinding> aliases = List.of(
|
||||
PoolAliasBinding.direct(startKey, replacementKey),
|
||||
PoolAliasBinding.direct(childAlias, unrelatedKey));
|
||||
Map<ResourceKey<StructureTemplatePool>, StructureTemplatePool> pools = Map.of(
|
||||
replacementKey, replacement,
|
||||
unrelatedKey, unrelated);
|
||||
|
||||
assertEquals(30, NativeStructureTemplatePoolBounds.sourceHorizontalSpan(
|
||||
null, start, startKey, aliases, pools::get));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceStartSpanDoesNotIncludeItsFallbackPool() {
|
||||
BoundPoolHolder fallbackHolder = new BoundPoolHolder("test:large_fallback");
|
||||
StructureTemplatePool fallback = new StructureTemplatePool(
|
||||
fallbackHolder, List.of(Pair.of(inlineElement(180, 8, 10), 1)));
|
||||
fallbackHolder.bind(fallback);
|
||||
BoundPoolHolder startHolder = new BoundPoolHolder("test:small_start");
|
||||
StructureTemplatePool start = new StructureTemplatePool(
|
||||
fallbackHolder, List.of(Pair.of(inlineElement(24, 8, 10), 1)));
|
||||
startHolder.bind(start);
|
||||
|
||||
assertEquals(180, NativeStructureTemplatePoolBounds.horizontalSpan(start, null));
|
||||
assertEquals(24, NativeStructureTemplatePoolBounds.sourceHorizontalSpan(
|
||||
null, start, startHolder.unwrapKey().orElseThrow(), List.of(), ignored -> null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customPoolElementUsesItsBoundingBoxCapability() {
|
||||
BoundPoolHolder fallbackHolder = new BoundPoolHolder("test:custom_fallback");
|
||||
StructureTemplatePool fallback = new StructureTemplatePool(fallbackHolder, List.of());
|
||||
fallbackHolder.bind(fallback);
|
||||
BoundPoolHolder poolHolder = new BoundPoolHolder("test:custom");
|
||||
StructureTemplatePool pool = new StructureTemplatePool(
|
||||
fallbackHolder, List.of(Pair.of(new BoundingPoolElement(37, 11), 1)));
|
||||
poolHolder.bind(pool);
|
||||
|
||||
assertEquals(37, NativeStructureTemplatePoolBounds.horizontalSpan(pool, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forcedGeneratorBypassesBiomeEligibilityWithoutFabricatingTerrain() {
|
||||
ChunkGenerator delegate = new TestChunkGenerator();
|
||||
Holder<Biome> biome = Holder.direct((Biome) null);
|
||||
LevelHeightAccessor heightAccessor = LevelHeightAccessor.create(-64, 384);
|
||||
ForcedStructureChunkGenerator generator = new ForcedStructureChunkGenerator(
|
||||
delegate, biome, -20);
|
||||
delegate, biome);
|
||||
|
||||
assertEquals(-63, generator.getSeaLevel());
|
||||
assertEquals(81, generator.getBaseHeight(
|
||||
assertEquals(63, generator.getSeaLevel());
|
||||
assertEquals(64, generator.getBaseHeight(
|
||||
0, 0, Heightmap.Types.WORLD_SURFACE_WG, heightAccessor, null));
|
||||
NoiseColumn column = generator.getBaseColumn(
|
||||
0, 0, heightAccessor, null);
|
||||
assertSame(Blocks.STONE, column.getBlock(80).getBlock());
|
||||
assertSame(Blocks.AIR, column.getBlock(81).getBlock());
|
||||
assertSame(Blocks.STONE, column.getBlock(81).getBlock());
|
||||
assertNotSame(delegate.getBiomeSource(), generator.getBiomeSource());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -103,6 +260,96 @@ public class NativeStructureFactoryTest {
|
||||
assertEquals(-20, relocated.getBoundingBox().minY());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidGeneratedStartIsANormalSkippedCandidate() {
|
||||
Structure source = new SwampHutStructure(
|
||||
new Structure.StructureSettings(HolderSet.empty()));
|
||||
StructureStart valid = new StructureStart(
|
||||
source,
|
||||
new ChunkPos(0, 0),
|
||||
0,
|
||||
new PiecesContainer(List.of(
|
||||
new SwampHutPiece(RandomSource.create(23L), 0, 0)))
|
||||
);
|
||||
|
||||
assertFalse(NativeStructureStartInjector.isUsableGeneratedStart(
|
||||
StructureStart.INVALID_START));
|
||||
assertFalse(NativeStructureStartInjector.isUsableGeneratedStart(null));
|
||||
assertFalse(NativeStructureStartInjector.isUsableGeneratedStart(
|
||||
new StructureStart(source, new ChunkPos(0, 0), 0,
|
||||
new PiecesContainer(List.of()))));
|
||||
assertTrue(NativeStructureStartInjector.isUsableGeneratedStart(valid));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unrepresentableGeneratedContentBecomesAnUnpublishedInvalidStart() {
|
||||
Structure source = new SwampHutStructure(
|
||||
new Structure.StructureSettings(HolderSet.empty()));
|
||||
SwampHutPiece boundaryPiece = new SwampHutPiece(RandomSource.create(29L), 0, 0);
|
||||
boundaryPiece.move(143 - boundaryPiece.getBoundingBox().maxX(), 0, 0);
|
||||
StructureStart boundary = new StructureStart(
|
||||
source,
|
||||
new ChunkPos(0, 0),
|
||||
0,
|
||||
new PiecesContainer(List.of(boundaryPiece))
|
||||
);
|
||||
SwampHutPiece oversizedPiece = new SwampHutPiece(RandomSource.create(31L), 0, 0);
|
||||
oversizedPiece.move(144 - oversizedPiece.getBoundingBox().maxX(), 0, 0);
|
||||
StructureStart oversized = new StructureStart(
|
||||
source,
|
||||
new ChunkPos(0, 0),
|
||||
0,
|
||||
new PiecesContainer(List.of(oversizedPiece))
|
||||
);
|
||||
|
||||
StructureStart boundaryResult = NativeStructureReferenceEnvelope.wrapForPublication(
|
||||
boundary, source, 0, new IrisStructureTerrain(), "test:boundary_content");
|
||||
|
||||
StructureStart result = NativeStructureReferenceEnvelope.wrapForPublication(
|
||||
oversized, source, 0,
|
||||
new IrisStructureTerrain(),
|
||||
"test:oversized_content");
|
||||
|
||||
assertTrue(boundaryResult.isValid());
|
||||
assertSame(StructureStart.INVALID_START, result);
|
||||
assertFalse(NativeStructureStartInjector.isUsableGeneratedStart(result));
|
||||
assertEquals(1, oversized.getPieces().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failedManualGenerationClearsStaleOwnershipAfterReplacementHandling() throws Exception {
|
||||
String source = Files.readString(
|
||||
Path.of(System.getProperty("iris.nativeStructureStartInjectorSource")),
|
||||
StandardCharsets.UTF_8);
|
||||
int failureStart = source.indexOf("if (!isUsableGeneratedStart(generated))");
|
||||
int failureEnd = source.indexOf("continue;", failureStart);
|
||||
String failureBranch = source.substring(failureStart, failureEnd);
|
||||
|
||||
assertTrue(failureBranch.contains("if (replacement)"));
|
||||
assertTrue(failureBranch.indexOf("NativeStructureOwnershipStore.discard(")
|
||||
> failureBranch.lastIndexOf("}") );
|
||||
assertTrue(failureStart < source.indexOf("NativeStructureOwnershipFingerprint.capture("));
|
||||
assertTrue(failureStart < source.indexOf("NativeStructureOwnershipStore.record("));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failedManualStartPublicationClearsRecordedOwnershipAndPreservesTheFailure() throws Exception {
|
||||
String source = Files.readString(
|
||||
Path.of(System.getProperty("iris.nativeStructureStartInjectorSource")),
|
||||
StandardCharsets.UTF_8);
|
||||
int ownershipRecord = source.indexOf("NativeStructureOwnershipStore.record(");
|
||||
int publication = source.indexOf("context.structureManager().setStartForStructure(", ownershipRecord);
|
||||
int cleanup = source.indexOf("NativeStructureOwnershipStore.discard(", publication);
|
||||
int suppressed = source.indexOf("publicationError.addSuppressed(cleanupError)", cleanup);
|
||||
int rethrow = source.indexOf("throw publicationError;", suppressed);
|
||||
|
||||
assertTrue(ownershipRecord >= 0);
|
||||
assertTrue(publication > ownershipRecord);
|
||||
assertTrue(cleanup > publication);
|
||||
assertTrue(suppressed > cleanup);
|
||||
assertTrue(rethrow > suppressed);
|
||||
}
|
||||
|
||||
private static final class TestChunkGenerator extends ChunkGenerator {
|
||||
private TestChunkGenerator() {
|
||||
super(new FixedBiomeSource(Holder.direct((Biome) null)));
|
||||
@@ -171,4 +418,91 @@ public class NativeStructureFactoryTest {
|
||||
BlockPos feetPos) {
|
||||
}
|
||||
}
|
||||
|
||||
private static StructurePoolElement inlineElement(int sizeX, int sizeY, int sizeZ) {
|
||||
StructureTemplate template = new StructureTemplate();
|
||||
CompoundTag tag = new CompoundTag();
|
||||
ListTag size = new ListTag();
|
||||
size.add(IntTag.valueOf(sizeX));
|
||||
size.add(IntTag.valueOf(sizeY));
|
||||
size.add(IntTag.valueOf(sizeZ));
|
||||
tag.put("size", size);
|
||||
template.load(BuiltInRegistries.BLOCK, tag);
|
||||
return new InlinePoolElement(template);
|
||||
}
|
||||
|
||||
private static final class InlinePoolElement extends SinglePoolElement {
|
||||
private final StructureTemplate inlineTemplate;
|
||||
|
||||
private InlinePoolElement(StructureTemplate template) {
|
||||
super(Either.right(template), Holder.direct(new StructureProcessorList(List.of())),
|
||||
StructureTemplatePool.Projection.RIGID, Optional.<LiquidSettings>empty());
|
||||
inlineTemplate = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoundingBox getBoundingBox(StructureTemplateManager templateManager,
|
||||
BlockPos position, Rotation rotation) {
|
||||
return inlineTemplate.getBoundingBox(
|
||||
new StructurePlaceSettings().setRotation(rotation), position);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BoundingPoolElement extends StructurePoolElement {
|
||||
private final int width;
|
||||
private final int depth;
|
||||
|
||||
private BoundingPoolElement(int width, int depth) {
|
||||
super(StructureTemplatePool.Projection.RIGID);
|
||||
this.width = width;
|
||||
this.depth = depth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3i getSize(StructureTemplateManager templateManager, Rotation rotation) {
|
||||
return new Vec3i(width, 1, depth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StructureTemplate.JigsawBlockInfo> getShuffledJigsawBlocks(
|
||||
StructureTemplateManager templateManager, BlockPos position,
|
||||
Rotation rotation, RandomSource random) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoundingBox getBoundingBox(StructureTemplateManager templateManager,
|
||||
BlockPos position, Rotation rotation) {
|
||||
return new BoundingBox(
|
||||
position.getX(), position.getY(), position.getZ(),
|
||||
position.getX() + width - 1, position.getY(),
|
||||
position.getZ() + depth - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean place(StructureTemplateManager templateManager,
|
||||
WorldGenLevel world,
|
||||
StructureManager structureManager, ChunkGenerator chunkGenerator,
|
||||
BlockPos position, BlockPos pivot, Rotation rotation,
|
||||
BoundingBox area, RandomSource random,
|
||||
LiquidSettings liquidSettings, boolean keepJigsaws) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StructurePoolElementType<?> getType() {
|
||||
return StructurePoolElementType.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BoundPoolHolder extends Holder.Reference<StructureTemplatePool> {
|
||||
private BoundPoolHolder(String key) {
|
||||
super(Type.STAND_ALONE, new HolderOwner<>() {
|
||||
}, ResourceKey.create(Registries.TEMPLATE_POOL, Identifier.parse(key)), null);
|
||||
}
|
||||
|
||||
private void bind(StructureTemplatePool pool) {
|
||||
bindValue(pool);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
package art.arcane.iris.nativegen;
|
||||
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NativeStructureLocatePersistenceTest {
|
||||
@BeforeClass
|
||||
public static void bootstrapMinecraftRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void storageProbeScansEachCandidateOnlyOnce() {
|
||||
AtomicInteger reads = new AtomicInteger();
|
||||
CompoundTag chunk = new CompoundTag();
|
||||
chunk.putInt("DataVersion", SharedConstants.getCurrentVersion().dataVersion().version());
|
||||
chunk.putString("Status", "full");
|
||||
CompoundTag start = new CompoundTag();
|
||||
start.putString("id", "minecraft:village_plains");
|
||||
start.putInt("references", 0);
|
||||
CompoundTag starts = new CompoundTag();
|
||||
starts.put("minecraft:village_plains", start);
|
||||
CompoundTag structures = new CompoundTag();
|
||||
structures.put("starts", starts);
|
||||
chunk.put("structures", structures);
|
||||
NativeStructureLocatePersistence.ProbeBudget budget =
|
||||
new NativeStructureLocatePersistence.ProbeBudget(
|
||||
512,
|
||||
(chunkPos, visitor) -> {
|
||||
reads.incrementAndGet();
|
||||
chunk.acceptAsRoot(visitor);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
});
|
||||
ChunkPos chunkPos = new ChunkPos(7, -11);
|
||||
|
||||
assertTrue(budget.acceptsStored(
|
||||
null, chunkPos, "minecraft:village_plains", true));
|
||||
assertTrue(budget.acceptsStored(
|
||||
null, chunkPos, "minecraft:village_plains", true));
|
||||
assertEquals(1, reads.get());
|
||||
assertEquals(1, budget.used());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacyStoredChunkIsDatafixedBeforeItsStartIsClassified() {
|
||||
AtomicBoolean datafixed = new AtomicBoolean();
|
||||
CompoundTag legacy = new CompoundTag();
|
||||
legacy.putInt("DataVersion", 100);
|
||||
legacy.putString("Status", "full");
|
||||
NativeStructureLocatePersistence.ProbeBudget budget =
|
||||
new NativeStructureLocatePersistence.ProbeBudget(
|
||||
512,
|
||||
(chunkPos, visitor) -> {
|
||||
legacy.acceptAsRoot(visitor);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
},
|
||||
(level, storedChunk) -> {
|
||||
datafixed.set(true);
|
||||
CompoundTag fixed = storedChunk.copy();
|
||||
fixed.putInt("DataVersion",
|
||||
SharedConstants.getCurrentVersion().dataVersion().version());
|
||||
CompoundTag start = new CompoundTag();
|
||||
start.putString("id", "minecraft:village_plains");
|
||||
start.putInt("references", 1);
|
||||
CompoundTag starts = new CompoundTag();
|
||||
starts.put("minecraft:village_plains", start);
|
||||
CompoundTag structures = new CompoundTag();
|
||||
structures.put("starts", starts);
|
||||
fixed.put("structures", structures);
|
||||
return fixed;
|
||||
});
|
||||
|
||||
assertFalse(budget.acceptsStored(
|
||||
null, new ChunkPos(3, 4), "minecraft:village_plains", true));
|
||||
assertTrue(datafixed.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void partialDatafixFailureFallsBackToSelectedChunkVerification() {
|
||||
CompoundTag legacy = new CompoundTag();
|
||||
legacy.putInt("DataVersion", 100);
|
||||
NativeStructureLocatePersistence.ProbeBudget budget =
|
||||
new NativeStructureLocatePersistence.ProbeBudget(
|
||||
512,
|
||||
(chunkPos, visitor) -> {
|
||||
legacy.acceptAsRoot(visitor);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
},
|
||||
(level, storedChunk) -> {
|
||||
throw new IllegalStateException("broken partial datafix");
|
||||
});
|
||||
|
||||
assertTrue(budget.acceptsStored(
|
||||
null, new ChunkPos(9, -2), "minecraft:village_plains", true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingStartsContainerFallsBackToSelectedChunkVerification() {
|
||||
CompoundTag chunk = currentChunk();
|
||||
NativeStructureLocatePersistence.ProbeBudget budget = budget(chunk);
|
||||
|
||||
assertTrue(budget.acceptsStored(
|
||||
null, new ChunkPos(5, 8), "minecraft:village_plains", false));
|
||||
assertTrue(budget.acceptsStored(
|
||||
null, new ChunkPos(5, 8), "minecraft:village_plains", true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void presentEmptyStartsContainerRejectsTheCandidateWithoutLoading() {
|
||||
CompoundTag chunk = currentChunk();
|
||||
CompoundTag structures = new CompoundTag();
|
||||
structures.put("starts", new CompoundTag());
|
||||
chunk.put("structures", structures);
|
||||
NativeStructureLocatePersistence.ProbeBudget budget = budget(chunk);
|
||||
|
||||
assertFalse(budget.acceptsStored(
|
||||
null, new ChunkPos(-3, 2), "minecraft:village_plains", false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void collisionTombstoneIsRejectedForNormalAndUnexploredLocate() {
|
||||
CompoundTag chunk = currentChunk();
|
||||
CompoundTag tombstone = new CompoundTag();
|
||||
tombstone.putString("id", "INVALID");
|
||||
CompoundTag starts = new CompoundTag();
|
||||
starts.put("minecraft:village_plains", tombstone);
|
||||
CompoundTag structures = new CompoundTag();
|
||||
structures.put("starts", starts);
|
||||
chunk.put("structures", structures);
|
||||
ChunkPos chunkPos = new ChunkPos(12, -9);
|
||||
|
||||
assertFalse(budget(chunk).acceptsStored(
|
||||
null, chunkPos, "minecraft:village_plains", false));
|
||||
assertFalse(budget(chunk).acceptsStored(
|
||||
null, chunkPos, "minecraft:village_plains", true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void malformedNonCompoundStartIsRejectedWithoutLoading() {
|
||||
CompoundTag chunk = currentChunk();
|
||||
CompoundTag starts = new CompoundTag();
|
||||
starts.putInt("minecraft:village_plains", 1);
|
||||
CompoundTag structures = new CompoundTag();
|
||||
structures.put("starts", starts);
|
||||
chunk.put("structures", structures);
|
||||
|
||||
assertFalse(budget(chunk).acceptsStored(
|
||||
null, new ChunkPos(1, 1), "minecraft:village_plains", false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void referencedStoredStartIsOnlyRejectedForUnexploredLocate() {
|
||||
CompoundTag chunk = currentChunk();
|
||||
CompoundTag start = new CompoundTag();
|
||||
start.putString("id", "minecraft:village_plains");
|
||||
start.putInt("references", 1);
|
||||
CompoundTag starts = new CompoundTag();
|
||||
starts.put("minecraft:village_plains", start);
|
||||
CompoundTag structures = new CompoundTag();
|
||||
structures.put("starts", starts);
|
||||
chunk.put("structures", structures);
|
||||
ChunkPos chunkPos = new ChunkPos(-4, -7);
|
||||
|
||||
assertTrue(budget(chunk).acceptsStored(
|
||||
null, chunkPos, "minecraft:village_plains", false));
|
||||
assertFalse(budget(chunk).acceptsStored(
|
||||
null, chunkPos, "minecraft:village_plains", true));
|
||||
}
|
||||
|
||||
private static CompoundTag currentChunk() {
|
||||
CompoundTag chunk = new CompoundTag();
|
||||
chunk.putInt("DataVersion", SharedConstants.getCurrentVersion().dataVersion().version());
|
||||
return chunk;
|
||||
}
|
||||
|
||||
private static NativeStructureLocatePersistence.ProbeBudget budget(CompoundTag chunk) {
|
||||
return new NativeStructureLocatePersistence.ProbeBudget(
|
||||
512,
|
||||
(chunkPos, visitor) -> {
|
||||
chunk.acceptAsRoot(visitor);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
package art.arcane.iris.nativegen;
|
||||
|
||||
import art.arcane.iris.engine.framework.NativeStructureOwnershipRecord;
|
||||
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
|
||||
import art.arcane.iris.engine.object.IrisNativeStructure;
|
||||
import art.arcane.iris.engine.object.IrisStructurePlacement;
|
||||
import art.arcane.iris.engine.object.IrisStructureTerrain;
|
||||
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.StructureManager;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.block.Rotation;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.LegacyRandomSource;
|
||||
import net.minecraft.world.level.levelgen.RandomSupport;
|
||||
import net.minecraft.world.level.levelgen.WorldgenRandom;
|
||||
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
|
||||
import net.minecraft.world.level.levelgen.structure.StructurePiece;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
||||
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElementType;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentPieces;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentStructure;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NativeStructureOwnershipFingerprintTest {
|
||||
@BeforeClass
|
||||
public static void bootstrapMinecraftRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void monumentFingerprintSurvivesSourceYRegenerationWithoutSyntheticPieces() {
|
||||
long seed = 8372619L;
|
||||
ChunkPos origin = new ChunkPos(9, -4);
|
||||
OceanMonumentStructure structure = new OceanMonumentStructure(
|
||||
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
|
||||
StructureStart generated = monumentStart(structure, origin, seed);
|
||||
NativeStructureVerticalPlacer.alignOceanMonumentToSeaLevel(
|
||||
generated, 0, 80, -64, 320);
|
||||
int alignedLocatorY = NativeStructureOwnershipFingerprint.locatorY(generated);
|
||||
IrisStructureTerrain terrain = new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
|
||||
.setHorizontalPadding(24);
|
||||
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
|
||||
generated,
|
||||
structure,
|
||||
0,
|
||||
terrain);
|
||||
String beforeReload = NativeStructureOwnershipFingerprint.fingerprint(
|
||||
"minecraft:monument", wrapped);
|
||||
|
||||
ListTag serializedPieces = (ListTag) new PiecesContainer(wrapped.getPieces()).save(null);
|
||||
PiecesContainer loadedPieces = PiecesContainer.load(serializedPieces, null);
|
||||
assertEquals(1, loadedPieces.pieces().size());
|
||||
assertTrue(loadedPieces.pieces().getFirst()
|
||||
instanceof OceanMonumentPieces.MonumentBuilding);
|
||||
PiecesContainer regeneratedPieces = OceanMonumentStructure.regeneratePiecesAfterLoad(
|
||||
origin, seed, loadedPieces);
|
||||
StructureStart reloaded = new StructureStart(structure, origin, 0, regeneratedPieces);
|
||||
String afterReload = NativeStructureOwnershipFingerprint.fingerprint(
|
||||
"minecraft:monument", reloaded);
|
||||
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
|
||||
"minecraft:monument",
|
||||
wrapped,
|
||||
plan(origin, NativeStructureReferenceEnvelope.contentBounds(wrapped).minY()),
|
||||
NativeStructureReferenceEnvelope.referenceBounds(wrapped, structure, terrain));
|
||||
|
||||
assertEquals(beforeReload, afterReload);
|
||||
assertTrue(NativeStructureOwnershipFingerprint.matches(ownership, reloaded));
|
||||
assertNotEquals(alignedLocatorY, NativeStructureOwnershipFingerprint.locatorY(reloaded));
|
||||
assertEquals(alignedLocatorY, ownership.locatorY());
|
||||
assertEquals(1, reloaded.getPieces().size());
|
||||
assertEquals(1, wrapped.getPieces().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sameKeyAndOriginStillRequiresMatchingContentIdentity() {
|
||||
long seed = 18273L;
|
||||
ChunkPos origin = new ChunkPos(2, 3);
|
||||
OceanMonumentStructure structure = new OceanMonumentStructure(
|
||||
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
|
||||
StructureStart expected = monumentStart(structure, origin, seed);
|
||||
StructureStart moved = monumentStart(structure, origin, seed);
|
||||
for (StructurePiece piece : moved.getPieces()) {
|
||||
piece.move(1, 0, 0);
|
||||
}
|
||||
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
|
||||
"minecraft:monument",
|
||||
expected,
|
||||
plan(origin, NativeStructureReferenceEnvelope.contentBounds(expected).minY()),
|
||||
expected.getBoundingBox());
|
||||
|
||||
assertFalse(NativeStructureOwnershipFingerprint.matches(ownership, moved));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void persistedEnvelopeRepairsReloadedMonumentReferenceOutsideLiveBounds() {
|
||||
long seed = 772931L;
|
||||
ChunkPos origin = new ChunkPos(4, -7);
|
||||
OceanMonumentStructure structure = new OceanMonumentStructure(
|
||||
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
|
||||
StructureStart generated = monumentStart(structure, origin, seed);
|
||||
NativeStructureVerticalPlacer.alignOceanMonumentToSeaLevel(
|
||||
generated, 0, 80, -64, 320);
|
||||
IrisStructureTerrain terrain = new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
|
||||
.setHorizontalPadding(24);
|
||||
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
|
||||
generated,
|
||||
structure,
|
||||
0,
|
||||
terrain);
|
||||
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
|
||||
"minecraft:monument",
|
||||
wrapped,
|
||||
plan(origin, NativeStructureReferenceEnvelope.contentBounds(wrapped).minY()),
|
||||
NativeStructureReferenceEnvelope.referenceBounds(wrapped, structure, terrain));
|
||||
PiecesContainer regeneratedPieces = OceanMonumentStructure.regeneratePiecesAfterLoad(
|
||||
origin, seed, new PiecesContainer(wrapped.getPieces()));
|
||||
StructureStart reloaded = new StructureStart(structure, origin, 0, regeneratedPieces);
|
||||
ChunkPos target = outsideLiveBounds(ownership, reloaded);
|
||||
|
||||
assertNotNull(target);
|
||||
assertTrue(NativeStructureReferenceRepair.requiresReference(
|
||||
target, "minecraft:monument", reloaded, ownership));
|
||||
assertFalse(NativeStructureReferenceRepair.requiresReference(
|
||||
target, "minecraft:monument", reloaded, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overlappingManualStartsSixteenChunksApartRemainIndependentlyReferenced() {
|
||||
OceanMonumentStructure structure = new OceanMonumentStructure(
|
||||
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
|
||||
ChunkPos westOrigin = new ChunkPos(0, 0);
|
||||
ChunkPos eastOrigin = new ChunkPos(16, 0);
|
||||
StructureStart west = monumentStart(structure, westOrigin, 317L);
|
||||
StructureStart east = monumentStart(structure, eastOrigin, 941L);
|
||||
west.getPieces().getFirst().move(112, 0, 0);
|
||||
east.getPieces().getFirst().move(-96, 0, 0);
|
||||
NativeStructureOwnershipRecord westOwnership = ownership(westOrigin, west, structure);
|
||||
NativeStructureOwnershipRecord eastOwnership = ownership(eastOrigin, east, structure);
|
||||
ChunkPos shared = new ChunkPos(8, 0);
|
||||
|
||||
assertTrue(NativeStructureReferenceEnvelope.contentBounds(west).intersects(
|
||||
NativeStructureReferenceEnvelope.contentBounds(east)));
|
||||
assertTrue(NativeStructureReferenceRepair.requiresReference(
|
||||
shared, "minecraft:monument", west, westOwnership));
|
||||
assertTrue(NativeStructureReferenceRepair.requiresReference(
|
||||
shared, "minecraft:monument", east, eastOwnership));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void denseAdjacentManualStartsDoNotSuppressEachOther() {
|
||||
OceanMonumentStructure structure = new OceanMonumentStructure(
|
||||
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
|
||||
ChunkPos firstOrigin = new ChunkPos(0, 0);
|
||||
ChunkPos secondOrigin = new ChunkPos(1, 0);
|
||||
StructureStart first = monumentStart(structure, firstOrigin, 17L);
|
||||
StructureStart second = monumentStart(structure, secondOrigin, 23L);
|
||||
NativeStructureOwnershipRecord firstOwnership = ownership(firstOrigin, first, structure);
|
||||
NativeStructureOwnershipRecord secondOwnership = ownership(secondOrigin, second, structure);
|
||||
ChunkPos shared = new ChunkPos(0, 0);
|
||||
|
||||
assertTrue(NativeStructureReferenceEnvelope.contentBounds(first).intersects(
|
||||
NativeStructureReferenceEnvelope.contentBounds(second)));
|
||||
assertTrue(NativeStructureReferenceRepair.requiresReference(
|
||||
shared, "minecraft:monument", first, firstOwnership));
|
||||
assertTrue(NativeStructureReferenceRepair.requiresReference(
|
||||
shared, "minecraft:monument", second, secondOwnership));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customPoolElementIdentityDoesNotDependOnProcessLocalToString() {
|
||||
OceanMonumentStructure structure = new OceanMonumentStructure(
|
||||
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
|
||||
ChunkPos origin = new ChunkPos(3, -2);
|
||||
StructureStart first = poolStart(structure, origin, new IdentityStringPoolElement());
|
||||
StructureStart reloaded = poolStart(structure, origin, new IdentityStringPoolElement());
|
||||
|
||||
assertEquals(
|
||||
NativeStructureOwnershipFingerprint.fingerprint("test:custom_pool", first),
|
||||
NativeStructureOwnershipFingerprint.fingerprint("test:custom_pool", reloaded));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void namedPoolTemplateRemainsPartOfTheStableFingerprint() {
|
||||
OceanMonumentStructure structure = new OceanMonumentStructure(
|
||||
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
|
||||
ChunkPos origin = new ChunkPos(3, -2);
|
||||
StructurePoolElement firstElement = StructurePoolElement.single("test:first")
|
||||
.apply(StructureTemplatePool.Projection.RIGID);
|
||||
StructurePoolElement secondElement = StructurePoolElement.single("test:second")
|
||||
.apply(StructureTemplatePool.Projection.RIGID);
|
||||
|
||||
assertNotEquals(
|
||||
NativeStructureOwnershipFingerprint.fingerprint(
|
||||
"test:named_pool", poolStart(structure, origin, firstElement)),
|
||||
NativeStructureOwnershipFingerprint.fingerprint(
|
||||
"test:named_pool", poolStart(structure, origin, secondElement)));
|
||||
}
|
||||
|
||||
private static StructureStart monumentStart(OceanMonumentStructure structure,
|
||||
ChunkPos origin, long seed) {
|
||||
WorldgenRandom random = new WorldgenRandom(
|
||||
new LegacyRandomSource(RandomSupport.generateUniqueSeed()));
|
||||
random.setLargeFeatureSeed(seed, origin.x(), origin.z());
|
||||
Direction orientation = Direction.Plane.HORIZONTAL.getRandomDirection(random);
|
||||
OceanMonumentPieces.MonumentBuilding building = new OceanMonumentPieces.MonumentBuilding(
|
||||
random,
|
||||
origin.getMinBlockX() - 29,
|
||||
origin.getMinBlockZ() - 29,
|
||||
orientation
|
||||
);
|
||||
return new StructureStart(
|
||||
structure,
|
||||
origin,
|
||||
0,
|
||||
new PiecesContainer(List.of(building))
|
||||
);
|
||||
}
|
||||
|
||||
private static NativeStructureStartPlan plan(ChunkPos origin, int baseY) {
|
||||
IrisNativeStructure source = new IrisNativeStructure()
|
||||
.setStructure("minecraft:monument")
|
||||
.setWeight(1);
|
||||
IrisStructurePlacement placement = new IrisStructurePlacement()
|
||||
.setPlacementId("monument-test")
|
||||
.setNativeStructures(new KList<IrisNativeStructure>().qadd(source));
|
||||
return new NativeStructureStartPlan(
|
||||
placement,
|
||||
source,
|
||||
origin.x(),
|
||||
origin.z(),
|
||||
baseY
|
||||
);
|
||||
}
|
||||
|
||||
private static NativeStructureOwnershipRecord ownership(
|
||||
ChunkPos origin, StructureStart start, OceanMonumentStructure structure) {
|
||||
return NativeStructureOwnershipFingerprint.capture(
|
||||
"minecraft:monument",
|
||||
start,
|
||||
plan(origin, NativeStructureReferenceEnvelope.contentBounds(start).minY()),
|
||||
NativeStructureReferenceEnvelope.referenceBounds(
|
||||
start, structure, new IrisStructureTerrain())
|
||||
);
|
||||
}
|
||||
|
||||
private static StructureStart poolStart(
|
||||
OceanMonumentStructure structure, ChunkPos origin, StructurePoolElement element) {
|
||||
BlockPos position = new BlockPos(
|
||||
origin.getMiddleBlockX(), 64, origin.getMiddleBlockZ());
|
||||
PoolElementStructurePiece piece = new PoolElementStructurePiece(
|
||||
null, element, position, 0, Rotation.NONE,
|
||||
new BoundingBox(position), LiquidSettings.APPLY_WATERLOGGING);
|
||||
return new StructureStart(
|
||||
structure, origin, 0, new PiecesContainer(List.of(piece)));
|
||||
}
|
||||
|
||||
private static final class IdentityStringPoolElement extends StructurePoolElement {
|
||||
private IdentityStringPoolElement() {
|
||||
super(StructureTemplatePool.Projection.RIGID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3i getSize(StructureTemplateManager templateManager, Rotation rotation) {
|
||||
return new Vec3i(1, 1, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StructureTemplate.JigsawBlockInfo> getShuffledJigsawBlocks(
|
||||
StructureTemplateManager templateManager, BlockPos position,
|
||||
Rotation rotation, RandomSource random) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoundingBox getBoundingBox(StructureTemplateManager templateManager,
|
||||
BlockPos position, Rotation rotation) {
|
||||
return new BoundingBox(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean place(StructureTemplateManager templateManager,
|
||||
WorldGenLevel world, StructureManager structureManager,
|
||||
ChunkGenerator chunkGenerator, BlockPos position, BlockPos pivot,
|
||||
Rotation rotation, BoundingBox area, RandomSource random,
|
||||
LiquidSettings liquidSettings, boolean keepJigsaws) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StructurePoolElementType<?> getType() {
|
||||
return StructurePoolElementType.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IdentityStringPoolElement@"
|
||||
+ Integer.toHexString(System.identityHashCode(this));
|
||||
}
|
||||
}
|
||||
|
||||
private static ChunkPos outsideLiveBounds(NativeStructureOwnershipRecord ownership,
|
||||
StructureStart start) {
|
||||
for (int chunkX = ownership.referenceMinChunkX();
|
||||
chunkX <= ownership.referenceMaxChunkX(); chunkX++) {
|
||||
for (int chunkZ = ownership.referenceMinChunkZ();
|
||||
chunkZ <= ownership.referenceMaxChunkZ(); chunkZ++) {
|
||||
ChunkPos candidate = new ChunkPos(chunkX, chunkZ);
|
||||
if (!start.getBoundingBox().intersects(
|
||||
candidate.getMinBlockX(), candidate.getMinBlockZ(),
|
||||
candidate.getMaxBlockX(), candidate.getMaxBlockZ())) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
package art.arcane.iris.nativegen;
|
||||
|
||||
import art.arcane.iris.engine.framework.NativeStructureOwnershipRecord;
|
||||
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
|
||||
import art.arcane.iris.engine.framework.StructurePlacementGrid;
|
||||
import art.arcane.iris.engine.object.IrisNativeStructure;
|
||||
import art.arcane.iris.engine.object.IrisStructurePlacement;
|
||||
import art.arcane.iris.engine.object.IrisStructureTerrain;
|
||||
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.levelgen.LegacyRandomSource;
|
||||
import net.minecraft.world.level.levelgen.RandomSupport;
|
||||
import net.minecraft.world.level.levelgen.WorldgenRandom;
|
||||
import net.minecraft.world.level.levelgen.structure.StructurePiece;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
||||
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentPieces;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentStructure;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NativeStructureOwnershipRecoveryTest {
|
||||
@BeforeClass
|
||||
public static void bootstrapMinecraftRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingSidecarRecoversExactPolicyEnvelopeAndLocatorAfterReload() {
|
||||
long seed = 5519231L;
|
||||
ChunkPos origin = new ChunkPos(7, -5);
|
||||
OceanMonumentStructure structure = structure();
|
||||
StructureStart expected = monumentStart(structure, origin, seed);
|
||||
NativeStructureVerticalPlacer.alignOceanMonumentToSeaLevel(
|
||||
expected, 0, 80, -64, 320);
|
||||
int expectedLocatorY = NativeStructureOwnershipFingerprint.locatorY(expected);
|
||||
NativeStructureStartPlan plan = plan(origin, "recovery-exact", 24);
|
||||
StructureStart reloaded = new StructureStart(
|
||||
structure,
|
||||
origin,
|
||||
expected.getReferences(),
|
||||
OceanMonumentStructure.regeneratePiecesAfterLoad(
|
||||
origin, seed, new PiecesContainer(expected.getPieces()))
|
||||
);
|
||||
|
||||
NativeStructureOwnershipRecord recovered =
|
||||
NativeStructureOwnershipRecovery.proveCandidate(
|
||||
"minecraft:monument", structure, reloaded,
|
||||
plan, expected, false);
|
||||
|
||||
assertNotNull(recovered);
|
||||
assertEquals(StructurePlacementGrid.placementIdentity(plan.placement()),
|
||||
recovered.placementIdentity());
|
||||
assertEquals(expectedLocatorY, recovered.locatorY());
|
||||
assertNotEquals(NativeStructureOwnershipFingerprint.locatorY(reloaded),
|
||||
recovered.locatorY());
|
||||
assertEquals(IrisStructureTerrainMode.FORCE_CARVE,
|
||||
recovered.restoredDecision().terrain().resolvedMode());
|
||||
assertEquals(24, recovered.restoredDecision().terrain().getHorizontalPadding());
|
||||
assertTrue(recovered.referenceMinChunkX()
|
||||
< NativeStructureReferenceEnvelope.contentBounds(reloaded).minX() >> 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void naturalSameKeyStartRemainsUnownedWhenOriginIsAmbiguous() {
|
||||
long seed = 22817L;
|
||||
ChunkPos origin = new ChunkPos(-3, 6);
|
||||
OceanMonumentStructure structure = structure();
|
||||
StructureStart natural = monumentStart(structure, origin, seed);
|
||||
NativeStructureStartPlan plan = plan(origin, "natural-ambiguous", 0);
|
||||
|
||||
NativeStructureOwnershipRecord recovered =
|
||||
NativeStructureOwnershipRecovery.proveCandidate(
|
||||
"minecraft:monument", structure, natural,
|
||||
plan, natural, true);
|
||||
|
||||
assertNull(recovered);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changedPlacementIdentityOrGeometryCannotRecoverOwnership() {
|
||||
long seed = 991723L;
|
||||
ChunkPos origin = new ChunkPos(2, 4);
|
||||
OceanMonumentStructure structure = structure();
|
||||
StructureStart expected = monumentStart(structure, origin, seed);
|
||||
StructureStart changedGeometry = monumentStart(structure, origin, seed);
|
||||
for (StructurePiece piece : changedGeometry.getPieces()) {
|
||||
piece.move(1, 0, 0);
|
||||
}
|
||||
NativeStructureStartPlan originalIdentity = plan(
|
||||
origin, "original-identity", 0);
|
||||
NativeStructureStartPlan changedIdentity = plan(
|
||||
new ChunkPos(origin.x() + 1, origin.z()), "changed-identity", 0);
|
||||
|
||||
assertNotEquals(
|
||||
StructurePlacementGrid.placementIdentity(originalIdentity.placement()),
|
||||
StructurePlacementGrid.placementIdentity(changedIdentity.placement()));
|
||||
assertNull(NativeStructureOwnershipRecovery.proveCandidate(
|
||||
"minecraft:monument", structure, expected,
|
||||
changedIdentity, expected, false));
|
||||
assertNull(NativeStructureOwnershipRecovery.proveCandidate(
|
||||
"minecraft:monument", structure, changedGeometry,
|
||||
plan(origin, "geometry-check", 0), expected, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recoveredEnvelopeStillRequiresExactFingerprint() {
|
||||
long seed = 81337L;
|
||||
ChunkPos origin = new ChunkPos(-8, -9);
|
||||
OceanMonumentStructure structure = structure();
|
||||
StructureStart expected = monumentStart(structure, origin, seed);
|
||||
NativeStructureOwnershipRecord recovered =
|
||||
NativeStructureOwnershipRecovery.proveCandidate(
|
||||
"minecraft:monument", structure, expected,
|
||||
plan(origin, "fingerprint-check", 16), expected, false);
|
||||
assertNotNull(recovered);
|
||||
|
||||
StructureStart moved = monumentStart(structure, origin, seed);
|
||||
for (StructurePiece piece : moved.getPieces()) {
|
||||
piece.move(0, 0, 1);
|
||||
}
|
||||
|
||||
assertFalse(NativeStructureOwnershipFingerprint.matches(recovered, moved));
|
||||
}
|
||||
|
||||
private static OceanMonumentStructure structure() {
|
||||
return new OceanMonumentStructure(
|
||||
new OceanMonumentStructure.StructureSettings(HolderSet.empty()));
|
||||
}
|
||||
|
||||
private static StructureStart monumentStart(OceanMonumentStructure structure,
|
||||
ChunkPos origin, long seed) {
|
||||
WorldgenRandom random = new WorldgenRandom(
|
||||
new LegacyRandomSource(RandomSupport.generateUniqueSeed()));
|
||||
random.setLargeFeatureSeed(seed, origin.x(), origin.z());
|
||||
Direction orientation = Direction.Plane.HORIZONTAL.getRandomDirection(random);
|
||||
OceanMonumentPieces.MonumentBuilding building =
|
||||
new OceanMonumentPieces.MonumentBuilding(
|
||||
random,
|
||||
origin.getMinBlockX() - 29,
|
||||
origin.getMinBlockZ() - 29,
|
||||
orientation
|
||||
);
|
||||
return new StructureStart(
|
||||
structure, origin, 0, new PiecesContainer(List.of(building)));
|
||||
}
|
||||
|
||||
private static NativeStructureStartPlan plan(ChunkPos origin,
|
||||
String placementId,
|
||||
int horizontalPadding) {
|
||||
IrisNativeStructure source = new IrisNativeStructure()
|
||||
.setStructure("minecraft:monument")
|
||||
.setWeight(1);
|
||||
IrisStructureTerrain terrain = new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
|
||||
.setHorizontalPadding(horizontalPadding);
|
||||
IrisStructurePlacement placement = new IrisStructurePlacement()
|
||||
.setPlacementId(placementId)
|
||||
.setTerrain(terrain)
|
||||
.setNativeStructures(new KList<IrisNativeStructure>().qadd(source));
|
||||
return new NativeStructureStartPlan(
|
||||
placement,
|
||||
source,
|
||||
origin.x(),
|
||||
origin.z(),
|
||||
NativeStructureReferenceEnvelope.contentBounds(
|
||||
monumentStart(structure(), origin, 1L)).minY()
|
||||
);
|
||||
}
|
||||
}
|
||||
+273
-27
@@ -7,22 +7,35 @@ import art.arcane.iris.engine.object.IrisStructureYBand;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.Rotation;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.dimension.BuiltinDimensionTypes;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.StructurePiece;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
||||
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
|
||||
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockIgnoreProcessor;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -31,14 +44,16 @@ import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.BitSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NativeStructurePostProcessorEncaseTest {
|
||||
@@ -98,6 +113,96 @@ public class NativeStructurePostProcessorEncaseTest {
|
||||
state(blocks, bounds.minX(), 0, bounds.minZ()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultEncaseMaterialFollowsVanillaDimensionTerrain() {
|
||||
assertEquals(Blocks.NETHERRACK.defaultBlockState(),
|
||||
NativeStructureTerrainIntegrator.defaultEncaseBlock(Level.NETHER, 64));
|
||||
assertEquals(Blocks.END_STONE.defaultBlockState(),
|
||||
NativeStructureTerrainIntegrator.defaultEncaseBlock(Level.END, 64));
|
||||
assertEquals(Blocks.STONE.defaultBlockState(),
|
||||
NativeStructureTerrainIntegrator.defaultEncaseBlock(Level.OVERWORLD, 64));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceEncaseMaterialFollowsNearbyIrisTerrain() {
|
||||
Map<BlockPos, BlockState> blocks = new HashMap<>();
|
||||
BlockPos fill = new BlockPos(4, 64, 8);
|
||||
put(blocks, fill.getX() + 3, fill.getY(), fill.getZ(), Blocks.TUFF.defaultBlockState());
|
||||
|
||||
assertEquals(Blocks.TUFF.defaultBlockState(),
|
||||
NativeStructureTerrainIntegrator.sourceEncaseBlock(world(blocks), fill));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceMaterialSnapshotIgnoresPreviouslyDecoratedAdjacentChunks() {
|
||||
Map<BlockPos, BlockState> blocks = new HashMap<>();
|
||||
BlockPos fill = new BlockPos(0, 64, 8);
|
||||
put(blocks, -1, 64, 8, Blocks.OAK_PLANKS.defaultBlockState());
|
||||
put(blocks, 3, 64, 8, Blocks.TUFF.defaultBlockState());
|
||||
WorldGenLevel world = world(blocks);
|
||||
NativeStructureTerrainIntegrator.SourceTerrainSnapshot sourceTerrain =
|
||||
sourceSnapshot(world, new BoundingBox(0, 64, 0, 15, 64, 15));
|
||||
|
||||
assertEquals(Blocks.TUFF.defaultBlockState(),
|
||||
NativeStructureTerrainIntegrator.sourceEncaseBlock(
|
||||
world, fill, sourceTerrain));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceMaterialSnapshotIsStableAcrossEarlierFillsInTheSameChunk() {
|
||||
Map<BlockPos, BlockState> blocks = new HashMap<>();
|
||||
BlockPos fill = new BlockPos(0, 64, 8);
|
||||
put(blocks, 3, 64, 8, Blocks.TUFF.defaultBlockState());
|
||||
WorldGenLevel world = world(blocks);
|
||||
NativeStructureTerrainIntegrator.SourceTerrainSnapshot sourceTerrain =
|
||||
sourceSnapshot(world, new BoundingBox(0, 64, 0, 15, 64, 15));
|
||||
put(blocks, 1, 64, 8, Blocks.OAK_PLANKS.defaultBlockState());
|
||||
|
||||
assertEquals(Blocks.TUFF.defaultBlockState(),
|
||||
NativeStructureTerrainIntegrator.sourceEncaseBlock(
|
||||
world, fill, sourceTerrain));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceMaterialRejectsStructureBlocksAndOresBeforeDimensionFallback() {
|
||||
Map<BlockPos, BlockState> blocks = new HashMap<>();
|
||||
BlockPos fill = new BlockPos(4, -20, 8);
|
||||
put(blocks, 3, -20, 8, Blocks.OAK_PLANKS.defaultBlockState());
|
||||
put(blocks, 5, -20, 8, Blocks.STONE_BRICKS.defaultBlockState());
|
||||
put(blocks, 4, -20, 7, Blocks.IRON_ORE.defaultBlockState());
|
||||
|
||||
assertEquals(Blocks.DEEPSLATE.defaultBlockState(),
|
||||
NativeStructureTerrainIntegrator.sourceEncaseBlock(world(blocks), fill));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceMaterialSnapshotOnlyAllocatesActualFillLayers() {
|
||||
StructureStart start = start(TerrainAdjustment.BURY, 64);
|
||||
BoundingBox area = new BoundingBox(0, -2032, 0, 15, 2030, 15);
|
||||
NativeStructureTerrainIntegrator.SourceTerrainSnapshot sourceTerrain =
|
||||
NativeStructureTerrainIntegrator.captureSourceTerrain(
|
||||
world(new HashMap<>()), area,
|
||||
List.of(new NativeStructureTerrainIntegrator.TerrainTarget(
|
||||
"test:bounded-snapshot", start,
|
||||
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))));
|
||||
|
||||
assertNotNull(sourceTerrain);
|
||||
assertEquals(23 * 16 * 16, sourceTerrain.sampledCells());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customDimensionKeyUsesRegisteredVanillaDimensionType() {
|
||||
ResourceKey<Level> customDimension = ResourceKey.create(
|
||||
Registries.DIMENSION, Identifier.parse("iris:custom_nether"));
|
||||
|
||||
assertEquals(Blocks.NETHERRACK.defaultBlockState(),
|
||||
NativeStructureTerrainIntegrator.defaultEncaseBlock(
|
||||
customDimension, BuiltinDimensionTypes.NETHER, null, 64));
|
||||
assertEquals(Blocks.END_STONE.defaultBlockState(),
|
||||
NativeStructureTerrainIntegrator.defaultEncaseBlock(
|
||||
customDimension, BuiltinDimensionTypes.END, null, 64));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configuredEncasePaletteReplacesTheDefaultMaterial() {
|
||||
StructureStart start = start(TerrainAdjustment.BURY, 64);
|
||||
@@ -129,58 +234,170 @@ public class NativeStructurePostProcessorEncaseTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buryAndEncapsulateAdaptationsAutoDefaultToEncase() {
|
||||
for (TerrainAdjustment adjustment : List.of(TerrainAdjustment.BURY, TerrainAdjustment.ENCAPSULATE)) {
|
||||
public void omittedTerrainConfigurationRetainsSourceSemantics() {
|
||||
for (TerrainAdjustment adjustment : TerrainAdjustment.values()) {
|
||||
IrisStructureTerrain resolved = NativeStructureTerrainIntegrator.resolveNativeTerrain(
|
||||
start(adjustment, 64), null);
|
||||
|
||||
assertEquals(IrisStructureTerrainMode.ENCASE, resolved.resolvedMode());
|
||||
assertEquals(3, resolved.getHorizontalPadding());
|
||||
assertEquals(3, resolved.getCeilingPadding());
|
||||
assertEquals(3, resolved.getFloorPadding());
|
||||
assertNull(resolved.getEncasePalette());
|
||||
assertEquals(IrisStructureTerrainMode.SOURCE, resolved.resolvedMode());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void otherAdaptationsNeverAutoDefaultToEncase() {
|
||||
for (TerrainAdjustment adjustment : List.of(
|
||||
TerrainAdjustment.NONE, TerrainAdjustment.BEARD_THIN, TerrainAdjustment.BEARD_BOX)) {
|
||||
assertNull(NativeStructureTerrainIntegrator.resolveNativeTerrain(
|
||||
start(adjustment, 64), null));
|
||||
}
|
||||
public void sourceBuryUsesTheVanillaSixByTwelveInfluenceEnvelope() {
|
||||
StructureStart start = start(TerrainAdjustment.BURY, 64);
|
||||
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
|
||||
int groundY = bounds.minY();
|
||||
BoundingBox area = new BoundingBox(
|
||||
bounds.minX() - 7, groundY - 13, bounds.minZ(),
|
||||
bounds.maxX(), groundY + 13, bounds.maxZ());
|
||||
Map<BlockPos, BlockState> blocks = new HashMap<>();
|
||||
|
||||
NativeStructureTerrainIntegrator.integrateTerrain(
|
||||
world(blocks), area, "test:bury", start,
|
||||
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE), null);
|
||||
|
||||
assertEquals(Blocks.STONE.defaultBlockState(),
|
||||
state(blocks, bounds.minX() - 5, groundY, bounds.minZ()));
|
||||
assertEquals(Blocks.STONE.defaultBlockState(),
|
||||
state(blocks, bounds.minX(), groundY + 11, bounds.minZ()));
|
||||
assertEquals(Blocks.AIR.defaultBlockState(),
|
||||
state(blocks, bounds.minX() - 6, groundY, bounds.minZ()));
|
||||
assertEquals(Blocks.AIR.defaultBlockState(),
|
||||
state(blocks, bounds.minX(), groundY + 12, bounds.minZ()));
|
||||
assertEquals(Blocks.AIR.defaultBlockState(),
|
||||
state(blocks, bounds.minX() - 7, groundY, bounds.minZ()));
|
||||
assertEquals(Blocks.AIR.defaultBlockState(),
|
||||
state(blocks, bounds.minX(), groundY + 13, bounds.minZ()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceEncapsulateWrapsTheFullPieceBounds() {
|
||||
StructureStart start = start(TerrainAdjustment.ENCAPSULATE, 64);
|
||||
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
|
||||
BoundingBox area = new BoundingBox(
|
||||
bounds.minX() - 13, bounds.minY(), bounds.minZ(),
|
||||
bounds.maxX(), bounds.maxY() + 13, bounds.maxZ());
|
||||
Map<BlockPos, BlockState> blocks = new HashMap<>();
|
||||
|
||||
NativeStructureTerrainIntegrator.integrateTerrain(
|
||||
world(blocks), area, "test:encapsulate", start,
|
||||
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE), null);
|
||||
|
||||
assertEquals(Blocks.STONE.defaultBlockState(),
|
||||
state(blocks, bounds.minX() - 11, bounds.minY(), bounds.minZ()));
|
||||
assertEquals(Blocks.STONE.defaultBlockState(),
|
||||
state(blocks, bounds.minX(), bounds.maxY() + 11, bounds.minZ()));
|
||||
assertEquals(Blocks.AIR.defaultBlockState(),
|
||||
state(blocks, bounds.minX() - 12, bounds.minY(), bounds.minZ()));
|
||||
assertEquals(Blocks.AIR.defaultBlockState(),
|
||||
state(blocks, bounds.minX(), bounds.maxY() + 12, bounds.minZ()));
|
||||
assertEquals(Blocks.AIR.defaultBlockState(),
|
||||
state(blocks, bounds.minX() - 13, bounds.minY(), bounds.minZ()));
|
||||
assertEquals(Blocks.AIR.defaultBlockState(),
|
||||
state(blocks, bounds.minX(), bounds.maxY() + 13, bounds.minZ()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceTerrainDensityOnlyUsesRigidPoolPieces() {
|
||||
PoolElementStructurePiece terrainMatching = poolPiece(StructureTemplatePool.Projection.TERRAIN_MATCHING);
|
||||
PoolElementStructurePiece rigid = poolPiece(StructureTemplatePool.Projection.RIGID);
|
||||
|
||||
assertFalse(NativeStructureTerrainIntegrator.isSourceRigidPiece(terrainMatching));
|
||||
assertTrue(NativeStructureTerrainIntegrator.isSourceRigidPiece(rigid));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceJunctionTerrainUsesTheStrictLowerTwelveBlockEnvelope() {
|
||||
assertTrue(NativeStructureTerrainIntegrator.insideSourceJunctionEnvelope(0, -1, 0));
|
||||
assertTrue(NativeStructureTerrainIntegrator.insideSourceJunctionEnvelope(0, -11, 0));
|
||||
assertFalse(NativeStructureTerrainIntegrator.insideSourceJunctionEnvelope(0, 0, 0));
|
||||
assertFalse(NativeStructureTerrainIntegrator.insideSourceJunctionEnvelope(0, -12, 0));
|
||||
assertFalse(NativeStructureTerrainIntegrator.insideSourceJunctionEnvelope(12, -1, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitTerrainConfigurationWinsOverAutoEncase() {
|
||||
IrisStructureTerrain configured = new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.SOURCE);
|
||||
.setMode(IrisStructureTerrainMode.PRESERVE);
|
||||
|
||||
assertSame(configured, NativeStructureTerrainIntegrator.resolveNativeTerrain(
|
||||
start(TerrainAdjustment.BURY, 64), configured));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacyTemplateAirIsClearedAfterEveryTerrainFillPath() {
|
||||
IrisStructureTerrain source = new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.SOURCE);
|
||||
IrisStructureTerrain preserve = new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.PRESERVE);
|
||||
IrisStructureTerrain encase = new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.ENCASE);
|
||||
|
||||
assertTrue(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
|
||||
start(TerrainAdjustment.BURY, 64), source));
|
||||
assertTrue(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
|
||||
start(TerrainAdjustment.BEARD_BOX, 64), source));
|
||||
assertTrue(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
|
||||
start(TerrainAdjustment.NONE, 64), encase));
|
||||
assertFalse(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
|
||||
start(TerrainAdjustment.NONE, 64), source));
|
||||
assertFalse(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
|
||||
start(TerrainAdjustment.BURY, 64), preserve));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encaseReservesTheNeighborChunkEnvelope() {
|
||||
StructureStart generated = start(TerrainAdjustment.BURY, 64);
|
||||
BoundingBox content = generated.getBoundingBox();
|
||||
BoundingBox sourceBounds = generated.getBoundingBox();
|
||||
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
|
||||
IrisStructureTerrain terrain = new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.ENCASE)
|
||||
.setHorizontalPadding(4);
|
||||
|
||||
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
|
||||
generated,
|
||||
generated.getStructure(),
|
||||
0,
|
||||
null,
|
||||
new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.ENCASE)
|
||||
.setHorizontalPadding(4));
|
||||
terrain);
|
||||
BoundingBox referenceBounds = NativeStructureReferenceEnvelope.referenceBounds(
|
||||
wrapped, wrapped.getStructure(), terrain);
|
||||
|
||||
assertEquals(content.minX() - 4, wrapped.getBoundingBox().minX());
|
||||
assertEquals(content.maxX() + 4, wrapped.getBoundingBox().maxX());
|
||||
assertEquals(content.minZ() - 4, wrapped.getBoundingBox().minZ());
|
||||
assertEquals(content.maxZ() + 4, wrapped.getBoundingBox().maxZ());
|
||||
assertEquals(2, wrapped.getPieces().stream()
|
||||
.filter(NativeStructureReferenceEnvelope::isMarker)
|
||||
.count());
|
||||
assertEquals(sourceBounds, wrapped.getBoundingBox());
|
||||
assertEquals(content.minX() - 4, referenceBounds.minX());
|
||||
assertEquals(content.maxX() + 4, referenceBounds.maxX());
|
||||
assertEquals(content.minZ() - 4, referenceBounds.minZ());
|
||||
assertEquals(content.maxZ() + 4, referenceBounds.maxZ());
|
||||
assertEquals(generated.getPieces().size(), wrapped.getPieces().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void referenceEnvelopeRejectsChunkCoordinateOverflow() {
|
||||
StructureStart generated = start(TerrainAdjustment.NONE, 64);
|
||||
StructureStart extreme = new StructureStart(
|
||||
generated.getStructure(), new ChunkPos(Integer.MAX_VALUE, 0), 0,
|
||||
new PiecesContainer(generated.getPieces()));
|
||||
|
||||
assertThrows(IllegalStateException.class, () ->
|
||||
NativeStructureReferenceEnvelope.referenceBounds(
|
||||
extreme, extreme.getStructure(), new IrisStructureTerrain()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitTerrainEnvelopeDoesNotInheritSourceAdjustment() {
|
||||
StructureStart generated = start(TerrainAdjustment.BURY, 64);
|
||||
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
|
||||
IrisStructureTerrain terrain = new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.ENCASE)
|
||||
.setHorizontalPadding(112);
|
||||
|
||||
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
|
||||
generated, generated.getStructure(), 0, terrain);
|
||||
BoundingBox referenceBounds = NativeStructureReferenceEnvelope.referenceBounds(
|
||||
wrapped, wrapped.getStructure(), terrain);
|
||||
|
||||
assertEquals(content.minX() - 112, referenceBounds.minX());
|
||||
assertEquals(content.maxX() + 112, referenceBounds.maxX());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -259,6 +476,24 @@ public class NativeStructurePostProcessorEncaseTest {
|
||||
structure, new ChunkPos(0, 0), 0, new PiecesContainer(List.of(piece)));
|
||||
}
|
||||
|
||||
private static PoolElementStructurePiece poolPiece(StructureTemplatePool.Projection projection) {
|
||||
BlockPos position = new BlockPos(0, 64, 0);
|
||||
StructurePoolElement element = StructurePoolElement.single(
|
||||
"minecraft:empty",
|
||||
Holder.direct(new StructureProcessorList(List.of(
|
||||
BlockIgnoreProcessor.STRUCTURE_AND_AIR))),
|
||||
LiquidSettings.APPLY_WATERLOGGING
|
||||
).apply(projection);
|
||||
return new PoolElementStructurePiece(
|
||||
null,
|
||||
element,
|
||||
position,
|
||||
0,
|
||||
Rotation.NONE,
|
||||
new BoundingBox(position),
|
||||
LiquidSettings.APPLY_WATERLOGGING);
|
||||
}
|
||||
|
||||
private static WorldGenLevel world(Map<BlockPos, BlockState> blocks) {
|
||||
InvocationHandler handler = (proxy, method, arguments) -> {
|
||||
String methodName = method.getName();
|
||||
@@ -275,6 +510,9 @@ public class NativeStructurePostProcessorEncaseTest {
|
||||
if (methodName.equals("getSeed")) {
|
||||
return TEST_SEED;
|
||||
}
|
||||
if (methodName.equals("getLevel")) {
|
||||
return null;
|
||||
}
|
||||
if (methodName.equals("hashCode")) {
|
||||
return System.identityHashCode(proxy);
|
||||
}
|
||||
@@ -290,6 +528,14 @@ public class NativeStructurePostProcessorEncaseTest {
|
||||
WorldGenLevel.class.getClassLoader(), new Class<?>[]{WorldGenLevel.class}, handler);
|
||||
}
|
||||
|
||||
private static NativeStructureTerrainIntegrator.SourceTerrainSnapshot sourceSnapshot(
|
||||
WorldGenLevel world, BoundingBox area) {
|
||||
BitSet requiredLayers = new BitSet(area.getYSpan());
|
||||
requiredLayers.set(0, area.getYSpan());
|
||||
return NativeStructureTerrainIntegrator.SourceTerrainSnapshot.capture(
|
||||
world, area, requiredLayers);
|
||||
}
|
||||
|
||||
private static void put(Map<BlockPos, BlockState> blocks,
|
||||
int x, int y, int z, BlockState state) {
|
||||
blocks.put(new BlockPos(x, y, z), state);
|
||||
|
||||
+62
@@ -1,5 +1,7 @@
|
||||
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.Direction;
|
||||
import net.minecraft.core.HolderSet;
|
||||
@@ -16,7 +18,12 @@ import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentStru
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
@@ -80,6 +87,25 @@ public class NativeStructurePostProcessorMonumentTest {
|
||||
assertEquals(51, start.getBoundingBox().maxY());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void referenceEnvelopeDoesNotAddMonumentPieces() {
|
||||
StructureStart start = monumentStart(1337L);
|
||||
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
|
||||
start,
|
||||
start.getStructure(),
|
||||
0,
|
||||
new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
|
||||
.setHorizontalPadding(4));
|
||||
|
||||
int offset = NativeStructureVerticalPlacer.applyVerticalPlacement(
|
||||
wrapped, "minecraft:monument", 0, 63, -64, 320,
|
||||
false, false, null, (x, z) -> 0);
|
||||
|
||||
assertEquals(0, offset);
|
||||
assertEquals(1, wrapped.getPieces().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void vanillaReloadRegenerationIsRealignedBeforePlacement() {
|
||||
long seed = 1337L;
|
||||
@@ -101,6 +127,42 @@ public class NativeStructurePostProcessorMonumentTest {
|
||||
assertEquals(48, reloaded.getBoundingBox().maxY());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void neighboringChunkPlacementRealignsReloadedMonumentOnce() throws Exception {
|
||||
long seed = 1337L;
|
||||
ChunkPos chunkPos = new ChunkPos(0, 0);
|
||||
StructureStart initial = monumentStart(seed);
|
||||
NativeStructureVerticalPlacer.applyVerticalPlacement(
|
||||
initial, "minecraft:monument", 0, 50, -256, 512,
|
||||
false, false, null, (x, z) -> 0);
|
||||
PiecesContainer regenerated = OceanMonumentStructure.regeneratePiecesAfterLoad(
|
||||
chunkPos, seed, new PiecesContainer(initial.getPieces()));
|
||||
StructureStart reloaded = new StructureStart(
|
||||
monumentStructure(), chunkPos, 0, regenerated);
|
||||
ExecutorService executor = Executors.newFixedThreadPool(8);
|
||||
CountDownLatch release = new CountDownLatch(1);
|
||||
List<Future<?>> futures = new ArrayList<>();
|
||||
try {
|
||||
for (int task = 0; task < 16; task++) {
|
||||
futures.add(executor.submit(() -> {
|
||||
release.await();
|
||||
NativeStructureVerticalPlacer.ensureMonumentSeaLevelAlignment(
|
||||
reloaded, "minecraft:monument", 0, 50, -256, 512);
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
release.countDown();
|
||||
for (Future<?> future : futures) {
|
||||
future.get();
|
||||
}
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
|
||||
assertEquals(26, reloaded.getBoundingBox().minY());
|
||||
assertEquals(48, reloaded.getBoundingBox().maxY());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void impossibleSeaLevelAlignmentFailsInsteadOfClippingTheMonument() {
|
||||
StructureStart start = monumentStart(1337L);
|
||||
|
||||
+21
@@ -135,6 +135,27 @@ public class NativeStructurePostProcessorScatteredFeatureTest {
|
||||
assertTrue(field.trySetAccessible());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void datapackOverridesOfSpecialVanillaIdsUseGenericPlacement() {
|
||||
StructureStart pyramidOverride = swampStart(
|
||||
new SwampHutPiece(RandomSource.create(43L), 0, 0));
|
||||
int pyramidMinY = pyramidOverride.getBoundingBox().minY();
|
||||
int pyramidOffset = NativeStructureVerticalPlacer.applyVerticalPlacement(
|
||||
pyramidOverride, "minecraft:desert_pyramid", 5,
|
||||
63, -64, 320, false, false, null, (x, z) -> 200);
|
||||
|
||||
assertEquals(5, pyramidOffset);
|
||||
assertEquals(pyramidMinY + 5, pyramidOverride.getBoundingBox().minY());
|
||||
|
||||
StructureStart monumentOverride = swampStart(
|
||||
new SwampHutPiece(RandomSource.create(47L), 0, 0));
|
||||
int monumentMinY = monumentOverride.getBoundingBox().minY();
|
||||
NativeStructureVerticalPlacer.ensureMonumentSeaLevelAlignment(
|
||||
monumentOverride, "minecraft:monument", 0, 40, -64, 320);
|
||||
|
||||
assertEquals(monumentMinY, monumentOverride.getBoundingBox().minY());
|
||||
}
|
||||
|
||||
private static StructureStart desertStart(StructurePiece piece) {
|
||||
Structure structure = new DesertPyramidStructure(new Structure.StructureSettings(HolderSet.empty()));
|
||||
return start(structure, piece);
|
||||
|
||||
+416
-59
@@ -5,23 +5,41 @@ import art.arcane.iris.engine.object.IrisStructureCarveShape;
|
||||
import art.arcane.iris.engine.object.IrisStructureTerrain;
|
||||
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import com.mojang.serialization.Codec;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.core.IdMapper;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.Rotation;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||
import net.minecraft.world.level.chunk.PalettedContainerFactory;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.Strategy;
|
||||
import net.minecraft.world.level.chunk.UpgradeData;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.StructurePiece;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
||||
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
|
||||
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
|
||||
@@ -35,15 +53,22 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NativeStructurePostProcessorSurfaceTerrainTest {
|
||||
@@ -60,34 +85,148 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onlySurfaceBeardThinStructuresPrepareTerrain() {
|
||||
public void sourceBeardAdjustmentsPrepareTerrainAcrossDeclaredSteps() {
|
||||
assertTrue(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
|
||||
TerrainAdjustment.BEARD_THIN, GenerationStep.Decoration.SURFACE_STRUCTURES));
|
||||
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
|
||||
TerrainAdjustment.BURY, GenerationStep.Decoration.SURFACE_STRUCTURES));
|
||||
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
|
||||
TerrainAdjustment.BEARD_BOX, GenerationStep.Decoration.SURFACE_STRUCTURES));
|
||||
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
|
||||
TerrainAdjustment.ENCAPSULATE, GenerationStep.Decoration.SURFACE_STRUCTURES));
|
||||
assertTrue(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
|
||||
TerrainAdjustment.BEARD_BOX, GenerationStep.Decoration.FLUID_SPRINGS));
|
||||
assertTrue(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
|
||||
TerrainAdjustment.BEARD_THIN, GenerationStep.Decoration.UNDERGROUND_STRUCTURES));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undergroundAdjustmentsNeverPrepareSurfaceTerrain() {
|
||||
for (TerrainAdjustment adjustment : List.of(
|
||||
TerrainAdjustment.BEARD_THIN,
|
||||
TerrainAdjustment.BURY,
|
||||
TerrainAdjustment.BEARD_BOX,
|
||||
TerrainAdjustment.ENCAPSULATE)) {
|
||||
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
|
||||
adjustment, GenerationStep.Decoration.UNDERGROUND_STRUCTURES));
|
||||
public void nonBeardAdjustmentsDoNotPrepareSurfaceTerrain() {
|
||||
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
|
||||
TerrainAdjustment.BURY, GenerationStep.Decoration.SURFACE_STRUCTURES));
|
||||
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
|
||||
TerrainAdjustment.ENCAPSULATE, GenerationStep.Decoration.SURFACE_STRUCTURES));
|
||||
assertFalse(NativeStructureSurfaceFitter.shouldPrepareSurfaceTerrain(
|
||||
TerrainAdjustment.NONE, GenerationStep.Decoration.FLUID_SPRINGS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitTerrainOverrideDisablesSourceBeardFitting() {
|
||||
StructureStart start = desertStart(TerrainAdjustment.BEARD_BOX);
|
||||
|
||||
assertTrue(NativeStructureSurfaceFitter.requiresSurfaceTerrain(
|
||||
new NativeStructureTerrainIntegrator.TerrainTarget(
|
||||
"test:beard_box", start,
|
||||
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))));
|
||||
assertFalse(NativeStructureSurfaceFitter.requiresSurfaceTerrain(
|
||||
new NativeStructureTerrainIntegrator.TerrainTarget(
|
||||
"test:beard_box", start,
|
||||
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.PRESERVE))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beardBoxUsesTheFullRigidHeightWhileBeardThinUsesTheGroundPlane() {
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor thin =
|
||||
new NativeStructureSurfaceFitter.SurfaceAnchor(0, 4, 0, 4, 64, 2);
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor box =
|
||||
new NativeStructureSurfaceFitter.SurfaceAnchor(
|
||||
0, 4, 0, 4, 64, 2, 65, 90);
|
||||
|
||||
assertEquals(80, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(thin), 2, 2, 80));
|
||||
assertEquals(64, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(box), 2, 2, 80));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beardInfluenceBeginsAtGroundYRatherThanTheSurfaceBlock() {
|
||||
BoundingBox bounds = new BoundingBox(0, 64, 0, 4, 90, 4);
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor thin =
|
||||
NativeStructureSurfaceFitter.surfaceAnchor(
|
||||
bounds, 68, 2, TerrainAdjustment.BEARD_THIN);
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor box =
|
||||
NativeStructureSurfaceFitter.surfaceAnchor(
|
||||
bounds, 68, 2, TerrainAdjustment.BEARD_BOX);
|
||||
|
||||
assertEquals(67, thin.meetY());
|
||||
assertEquals(0, thin.verticalDistance(68));
|
||||
assertEquals(1, thin.verticalDistance(67));
|
||||
assertEquals(0, box.verticalDistance(68));
|
||||
assertEquals(1, box.verticalDistance(67));
|
||||
assertEquals(0, box.verticalDistance(90));
|
||||
assertEquals(1, box.verticalDistance(91));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rigidFootprintsRaiseTerrainAcrossGapsLargerThanTheBeardKernel() {
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor rigid = anchor(72, 2);
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor junction = anchor(72, 1);
|
||||
|
||||
assertEquals(72, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(rigid), 2, 2, 40));
|
||||
assertEquals(40, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(junction), 2, 2, 40));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void elevatedRigidFootprintsBlendIntoAHorizontalRescueTaper() {
|
||||
int originalY = 40;
|
||||
int kernelEdge = NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(anchor(51, 2)), 5, 2, originalY);
|
||||
int gapThirteen = NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(anchor(52, 2)), 5, 2, originalY);
|
||||
int gapEighteen = NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(anchor(57, 2)), 5, 2, originalY);
|
||||
int gapTwentyFour = NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(anchor(63, 2)), 5, 2, originalY);
|
||||
|
||||
assertEquals(originalY, kernelEdge);
|
||||
assertTrue(gapThirteen >= kernelEdge);
|
||||
assertTrue(gapEighteen > gapThirteen);
|
||||
assertTrue(gapTwentyFour > gapEighteen);
|
||||
assertTrue(gapTwentyFour < 63);
|
||||
assertEquals(originalY, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(anchor(63, 2)), 16, 2, originalY));
|
||||
assertEquals(originalY, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(anchor(63, 1)), 5, 2, originalY));
|
||||
assertEquals(80, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(anchor(64, 2)), 5, 2, 80));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void projectedCenterAndRigidChildrenShareTerrainSupportAcrossTheAssembly() {
|
||||
Structure structure = new DesertPyramidStructure(
|
||||
new Structure.StructureSettings(
|
||||
HolderSet.empty(), Map.of(),
|
||||
GenerationStep.Decoration.SURFACE_STRUCTURES,
|
||||
TerrainAdjustment.BEARD_BOX));
|
||||
PoolElementStructurePiece center = rigidPiece(
|
||||
new BoundingBox(0, 71, 0, 3, 80, 3), 1);
|
||||
PoolElementStructurePiece child = rigidPiece(
|
||||
new BoundingBox(10, 65, 0, 13, 76, 3), 7);
|
||||
StructureStart start = new StructureStart(
|
||||
structure, new ChunkPos(0, 0), 0,
|
||||
new PiecesContainer(List.of(center, child)));
|
||||
BoundingBox area = new BoundingBox(0, 60, 0, 15, 90, 15);
|
||||
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, 63, z, Blocks.DIRT.defaultBlockState());
|
||||
put(blocks, x, 64, z, Blocks.GRASS_BLOCK.defaultBlockState());
|
||||
}
|
||||
}
|
||||
|
||||
NativeStructureSurfaceFitter.prepareSurfaceStructures(
|
||||
world(blocks), area,
|
||||
List.of(new NativeStructureTerrainIntegrator.TerrainTarget(
|
||||
"nova_structures:tavern_oak", start,
|
||||
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))),
|
||||
(x, z) -> 64);
|
||||
|
||||
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 1, 71, 1));
|
||||
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 11, 71, 1));
|
||||
assertEquals(Blocks.DIRT.defaultBlockState(), state(blocks, 11, 70, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void surfaceAnchorIsFlushInsideAndUnchangedAtRadius() {
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor anchor = anchor(80, 2);
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor anchor = anchor(72, 2);
|
||||
|
||||
assertEquals(80, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
assertEquals(72, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(anchor), 2, 2, 64));
|
||||
assertEquals(64, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(anchor), 16, 2, 64));
|
||||
@@ -97,54 +236,88 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
||||
|
||||
@Test
|
||||
public void surfaceAnchorRaisesAndLowersThroughTheTaper() {
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor raised = anchor(80, 2);
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor raised = anchor(68, 2);
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor lowered = anchor(64, 2);
|
||||
|
||||
assertEquals(68, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
assertEquals(64, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(raised), 10, 2, 64));
|
||||
assertEquals(76, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(lowered), 10, 2, 80));
|
||||
assertEquals(67, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(lowered), 10, 2, 68));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containingRigidFloorsHaveDeterministicPriority() {
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor rigid = anchor(70, 2);
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor junction = anchor(90, 1);
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor junction = anchor(74, 1);
|
||||
|
||||
assertEquals(70, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(rigid, junction), 2, 2, 64));
|
||||
assertEquals(70, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(junction, rigid), 2, 2, 64));
|
||||
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor weakTie = anchor(48, 1);
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor strongTie = anchor(80, 2);
|
||||
assertEquals(80, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor weakTie = anchor(58, 1);
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor strongTie = anchor(70, 2);
|
||||
assertEquals(70, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(weakTie, strongTie), 2, 2, 64));
|
||||
assertEquals(80, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
assertEquals(70, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(strongTie, weakTie), 2, 2, 64));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stackedRigidPiecesPreserveTheLowerAuthoredSurface() {
|
||||
Structure structure = new DesertPyramidStructure(
|
||||
new Structure.StructureSettings(
|
||||
HolderSet.empty(), Map.of(),
|
||||
GenerationStep.Decoration.SURFACE_STRUCTURES,
|
||||
TerrainAdjustment.BEARD_BOX));
|
||||
PoolElementStructurePiece lower = rigidPiece(
|
||||
new BoundingBox(0, 62, 0, 4, 65, 4), 1);
|
||||
PoolElementStructurePiece upper = rigidPiece(
|
||||
new BoundingBox(0, 66, 0, 4, 78, 4), 1);
|
||||
StructureStart start = new StructureStart(
|
||||
structure, new ChunkPos(0, 0), 0,
|
||||
new PiecesContainer(List.of(upper, lower)));
|
||||
BoundingBox area = new BoundingBox(0, 58, 0, 4, 78, 4);
|
||||
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, 59, z, Blocks.DIRT.defaultBlockState());
|
||||
put(blocks, x, 60, z, Blocks.GRASS_BLOCK.defaultBlockState());
|
||||
}
|
||||
}
|
||||
|
||||
NativeStructureSurfaceFitter.prepareSurfaceStructures(
|
||||
world(blocks), area,
|
||||
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.AIR.defaultBlockState(), state(blocks, 2, 65, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containingFootprintOverridesAnAdjacentPiecesFalloff() {
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor local =
|
||||
new NativeStructureSurfaceFitter.SurfaceAnchor(0, 4, 0, 4, 65, 2);
|
||||
new NativeStructureSurfaceFitter.SurfaceAnchor(0, 4, 0, 4, 66, 2);
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor adjacent =
|
||||
new NativeStructureSurfaceFitter.SurfaceAnchor(5, 9, 0, 4, 80, 2);
|
||||
new NativeStructureSurfaceFitter.SurfaceAnchor(5, 9, 0, 4, 70, 2);
|
||||
|
||||
assertEquals(77, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
assertEquals(65, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(adjacent), 4, 2, 64));
|
||||
assertEquals(65, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
assertEquals(66, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(local, adjacent), 4, 2, 64));
|
||||
assertEquals(65, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
assertEquals(66, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(adjacent, local), 4, 2, 64));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void opposingFalloffsBlendWithoutAnAbruptMidpointSeam() {
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor high =
|
||||
new NativeStructureSurfaceFitter.SurfaceAnchor(0, 0, 0, 0, 80, 2);
|
||||
new NativeStructureSurfaceFitter.SurfaceAnchor(0, 0, 0, 0, 70, 2);
|
||||
NativeStructureSurfaceFitter.SurfaceAnchor low =
|
||||
new NativeStructureSurfaceFitter.SurfaceAnchor(12, 12, 0, 0, 48, 2);
|
||||
new NativeStructureSurfaceFitter.SurfaceAnchor(12, 12, 0, 0, 58, 2);
|
||||
int previous = NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(high, low), 0, 0, 64);
|
||||
|
||||
@@ -154,7 +327,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
||||
int reversed = NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
List.of(low, high), x, 0, 64);
|
||||
assertEquals(forward, reversed);
|
||||
assertTrue(Math.abs(forward - previous) <= 4);
|
||||
assertTrue(Math.abs(forward - previous) <= 6);
|
||||
previous = forward;
|
||||
}
|
||||
assertEquals(64, NativeStructureSurfaceFitter.resolveSurfaceTarget(
|
||||
@@ -195,6 +368,44 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
||||
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(raised, 0, 68, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rigidSurfaceBaseClosesAOneBlockSubsurfaceGap() {
|
||||
Map<BlockPos, BlockState> blocks = new HashMap<>();
|
||||
put(blocks, 0, 62, 0, Blocks.STONE.defaultBlockState());
|
||||
put(blocks, 0, 64, 0, Blocks.GRASS_BLOCK.defaultBlockState());
|
||||
|
||||
NativeStructureSurfaceFitter.applySurfaceColumn(
|
||||
world(blocks), new BlockPos.MutableBlockPos(),
|
||||
0, 0, 64, 64, -64, 319, true);
|
||||
|
||||
assertEquals(Blocks.STONE.defaultBlockState(), state(blocks, 0, 63, 0));
|
||||
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 64, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rigidSurfaceBaseReconstructsAMissingMeetLayer() {
|
||||
Map<BlockPos, BlockState> blocks = new HashMap<>();
|
||||
put(blocks, 0, 62, 0, Blocks.STONE.defaultBlockState());
|
||||
|
||||
NativeStructureSurfaceFitter.applySurfaceColumn(
|
||||
world(blocks), new BlockPos.MutableBlockPos(),
|
||||
0, 0, 64, 64, -64, 319, true);
|
||||
|
||||
assertEquals(Blocks.STONE.defaultBlockState(), state(blocks, 0, 63, 0));
|
||||
assertEquals(Blocks.STONE.defaultBlockState(), state(blocks, 0, 64, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rigidSurfaceBaseReconstructsAMissingMeetLayerAtTheWorldFloor() {
|
||||
Map<BlockPos, BlockState> blocks = new HashMap<>();
|
||||
|
||||
NativeStructureSurfaceFitter.applySurfaceColumn(
|
||||
world(blocks), new BlockPos.MutableBlockPos(),
|
||||
0, 0, -64, -64, -64, 319, true);
|
||||
|
||||
assertEquals(Blocks.STONE.defaultBlockState(), state(blocks, 0, -64, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void raisedSurfaceTerrainDoesNotSliceTreeBlocks() {
|
||||
Map<BlockPos, BlockState> blocks = new HashMap<>();
|
||||
@@ -213,6 +424,31 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
||||
assertEquals(leaves, state(blocks, 0, 68, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearedIntersectingTreeCellsBecomeRaisedTerrainSupport() {
|
||||
ProtoChunk chunk = new ProtoChunk(
|
||||
new ChunkPos(0, 0), UpgradeData.EMPTY,
|
||||
LevelHeightAccessor.create(-64, 384), containerFactory(), null);
|
||||
write(chunk, 0, 63, 0, Blocks.DIRT.defaultBlockState());
|
||||
write(chunk, 0, 64, 0, Blocks.GRASS_BLOCK.defaultBlockState());
|
||||
write(chunk, 0, 66, 0, Blocks.OAK_LOG.defaultBlockState());
|
||||
write(chunk, 0, 68, 0, Blocks.OAK_LEAVES.defaultBlockState());
|
||||
WorldGenLevel world = world(chunk);
|
||||
BoundingBox area = new BoundingBox(0, 63, 0, 0, 68, 0);
|
||||
|
||||
NativeStructureVegetationClearer.clearIntersectingVegetation(
|
||||
world, chunk, area, List.of(
|
||||
new NativeStructureVegetationClearer.VegetationTarget(desertStart(), false)));
|
||||
NativeStructureSurfaceFitter.applySurfaceColumn(
|
||||
world, new BlockPos.MutableBlockPos(),
|
||||
0, 0, 64, 68, -64, 319);
|
||||
|
||||
assertEquals(Blocks.DIRT.defaultBlockState(), chunk.getBlockState(new BlockPos(0, 65, 0)));
|
||||
assertEquals(Blocks.DIRT.defaultBlockState(), chunk.getBlockState(new BlockPos(0, 66, 0)));
|
||||
assertEquals(Blocks.DIRT.defaultBlockState(), chunk.getBlockState(new BlockPos(0, 67, 0)));
|
||||
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), chunk.getBlockState(new BlockPos(0, 68, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loweredFluidColumnsRemainFluidFilled() {
|
||||
Map<BlockPos, BlockState> blocks = new HashMap<>();
|
||||
@@ -437,6 +673,47 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
||||
assertNotSame(first, other);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void concurrentCarveRequestsShareOneFootprintBuild() throws Exception {
|
||||
StructureStart start = desertStart();
|
||||
int requestCount = 16;
|
||||
ExecutorService executor = Executors.newFixedThreadPool(requestCount);
|
||||
CountDownLatch ready = new CountDownLatch(requestCount);
|
||||
CountDownLatch release = new CountDownLatch(1);
|
||||
List<Future<StructureCarvingFootprint>> futures = new ArrayList<>();
|
||||
try {
|
||||
for (int request = 0; request < requestCount; request++) {
|
||||
futures.add(executor.submit(() -> {
|
||||
ready.countDown();
|
||||
release.await();
|
||||
return NativeStructureTerrainIntegrator.carveFootprint(
|
||||
start, 11,
|
||||
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
|
||||
}));
|
||||
}
|
||||
assertTrue(ready.await(5, TimeUnit.SECONDS));
|
||||
release.countDown();
|
||||
StructureCarvingFootprint expected = futures.getFirst().get();
|
||||
for (Future<StructureCarvingFootprint> future : futures) {
|
||||
assertSame(expected, future.get());
|
||||
}
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void carveFootprintCacheStaysInsideItsCellBudget() {
|
||||
for (int index = 0; index < 40; index++) {
|
||||
NativeStructureTerrainIntegrator.carveFootprint(
|
||||
desertStart(), 128,
|
||||
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
|
||||
}
|
||||
|
||||
assertTrue(NativeStructureTerrainIntegrator.cachedCarveFootprintCells()
|
||||
<= NativeStructureTerrainIntegrator.maximumCachedCarveFootprintCells());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void organicCarveNeverCutsBelowTheColumnSupportingFloor() {
|
||||
StructureStart start = desertStart();
|
||||
@@ -594,43 +871,48 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
||||
public void nativeTerrainEnvelopePersistsHorizontalReferenceCoverage() {
|
||||
StructureStart generated = desertStart();
|
||||
BoundingBox content = generated.getBoundingBox();
|
||||
IrisStructureTerrain terrain = new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
|
||||
.setHorizontalPadding(24);
|
||||
|
||||
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
|
||||
generated,
|
||||
generated.getStructure(),
|
||||
0,
|
||||
null,
|
||||
new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
|
||||
.setHorizontalPadding(24));
|
||||
terrain);
|
||||
BoundingBox referenceBounds = NativeStructureReferenceEnvelope.referenceBounds(
|
||||
wrapped, wrapped.getStructure(), terrain);
|
||||
|
||||
assertEquals(content, NativeStructureReferenceEnvelope.contentBounds(wrapped));
|
||||
assertEquals(content.minX() - 24, wrapped.getBoundingBox().minX());
|
||||
assertEquals(content.minZ() - 24, wrapped.getBoundingBox().minZ());
|
||||
assertEquals(content.maxX() + 24, wrapped.getBoundingBox().maxX());
|
||||
assertEquals(content.maxZ() + 24, wrapped.getBoundingBox().maxZ());
|
||||
assertEquals(2, wrapped.getPieces().stream()
|
||||
.filter(NativeStructureReferenceEnvelope::isMarker)
|
||||
.count());
|
||||
assertEquals(content, wrapped.getBoundingBox());
|
||||
assertEquals(content.minX() - 24, referenceBounds.minX());
|
||||
assertEquals(content.minZ() - 24, referenceBounds.minZ());
|
||||
assertEquals(content.maxX() + 24, referenceBounds.maxX());
|
||||
assertEquals(content.maxZ() + 24, referenceBounds.maxZ());
|
||||
assertEquals(generated.getPieces().size(), wrapped.getPieces().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nativeTerrainEnvelopeClipsToMinecraftReferenceCoverage() {
|
||||
public void nativeTerrainEnvelopeClipsOptionalCoverageWithoutDroppingContent() {
|
||||
StructureStart generated = desertStart();
|
||||
ChunkPos origin = generated.getChunkPos();
|
||||
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
|
||||
IrisStructureTerrain terrain = new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
|
||||
.setHorizontalPadding(124);
|
||||
|
||||
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
|
||||
generated,
|
||||
generated.getStructure(),
|
||||
0,
|
||||
null,
|
||||
new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
|
||||
.setHorizontalPadding(128));
|
||||
BoundingBox references = NativeStructureReferenceEnvelope.referenceBounds(
|
||||
generated, generated.getStructure(), terrain, "test:clipped_terrain");
|
||||
StructureStart published = NativeStructureReferenceEnvelope.wrapForPublication(
|
||||
generated, generated.getStructure(), 0, terrain, "test:clipped_terrain");
|
||||
|
||||
assertEquals(-128, wrapped.getBoundingBox().minX());
|
||||
assertEquals(-128, wrapped.getBoundingBox().minZ());
|
||||
assertEquals(143, wrapped.getBoundingBox().maxX());
|
||||
assertEquals(143, wrapped.getBoundingBox().maxZ());
|
||||
assertEquals(Math.max(content.minX() - 124, (origin.x() - 8) << 4), references.minX());
|
||||
assertEquals(Math.min(content.maxX() + 124, ((origin.x() + 8) << 4) + 15), references.maxX());
|
||||
assertEquals(Math.max(content.minZ() - 124, (origin.z() - 8) << 4), references.minZ());
|
||||
assertEquals(Math.min(content.maxZ() + 124, ((origin.z() + 8) << 4) + 15), references.maxZ());
|
||||
assertTrue(references.isInside(content.getCenter()));
|
||||
assertTrue(published.isValid());
|
||||
assertEquals(1, generated.getPieces().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -726,6 +1008,16 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
||||
return new NativeStructureSurfaceFitter.SurfaceAnchor(0, 4, 0, 4, meetY, strength);
|
||||
}
|
||||
|
||||
private static PoolElementStructurePiece rigidPiece(BoundingBox bounds, int groundLevelDelta) {
|
||||
StructurePoolElement element = StructurePoolElement.single(
|
||||
"minecraft:empty").apply(StructureTemplatePool.Projection.RIGID);
|
||||
return new PoolElementStructurePiece(
|
||||
null, element,
|
||||
new BlockPos(bounds.minX(), bounds.minY(), bounds.minZ()),
|
||||
groundLevelDelta, Rotation.NONE, bounds,
|
||||
LiquidSettings.APPLY_WATERLOGGING);
|
||||
}
|
||||
|
||||
private static NativeStructureTerrainIntegrator.OrganicCarve organicCarve(
|
||||
StructureStart start, int horizontalPadding) {
|
||||
return organicCarve(start, horizontalPadding, 0.85D);
|
||||
@@ -831,8 +1123,14 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
||||
}
|
||||
|
||||
private static StructureStart desertStart() {
|
||||
return desertStart(TerrainAdjustment.NONE);
|
||||
}
|
||||
|
||||
private static StructureStart desertStart(TerrainAdjustment adjustment) {
|
||||
Structure structure = new DesertPyramidStructure(
|
||||
new Structure.StructureSettings(HolderSet.empty()));
|
||||
new Structure.StructureSettings(
|
||||
HolderSet.empty(), Map.of(),
|
||||
GenerationStep.Decoration.SURFACE_STRUCTURES, adjustment));
|
||||
DesertPyramidPiece piece = new DesertPyramidPiece(RandomSource.create(7L), 0, 0);
|
||||
return new StructureStart(
|
||||
structure, new ChunkPos(0, 0), 0, new PiecesContainer(List.of(piece)));
|
||||
@@ -854,6 +1152,9 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
||||
if (methodName.equals("getSeed")) {
|
||||
return TEST_SEED;
|
||||
}
|
||||
if (methodName.equals("getLevel")) {
|
||||
return null;
|
||||
}
|
||||
if (methodName.equals("hashCode")) {
|
||||
return System.identityHashCode(proxy);
|
||||
}
|
||||
@@ -869,6 +1170,62 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
|
||||
WorldGenLevel.class.getClassLoader(), new Class<?>[]{WorldGenLevel.class}, handler);
|
||||
}
|
||||
|
||||
private static WorldGenLevel world(ChunkAccess chunk) {
|
||||
InvocationHandler handler = (proxy, method, arguments) -> {
|
||||
String methodName = method.getName();
|
||||
if (methodName.equals("getBlockState")) {
|
||||
return chunk.getBlockState((BlockPos) arguments[0]);
|
||||
}
|
||||
if (methodName.equals("setBlock")) {
|
||||
return chunk.setBlockState(
|
||||
((BlockPos) arguments[0]).immutable(),
|
||||
(BlockState) arguments[1], (int) arguments[2]) != null;
|
||||
}
|
||||
if (methodName.equals("getSeed")) {
|
||||
return TEST_SEED;
|
||||
}
|
||||
if (methodName.equals("getLevel")) {
|
||||
return null;
|
||||
}
|
||||
if (methodName.equals("hashCode")) {
|
||||
return System.identityHashCode(proxy);
|
||||
}
|
||||
if (methodName.equals("equals")) {
|
||||
return proxy == arguments[0];
|
||||
}
|
||||
if (methodName.equals("toString")) {
|
||||
return "surface-test-chunk-world";
|
||||
}
|
||||
throw new UnsupportedOperationException(method.toString());
|
||||
};
|
||||
return (WorldGenLevel) Proxy.newProxyInstance(
|
||||
WorldGenLevel.class.getClassLoader(), new Class<?>[]{WorldGenLevel.class}, handler);
|
||||
}
|
||||
|
||||
private static void write(ProtoChunk chunk, int x, int y, int z, BlockState state) {
|
||||
LevelChunkSection section = chunk.getSection(chunk.getSectionIndex(y));
|
||||
section.setBlockState(x, y & 15, z, state, false);
|
||||
}
|
||||
|
||||
private static PalettedContainerFactory containerFactory() {
|
||||
Strategy<BlockState> blockStates = Strategy.createForBlockStates(Block.BLOCK_STATE_REGISTRY);
|
||||
BlockState air = Blocks.AIR.defaultBlockState();
|
||||
IdMapper<Holder<Biome>> biomeIds = new IdMapper<>();
|
||||
Holder<Biome> defaultBiome = Holder.direct((Biome) null);
|
||||
biomeIds.add(defaultBiome);
|
||||
Strategy<Holder<Biome>> biomes = Strategy.createForBiomes(biomeIds);
|
||||
Codec<Holder<Biome>> biomeCodec = Codec.STRING.xmap(
|
||||
name -> defaultBiome, holder -> "iris-test-biome");
|
||||
return new PalettedContainerFactory(
|
||||
blockStates,
|
||||
air,
|
||||
PalettedContainer.codecRW(BlockState.CODEC, blockStates, air),
|
||||
biomes,
|
||||
defaultBiome,
|
||||
PalettedContainer.codecRO(biomeCodec, biomes, defaultBiome),
|
||||
PalettedContainer.codecRW(biomeCodec, biomes, defaultBiome));
|
||||
}
|
||||
|
||||
private static void put(Map<BlockPos, BlockState> blocks,
|
||||
int x, int y, int z, BlockState state) {
|
||||
blocks.put(new BlockPos(x, y, z), state);
|
||||
|
||||
+35
@@ -1,14 +1,33 @@
|
||||
package art.arcane.iris.nativegen;
|
||||
|
||||
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.BoundingBox;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
||||
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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NativeStructurePostProcessorVegetationTest {
|
||||
@BeforeClass
|
||||
public static void bootstrapMinecraftRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void surfaceStructuresClearTreeColumnsAutomatically() {
|
||||
assertTrue(NativeStructureVegetationClearer.shouldClearVegetationColumn(100, 100, false));
|
||||
@@ -26,6 +45,22 @@ public class NativeStructurePostProcessorVegetationTest {
|
||||
assertTrue(NativeStructureVegetationClearer.shouldClearVegetationColumn(20, 100, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonForcedValidTargetsReachIntersectionProcessing() {
|
||||
Structure structure = new DesertPyramidStructure(
|
||||
new Structure.StructureSettings(HolderSet.empty()));
|
||||
StructureStart start = new StructureStart(
|
||||
structure, new ChunkPos(0, 0), 0,
|
||||
new PiecesContainer(List.of(
|
||||
new DesertPyramidPiece(RandomSource.create(1L), 0, 0))));
|
||||
|
||||
assertTrue(NativeStructureVegetationClearer.shouldProcessTarget(
|
||||
new NativeStructureVegetationClearer.VegetationTarget(start, false)));
|
||||
assertFalse(NativeStructureVegetationClearer.shouldProcessTarget(
|
||||
new NativeStructureVegetationClearer.VegetationTarget(
|
||||
StructureStart.INVALID_START, true)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void surfaceStructuresPreserveVegetationUnlessConfigured() {
|
||||
assertFalse(NativeStructureVegetationClearer.shouldClearEntireVegetationFootprint(
|
||||
|
||||
Reference in New Issue
Block a user