This commit is contained in:
Brian Neumann-Fopiano
2026-07-30 02:36:15 -04:00
parent 7ad4909518
commit 0afbfcb7a2
110 changed files with 7157 additions and 846 deletions
@@ -0,0 +1,3 @@
[23:25:34] [Test worker/INFO]: [STDERR]: [Iris/WARN] Native structure terrain envelope at 0,0 was clipped to Minecraft's 8-chunk structure reference range
[23:25:34] [Test worker/INFO]: [STDERR]: [Iris/WARN] Native structure burial at 0,0 clamped to world floor: wanted -19, used -4
[23:25:34] [Test worker/INFO]: [STDERR]: [Iris/WARN] Native structure burial at 0,0 clamped to world floor: wanted -5, used -2
@@ -4,12 +4,15 @@ 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.NativeStructureStartPlan;
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisMaterialPalette;
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
import art.arcane.iris.engine.object.IrisStructureStiltSettings;
import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
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.NativeStructurePostProcessor;
import art.arcane.iris.spi.PlatformBlockState;
@@ -86,6 +89,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.IntBinaryOperator;
public class IrisChunkGenerator extends CustomChunkGenerator {
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
@@ -146,9 +150,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
throw new IllegalStateException("Native structure locate received an unregistered structure holder");
}
String structureId = id.toString();
IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(engine,
structureId, NativeStructurePostProcessor.isUndergroundStep(holder.value().step()));
if (decision.status() != NativeStructureGenerationStatus.REPLACED_BY_IRIS) {
if (!IrisStructureLocator.isPlaced(engine, structureId)) {
continue;
}
IrisStructureLocator.LocateResult result = IrisStructureLocator.locate(
@@ -250,11 +252,27 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
Map<Structure, StructureStart> previousStarts = new HashMap<>(access.getAllStarts());
super.createStructures(registryAccess, structureState, structureManager, access, templateManager, levelKey);
adjustGeneratedStructures(registryAccess, access, previousStarts);
Map<Structure, NativeStructureStartPlan> configuredStarts = NativeStructureStartInjector.inject(
new NativeStructureStartInjector.InjectionContext(
engine,
registryAccess,
structureState,
structureManager,
access,
templateManager,
levelKey,
this,
customBiomeSource
));
adjustGeneratedStructures(
registryAccess, access, previousStarts, configuredStarts, templateManager);
}
}
private void adjustGeneratedStructures(RegistryAccess registryAccess, ChunkAccess access, Map<Structure, StructureStart> previousStarts) {
private void adjustGeneratedStructures(RegistryAccess registryAccess, ChunkAccess access,
Map<Structure, StructureStart> previousStarts,
Map<Structure, NativeStructureStartPlan> configuredStarts,
StructureTemplateManager templateManager) {
Registry<Structure> registry = registryAccess.lookupOrThrow(Registries.STRUCTURE);
ChunkPos chunkPos = access.getPos();
for (Map.Entry<Structure, StructureStart> entry : access.getAllStarts().entrySet()) {
@@ -263,6 +281,9 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
if (!start.isValid() || previousStarts.get(structure) == start) {
continue;
}
if (configuredStarts.containsKey(structure)) {
continue;
}
Identifier id = registry.getKey(structure);
String structureId = id == null ? null : id.toString();
if (structureId == null) {
@@ -291,7 +312,13 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
access.getMinY(),
access.getMinY() + access.getHeight(),
undergroundStep,
decision.preserveSourceY(),
decision.yBand(),
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
start, structure, start.getReferences(), templateManager,
NativeStructurePostProcessor.resolveNativeTerrain(start, decision.terrain()));
access.setStartForStructure(structure, wrapped);
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"vertical adjustment", structureId, chunkPos.x(), chunkPos.z(), error);
@@ -330,7 +357,29 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public CompletableFuture<ChunkAccess> fillFromNoise(Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) {
return delegate.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess);
return delegate.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess)
.thenApply(filled -> {
try (GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_nms_worldgen_heightmaps");
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
primeWorldgenHeightmaps(filled);
return filled;
} catch (GenerationSessionException e) {
throw new IllegalStateException(
"Iris worldgen heightmap priming could not acquire its engine runtime.", e);
}
});
}
private void primeWorldgenHeightmaps(ChunkAccess chunkAccess) {
WorldgenTerrainHeightmaps.primeTerrain(chunkAccess, worldgenSurfaceHeight(), worldgenFloorHeight());
}
private IntBinaryOperator worldgenSurfaceHeight() {
return (x, z) -> engine.getHeight(x, z, false) + runtimeMinY + 1;
}
private IntBinaryOperator worldgenFloorHeight() {
return (x, z) -> engine.getHeight(x, z, true) + runtimeMinY + 1;
}
@Override
@@ -397,8 +446,10 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
BoundingBox area = writableArea(chunk);
int steps = GenerationStep.Decoration.values().length;
List<NativePlacementGroup> placementGroups = new ArrayList<>();
List<StructureStart> heightmapStarts = new ArrayList<>();
List<StructureStart> nativeStarts = new ArrayList<>();
List<NativeStructurePostProcessor.VegetationTarget> vegetationTargets = new ArrayList<>();
List<NativeStructurePostProcessor.TerrainTarget> terrainTargets = new ArrayList<>();
for (int step = 0; step < steps; step++) {
int index = 0;
for (Structure structure : byStep.get(step)) {
@@ -409,23 +460,36 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
"resolution", null, chunkPos.x(), chunkPos.z());
}
try {
IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(engine,
IrisNativeStructureDecision sourceDecision = NativeStructureGenerationPolicy.resolve(engine,
structureId, NativeStructurePostProcessor.isUndergroundStep(structure.step()));
if (decision.generate()) {
List<StructureStart> starts = structureManager.startsForStructure(sectionPos, structure);
if (!starts.isEmpty()) {
List<StructureStart> resolvedStarts = List.copyOf(starts);
placementGroups.add(new NativePlacementGroup(
structureId, decision, index, step, resolvedStarts));
nativeStarts.addAll(resolvedStarts);
boolean clearEntireFootprint = NativeStructurePostProcessor
.shouldClearEntireVegetationFootprint(
structure.step(), decision.clearVegetation());
for (StructureStart start : resolvedStarts) {
vegetationTargets.add(new NativeStructurePostProcessor.VegetationTarget(
start, clearEntireFootprint));
}
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);
if (!decision.generate()) {
continue;
}
resolvedPlacements.add(new NativePlacement(start, decision));
heightmapStarts.add(start);
terrainTargets.add(new NativeStructurePostProcessor.TerrainTarget(
structureId, start,
NativeStructurePostProcessor.resolveNativeTerrain(
start, decision.terrain())));
if (plan == null || !plan.placement().isUnderground()) {
nativeStarts.add(start);
}
boolean clearEntireFootprint = NativeStructurePostProcessor
.shouldClearEntireVegetationFootprint(
structure.step(), decision.clearVegetation());
vegetationTargets.add(new NativeStructurePostProcessor.VegetationTarget(
start, clearEntireFootprint));
}
if (!resolvedPlacements.isEmpty()) {
placementGroups.add(new NativePlacementGroup(
structureId, index, step, List.copyOf(resolvedPlacements)));
}
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
@@ -434,6 +498,14 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
index++;
}
}
try {
WorldgenTerrainHeightmaps.primeStructurePlacement(
world, heightmapStarts, worldgenSurfaceHeight(), worldgenFloorHeight());
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"heightmap priming", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
try {
NativeStructurePostProcessor.prepareSurfaceStructures(
world, area, nativeStarts,
@@ -451,12 +523,20 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
"vegetation cleanup", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
try {
NativeStructurePostProcessor.prepareTerrain(
world, area, terrainTargets, this::resolvePaletteBlock);
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"terrain carving", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
for (NativePlacementGroup group : placementGroups) {
random.setFeatureSeed(decoSeed, group.featureIndex(), group.step());
try {
for (StructureStart start : group.starts()) {
for (NativePlacement placement : group.placements()) {
placeVanillaStructure(world, structureManager, random, area, chunkPos,
group.structureId(), start, group.decision());
group.structureId(), placement.start(), placement.decision());
}
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
@@ -483,7 +563,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
BoundingBox area, ChunkPos chunkPos, String structureId, StructureStart start,
IrisNativeStructureDecision decision) {
NativeStructurePostProcessor.place(world, structureManager, this, random, area, chunkPos,
structureId, start, decision, this::resolveStiltBlock,
structureId, start, decision, this::resolvePaletteBlock,
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
}
@@ -514,13 +594,10 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
}
private BlockState resolveStiltBlock(IrisStructureStiltSettings settings, RNG rng, int x, int y, int z) {
if (settings.getPalette() == null) {
return Blocks.COBBLESTONE.defaultBlockState();
}
PlatformBlockState platformState = settings.getPalette().get(rng, x, y, z, engine.getData());
private BlockState resolvePaletteBlock(IrisMaterialPalette palette, RNG rng, int x, int y, int z) {
PlatformBlockState platformState = palette.get(rng, x, y, z, engine.getData());
if (platformState == null || !(platformState.nativeHandle() instanceof BlockData blockData)) {
throw new IllegalStateException("Configured native structure stilt palette did not resolve a Bukkit block at "
throw new IllegalStateException("Configured native structure palette did not resolve a Bukkit block at "
+ x + "," + y + "," + z);
}
if (blockData instanceof IrisCustomData customData) {
@@ -529,7 +606,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
if (blockData instanceof CraftBlockData craftBlockData) {
return craftBlockData.getState();
}
throw new IllegalStateException("Configured native structure stilt palette resolved unsupported Bukkit block data "
throw new IllegalStateException("Configured native structure palette resolved unsupported Bukkit block data "
+ blockData.getClass().getName() + " at " + x + "," + y + "," + z);
}
@@ -549,8 +626,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSectionY());
BlockPos blockPos = sectionPos.origin();
Heightmap surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG);
Heightmap ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG);
primeWorldgenHeightmaps(chunkAccess);
Heightmap motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING);
Heightmap motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES);
int minHeight = engine.getMinHeight();
@@ -561,9 +638,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
int wZ = z + blockPos.getZ();
int terrainTop = engine.getHeight(wX, wZ, false) + minHeight + 1;
int terrainNoFluid = engine.getHeight(wX, wZ, true) + minHeight + 1;
SET_HEIGHT.invoke(ocean, x, z, terrainNoFluid);
SET_HEIGHT.invoke(surface, x, z, terrainTop);
SET_HEIGHT.invoke(motion, x, z, terrainTop);
SET_HEIGHT.invoke(motionNoLeaves, x, z, terrainTop);
}
@@ -714,8 +788,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
private record StructureStepCache(Registry<Structure> registry, List<List<Structure>> structures) {
}
private record NativePlacementGroup(String structureId, IrisNativeStructureDecision decision,
int featureIndex, int step, List<StructureStart> starts) {
private record NativePlacement(StructureStart start, IrisNativeStructureDecision decision) {
}
private record NativePlacementGroup(String structureId, int featureIndex, int step,
List<NativePlacement> placements) {
}
private record NativeLocateCandidate(Holder<Structure> holder, String key) {
@@ -102,6 +102,8 @@ 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.Structure;
import net.minecraft.world.level.levelgen.structure.structures.JigsawStructure;
import net.minecraft.world.level.levelgen.feature.AbstractHugeMushroomFeature;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraft.world.level.levelgen.feature.FallenTreeFeature;
@@ -482,6 +484,36 @@ public class NMSBinding implements INMSBinding {
return keys;
}
@Override
public KList<String> getJigsawStructureKeys() {
KList<String> keys = new KList<>();
try {
Registry<Structure> structures = registry().lookupOrThrow(Registries.STRUCTURE);
for (Map.Entry<ResourceKey<Structure>, Structure> entry : structures.entrySet()) {
if (entry.getValue() instanceof JigsawStructure) {
keys.add(entry.getKey().identifier().toString());
}
}
} catch (RuntimeException e) {
throw new IllegalStateException(
"Iris failed to read registered jigsaw structure keys from the Minecraft registry", e);
}
return keys;
}
@Override
public KList<String> getTemplatePoolKeys() {
KList<String> keys = new KList<>();
try {
registry().lookupOrThrow(Registries.TEMPLATE_POOL).keySet()
.forEach(key -> keys.add(key.toString()));
} catch (RuntimeException e) {
throw new IllegalStateException(
"Iris failed to read registered template pool keys from the Minecraft registry", e);
}
return keys;
}
@Override
public KList<String> getStructureSetKeys() {
KList<String> keys = new KList<>();
@@ -0,0 +1,106 @@
package art.arcane.iris.core.nms.v26_2_R1;
import net.minecraft.core.SectionPos;
import net.minecraft.util.Mth;
import net.minecraft.util.SimpleBitStorage;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.IntBinaryOperator;
/**
* Writes WORLD_SURFACE_WG and OCEAN_FLOOR_WG from Iris engine heights.
*
* Iris writes its terrain straight into LevelChunkSection, so neither vanilla's noise fill nor
* ProtoChunk.setBlockState ever updates the two worldgen heightmaps. Without these writes
* ChunkAccess.getHeight lazily primes the map from finished blocks - Iris trees, leaves and snow
* included - and vanilla pieces that self-snap against WORLD_SURFACE_WG (igloo) or OCEAN_FLOOR_WG
* (ocean ruins, buried treasure, mineshaft interiors) anchor to the canopy instead of the terrain.
*
* Both resolvers take world block coordinates and return the absolute first free Y, matching
* Heightmap's internal "first available" semantics: WORLD_SURFACE_WG counts fluid, OCEAN_FLOOR_WG
* does not.
*/
final class WorldgenTerrainHeightmaps {
private static final int COLUMNS = 256;
private static final int PLACEMENT_CHUNK_MARGIN = 1;
private WorldgenTerrainHeightmaps() {
}
static void primeTerrain(ChunkAccess chunk, IntBinaryOperator surfaceFirstFreeY,
IntBinaryOperator floorFirstFreeY) {
Objects.requireNonNull(chunk, "Iris worldgen heightmap priming requires a chunk");
Objects.requireNonNull(surfaceFirstFreeY,
"Iris worldgen heightmap priming requires a surface height resolver");
Objects.requireNonNull(floorFirstFreeY,
"Iris worldgen heightmap priming requires an ocean floor height resolver");
write(chunk, Heightmap.Types.WORLD_SURFACE_WG, surfaceFirstFreeY);
write(chunk, Heightmap.Types.OCEAN_FLOOR_WG, floorFirstFreeY);
}
static void primeStructurePlacement(WorldGenLevel world, List<StructureStart> starts,
IntBinaryOperator surfaceFirstFreeY,
IntBinaryOperator floorFirstFreeY) {
Objects.requireNonNull(world,
"Iris worldgen heightmap priming requires a generation level");
if (starts == null || starts.isEmpty()) {
return;
}
Set<Long> primed = new HashSet<>();
for (StructureStart start : starts) {
if (start == null || !start.isValid()) {
continue;
}
BoundingBox bounds = start.getBoundingBox();
int minChunkX = SectionPos.blockToSectionCoord(bounds.minX()) - PLACEMENT_CHUNK_MARGIN;
int maxChunkX = SectionPos.blockToSectionCoord(bounds.maxX()) + PLACEMENT_CHUNK_MARGIN;
int minChunkZ = SectionPos.blockToSectionCoord(bounds.minZ()) - PLACEMENT_CHUNK_MARGIN;
int maxChunkZ = SectionPos.blockToSectionCoord(bounds.maxZ()) + PLACEMENT_CHUNK_MARGIN;
for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) {
for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) {
if (!primed.add(ChunkPos.pack(chunkX, chunkZ))) {
continue;
}
if (!world.hasChunk(chunkX, chunkZ)) {
continue;
}
primeTerrain(world.getChunk(chunkX, chunkZ), surfaceFirstFreeY, floorFirstFreeY);
}
}
}
}
private static void write(ChunkAccess chunk, Heightmap.Types type, IntBinaryOperator firstFreeY) {
ChunkPos pos = chunk.getPos();
int minY = chunk.getMinY();
int height = chunk.getHeight();
int baseX = pos.getMinBlockX();
int baseZ = pos.getMinBlockZ();
SimpleBitStorage storage = new SimpleBitStorage(Mth.ceillog2(height + 1), COLUMNS);
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
int firstFree = firstFreeY.applyAsInt(baseX + x, baseZ + z) - minY;
storage.set(x + z * 16, Mth.clamp(firstFree, 0, height));
}
}
Heightmap heightmap = chunk.getOrCreateHeightmapUnprimed(type);
long[] packed = storage.getRaw();
int expected = heightmap.getRawData().length;
if (expected != packed.length) {
throw new IllegalStateException("Iris cannot prime " + type + " for chunk "
+ pos.x() + "," + pos.z() + ": packed " + packed.length
+ " words for a heightmap storing " + expected
+ "; Heightmap.setRawData would silently fall back to block scanning");
}
heightmap.setRawData(chunk, type, packed);
}
}
@@ -6,6 +6,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -65,6 +66,41 @@ public class IrisChunkGeneratorFailureContractTest {
assertTrue(source.contains("catch (GenerationSessionException e)"));
}
@Test
public void terrainWritesPrimeTheWorldgenHeightmapsForEveryChunk() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
int fillStart = source.indexOf("public CompletableFuture<ChunkAccess> fillFromNoise");
int fillEnd = source.indexOf("public WeightedList<MobSpawnSettings.SpawnerData> getMobsAt", fillStart);
String fill = source.substring(fillStart, fillEnd);
int decorationStart = source.indexOf("public void addVanillaDecorations");
int decorationEnd = source.indexOf("public void spawnOriginalMobs", decorationStart);
String decorations = source.substring(decorationStart, decorationEnd);
int placementStart = source.indexOf("private void placeVanillaStructures");
int placementEnd = source.indexOf("private static String nativeStructureBatchContext", placementStart);
String placement = source.substring(placementStart, placementEnd);
assertTrue(fill.contains("thenApply"));
assertTrue(fill.contains("primeWorldgenHeightmaps"));
assertTrue(decorations.contains("primeWorldgenHeightmaps"));
assertFalse(decorations.contains("Heightmap.Types.WORLD_SURFACE_WG"));
assertFalse(decorations.contains("Heightmap.Types.OCEAN_FLOOR_WG"));
assertEquals(1, occurrences(source, "WorldgenTerrainHeightmaps.primeTerrain("));
assertTrue(placement.contains("WorldgenTerrainHeightmaps.primeStructurePlacement("));
assertTrue(placement.indexOf("WorldgenTerrainHeightmaps.primeStructurePlacement(")
< placement.indexOf("prepareSurfaceStructures"));
assertTrue(source.contains("engine.acquireGenerationLease(\"bukkit_nms_worldgen_heightmaps\")"));
}
private static int occurrences(String source, String needle) {
int count = 0;
int index = source.indexOf(needle);
while (index >= 0) {
count++;
index = source.indexOf(needle, index + needle.length());
}
return count;
}
@Test
public void vanillaChunkGenerationMobsUseTheVisibleBiomesVanillaDerivative() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
@@ -27,12 +27,11 @@ public class IrisChunkGeneratorMonumentLocateContractTest {
assertTrue(filterStart > irisHelperStart);
String outerMethod = source.substring(findStart, irisHelperStart);
String irisHelper = source.substring(irisHelperStart, filterStart);
int policyResolution = irisHelper.indexOf("NativeStructureGenerationPolicy.resolve(engine,");
int unexploredGuard = irisHelper.indexOf("if (findUnexplored)");
int registryLookup = irisHelper.indexOf("level.registryAccess().lookupOrThrow(Registries.STRUCTURE)");
int replacementCheck = irisHelper.indexOf(
"decision.status() != NativeStructureGenerationStatus.REPLACED_BY_IRIS");
int irisLocate = irisHelper.indexOf("IrisStructureLocator.locate(", replacementCheck);
int placedCheck = irisHelper.indexOf(
"if (!IrisStructureLocator.isPlaced(engine, structureId))");
int irisLocate = irisHelper.indexOf("IrisStructureLocator.locate(", placedCheck);
int searchLimit = irisHelper.indexOf("LocateStatus.SEARCH_LIMIT_REACHED", irisLocate);
int limitSkip = irisHelper.indexOf("continue;", searchLimit);
int nativeFilter = outerMethod.indexOf("filterReachableStructures(level, holders)");
@@ -47,12 +46,10 @@ public class IrisChunkGeneratorMonumentLocateContractTest {
int emptyNativePartition = filterMethod.indexOf("if (candidates.isEmpty())", filterContinue);
int reachabilityLookup = filterMethod.indexOf("reachableStructureKeys(level)", emptyNativePartition);
assertTrue(policyResolution >= 0);
assertTrue(unexploredGuard >= 0);
assertTrue(registryLookup > unexploredGuard);
assertTrue(policyResolution > registryLookup);
assertTrue(replacementCheck > policyResolution);
assertTrue(irisLocate > replacementCheck);
assertTrue(placedCheck > registryLookup);
assertTrue(irisLocate > placedCheck);
assertTrue(searchLimit > irisLocate);
assertTrue(limitSkip > searchLimit);
assertTrue(nativeFilter >= 0);
@@ -86,7 +83,7 @@ public class IrisChunkGeneratorMonumentLocateContractTest {
int placement = source.indexOf("start.placeInChunk(world, structureManager, generator");
int stiltPlacement = source.indexOf("placeStilts(world, area, structureId, start", placement);
int occupancyCheck = source.indexOf("if (state.isSolid())", stiltPlacement);
int terrainFloor = source.indexOf("y > terrainY", stiltPlacement);
int terrainFloor = source.indexOf("Math.max(terrainY,", stiltPlacement);
assertTrue(placement >= 0);
assertTrue(stiltPlacement > placement);
@@ -0,0 +1,303 @@
package art.arcane.iris.core.nms.v26_2_R1;
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.Blocks;
import net.minecraft.world.level.block.Block;
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.chunk.status.ChunkStatus;
import net.minecraft.world.level.levelgen.Heightmap;
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.IglooPieces;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutPiece;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutStructure;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.IntBinaryOperator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class WorldgenTerrainHeightmapsTest {
private static final int MIN_Y = -64;
private static final int HEIGHT = 384;
private static final int LAND_TERRAIN_TOP = 70;
private static final int LAND_CANOPY_TOP = 80;
private static final int OCEAN_FLOOR_TOP = 60;
private static final int OCEAN_FLUID_TOP = 64;
private static final int LAND_X = 3;
private static final int LAND_Z = 5;
private static final int OCEAN_X = 8;
private static final int OCEAN_Z = 8;
private static final int TERRAIN_SLAB_DEPTH = 4;
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void primeTerrainWritesEngineSurfaceAndOceanFloorHeights() {
ProtoChunk chunk = terrainChunk(new ChunkPos(0, 0));
assertFalse(chunk.hasPrimedHeightmap(Heightmap.Types.WORLD_SURFACE_WG));
assertFalse(chunk.hasPrimedHeightmap(Heightmap.Types.OCEAN_FLOOR_WG));
WorldgenTerrainHeightmaps.primeTerrain(chunk, surfaceFirstFreeY(), floorFirstFreeY());
assertTrue(chunk.hasPrimedHeightmap(Heightmap.Types.WORLD_SURFACE_WG));
assertTrue(chunk.hasPrimedHeightmap(Heightmap.Types.OCEAN_FLOOR_WG));
assertEquals(LAND_TERRAIN_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
assertEquals(LAND_TERRAIN_TOP, chunk.getHeight(
Heightmap.Types.OCEAN_FLOOR_WG, LAND_X, LAND_Z));
assertEquals(OCEAN_FLUID_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, OCEAN_X, OCEAN_Z));
assertEquals(OCEAN_FLOOR_TOP, chunk.getHeight(
Heightmap.Types.OCEAN_FLOOR_WG, OCEAN_X, OCEAN_Z));
}
@Test
public void finalHeightmapPrimingDoesNotClobberTheWorldgenHeightmaps() {
ProtoChunk chunk = terrainChunk(new ChunkPos(0, 0));
WorldgenTerrainHeightmaps.primeTerrain(chunk, surfaceFirstFreeY(), floorFirstFreeY());
Heightmap.primeHeightmaps(chunk, ChunkStatus.FINAL_HEIGHTMAPS);
assertEquals(LAND_TERRAIN_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
assertEquals(LAND_TERRAIN_TOP, chunk.getHeight(
Heightmap.Types.OCEAN_FLOOR_WG, LAND_X, LAND_Z));
assertEquals(LAND_CANOPY_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE, LAND_X, LAND_Z));
}
@Test
public void unprimedWorldgenHeightmapResolvesTheIrisCanopyInstead() {
ProtoChunk chunk = terrainChunk(new ChunkPos(0, 0));
assertEquals(LAND_CANOPY_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
}
@Test
public void primeTerrainOverwritesAnAlreadyLazyPrimedCanopyHeight() {
ProtoChunk chunk = terrainChunk(new ChunkPos(0, 0));
assertEquals(LAND_CANOPY_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
WorldgenTerrainHeightmaps.primeTerrain(chunk, surfaceFirstFreeY(), floorFirstFreeY());
assertEquals(LAND_TERRAIN_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
}
@Test
public void structurePlacementPrimesTheWestNeighbourAnIglooEntranceReads() {
Map<Long, ChunkAccess> chunks = new HashMap<>();
chunks.put(ChunkPos.pack(0, 0), terrainChunk(new ChunkPos(0, 0)));
chunks.put(ChunkPos.pack(-1, 0), terrainChunk(new ChunkPos(-1, 0)));
WorldGenLevel world = world(chunks);
StructureStart start = startInOrigin();
int canopySnap = IglooPieces.GENERATION_HEIGHT + iglooSnapOffset(world);
WorldgenTerrainHeightmaps.primeStructurePlacement(
world, List.of(start), surfaceFirstFreeY(), floorFirstFreeY());
int terrainSnap = IglooPieces.GENERATION_HEIGHT + iglooSnapOffset(world);
assertEquals(LAND_TERRAIN_TOP + 1, world.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, -2, 5));
assertEquals(LAND_TERRAIN_TOP, terrainSnap);
assertEquals(LAND_CANOPY_TOP, canopySnap);
assertEquals(LAND_CANOPY_TOP - LAND_TERRAIN_TOP, canopySnap - terrainSnap);
}
@Test
public void structurePlacementPrimesEveryChunkTheStartFootprintTouches() {
Map<Long, ChunkAccess> chunks = new HashMap<>();
for (int chunkX = -1; chunkX <= 1; chunkX++) {
for (int chunkZ = -1; chunkZ <= 1; chunkZ++) {
chunks.put(ChunkPos.pack(chunkX, chunkZ), terrainChunk(new ChunkPos(chunkX, chunkZ)));
}
}
WorldGenLevel world = world(chunks);
WorldgenTerrainHeightmaps.primeStructurePlacement(
world, List.of(startInOrigin()), surfaceFirstFreeY(), floorFirstFreeY());
for (ChunkAccess chunk : chunks.values()) {
assertTrue(chunk.getPos().toString(),
chunk.hasPrimedHeightmap(Heightmap.Types.WORLD_SURFACE_WG));
assertTrue(chunk.getPos().toString(),
chunk.hasPrimedHeightmap(Heightmap.Types.OCEAN_FLOOR_WG));
assertEquals(LAND_TERRAIN_TOP, chunk.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
}
}
@Test
public void structurePlacementSkipsChunksOutsideTheGenerationRegion() {
Map<Long, ChunkAccess> chunks = new HashMap<>();
ProtoChunk origin = terrainChunk(new ChunkPos(0, 0));
chunks.put(ChunkPos.pack(0, 0), origin);
WorldGenLevel world = world(chunks);
WorldgenTerrainHeightmaps.primeStructurePlacement(
world, List.of(startInOrigin()), surfaceFirstFreeY(), floorFirstFreeY());
assertTrue(origin.hasPrimedHeightmap(Heightmap.Types.WORLD_SURFACE_WG));
assertEquals(LAND_TERRAIN_TOP, origin.getHeight(
Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
}
private static int iglooSnapOffset(WorldGenLevel world) {
return world.getHeight(Heightmap.Types.WORLD_SURFACE_WG, -2, 5)
- IglooPieces.GENERATION_HEIGHT - 1;
}
private static StructureStart startInOrigin() {
Structure structure = new SwampHutStructure(new Structure.StructureSettings(HolderSet.empty()));
SwampHutPiece piece = new SwampHutPiece(RandomSource.create(13L), 0, 0);
return new StructureStart(
structure, new ChunkPos(0, 0), 0, new PiecesContainer(List.of(piece)));
}
private static IntBinaryOperator surfaceFirstFreeY() {
return (x, z) -> isOcean(x, z) ? OCEAN_FLUID_TOP + 1 : LAND_TERRAIN_TOP + 1;
}
private static IntBinaryOperator floorFirstFreeY() {
return (x, z) -> isOcean(x, z) ? OCEAN_FLOOR_TOP + 1 : LAND_TERRAIN_TOP + 1;
}
private static boolean isOcean(int x, int z) {
return Math.floorMod(x, 16) == OCEAN_X && Math.floorMod(z, 16) == OCEAN_Z;
}
private static ProtoChunk terrainChunk(ChunkPos pos) {
ProtoChunk chunk = new ProtoChunk(
pos, UpgradeData.EMPTY, LevelHeightAccessor.create(MIN_Y, HEIGHT),
containerFactory(), null);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
boolean ocean = isOcean(pos.getMinBlockX() + x, pos.getMinBlockZ() + z);
int stoneTop = ocean ? OCEAN_FLOOR_TOP : LAND_TERRAIN_TOP;
for (int y = stoneTop - TERRAIN_SLAB_DEPTH; y <= stoneTop; y++) {
write(chunk, x, y, z, Blocks.STONE.defaultBlockState());
}
if (ocean) {
for (int y = OCEAN_FLOOR_TOP + 1; y <= OCEAN_FLUID_TOP; y++) {
write(chunk, x, y, z, Blocks.WATER.defaultBlockState());
}
continue;
}
for (int y = LAND_TERRAIN_TOP + 1; y <= LAND_TERRAIN_TOP + 6; y++) {
write(chunk, x, y, z, Blocks.SPRUCE_LOG.defaultBlockState());
}
for (int y = LAND_TERRAIN_TOP + 7; y <= LAND_CANOPY_TOP; y++) {
write(chunk, x, y, z, Blocks.SPRUCE_LEAVES.defaultBlockState());
}
}
}
return chunk;
}
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 WorldGenLevel world(Map<Long, ChunkAccess> chunks) {
InvocationHandler handler = (proxy, method, arguments) -> {
String methodName = method.getName();
if (methodName.equals("hasChunk")) {
return chunks.containsKey(ChunkPos.pack((int) arguments[0], (int) arguments[1]));
}
if (methodName.equals("getChunk") && arguments.length == 2) {
ChunkAccess chunk = chunks.get(ChunkPos.pack((int) arguments[0], (int) arguments[1]));
if (chunk == null) {
throw new AssertionError("Heightmap priming requested an unavailable chunk "
+ arguments[0] + "," + arguments[1]);
}
return chunk;
}
if (methodName.equals("getHeight") && arguments.length == 3) {
Heightmap.Types type = (Heightmap.Types) arguments[0];
int x = (int) arguments[1];
int z = (int) arguments[2];
ChunkAccess chunk = chunks.get(ChunkPos.pack(x >> 4, z >> 4));
if (chunk == null) {
throw new AssertionError("Heightmap read requested an unavailable chunk at "
+ x + "," + z);
}
return chunk.getHeight(type, x & 15, z & 15) + 1;
}
if (methodName.equals("getBlockState")) {
BlockPos position = (BlockPos) arguments[0];
ChunkAccess chunk = chunks.get(ChunkPos.pack(position.getX() >> 4, position.getZ() >> 4));
if (chunk == null) {
throw new AssertionError("Block read requested an unavailable chunk at " + position);
}
return chunk.getBlockState(position);
}
if (methodName.equals("hashCode")) {
return System.identityHashCode(proxy);
}
if (methodName.equals("equals")) {
return proxy == arguments[0];
}
if (methodName.equals("toString")) {
return "worldgen-heightmap-test-world";
}
throw new UnsupportedOperationException(method.toString());
};
return (WorldGenLevel) Proxy.newProxyInstance(
WorldGenLevel.class.getClassLoader(), new Class<?>[]{WorldGenLevel.class}, handler);
}
}
@@ -0,0 +1,174 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.engine.object.IrisJigsawConfiguration;
import com.mojang.serialization.MapCodec;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.util.RandomSource;
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.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.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.Heightmap;
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.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutPiece;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutStructure;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
public class NativeStructureFactoryTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void genericSourceDoesNotRequireJigsawType() {
Structure source = new SwampHutStructure(
new Structure.StructureSettings(HolderSet.empty()));
Structure configured = NativeStructureFactory.configure(
null, source, null, false, 80);
assertSame(source, configured);
}
@Test
public void jigsawOptionsFailClosedForNonJigsawSource() {
Structure source = new SwampHutStructure(
new Structure.StructureSettings(HolderSet.empty()));
assertThrows(IllegalStateException.class, () -> NativeStructureFactory.configure(
null, source, new IrisJigsawConfiguration(), false, 80));
}
@Test
public void forcedGeneratorSuppliesDeterministicPermissiveEnvironment() {
ChunkGenerator delegate = new TestChunkGenerator();
Holder<Biome> biome = Holder.direct((Biome) null);
LevelHeightAccessor heightAccessor = LevelHeightAccessor.create(-64, 384);
ForcedStructureChunkGenerator generator = new ForcedStructureChunkGenerator(
delegate, biome, -20);
assertEquals(-63, generator.getSeaLevel());
assertEquals(81, 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());
}
@Test
public void undergroundRelocationMovesGenericPiecesAndReturnsFreshStart() {
Structure source = new SwampHutStructure(
new Structure.StructureSettings(HolderSet.empty()));
SwampHutPiece piece = new SwampHutPiece(RandomSource.create(17L), 0, 0);
StructureStart start = new StructureStart(
source,
new ChunkPos(0, 0),
0,
new PiecesContainer(List.of(piece))
);
StructureStart relocated = NativeStructurePostProcessor.relocateToMinY(
start, source, -20, LevelHeightAccessor.create(-64, 384));
assertNotSame(start, relocated);
assertEquals(-20, relocated.getPieces().getFirst().getBoundingBox().minY());
assertEquals(-20, relocated.getBoundingBox().minY());
}
private static final class TestChunkGenerator extends ChunkGenerator {
private TestChunkGenerator() {
super(new FixedBiomeSource(Holder.direct((Biome) null)));
}
@Override
protected MapCodec<? extends ChunkGenerator> codec() {
return MapCodec.unit(this);
}
@Override
public void applyCarvers(WorldGenRegion region, long seed, RandomState randomState,
BiomeManager biomeManager, StructureManager structureManager,
ChunkAccess chunk) {
}
@Override
public void buildSurface(WorldGenRegion level, StructureManager structureManager,
RandomState randomState, ChunkAccess protoChunk) {
}
@Override
public void spawnOriginalMobs(WorldGenRegion worldGenRegion) {
}
@Override
public int getGenDepth() {
return 384;
}
@Override
public CompletableFuture<ChunkAccess> fillFromNoise(
Blender blender, RandomState randomState,
StructureManager structureManager, ChunkAccess centerChunk) {
return CompletableFuture.completedFuture(centerChunk);
}
@Override
public int getSeaLevel() {
return 63;
}
@Override
public int getMinY() {
return -64;
}
@Override
public int getBaseHeight(int x, int z, Heightmap.Types type,
LevelHeightAccessor heightAccessor, RandomState randomState) {
return 64;
}
@Override
public NoiseColumn getBaseColumn(int x, int z, LevelHeightAccessor heightAccessor,
RandomState randomState) {
BlockState[] states = new BlockState[heightAccessor.getHeight()];
for (int index = 0; index < states.length; index++) {
states[index] = Blocks.STONE.defaultBlockState();
}
return new NoiseColumn(heightAccessor.getMinY(), states);
}
@Override
public void addDebugScreenInfo(List<String> result, RandomState randomState,
BlockPos feetPos) {
}
}
}
@@ -0,0 +1,301 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.engine.object.IrisMaterialPalette;
import art.arcane.iris.engine.object.IrisStructureTerrain;
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
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.HolderSet;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
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.structures.DesertPyramidPiece;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class NativeStructurePostProcessorEncaseTest {
private static final long TEST_SEED = 1234L;
@BeforeClass
public static void bootstrapMinecraft() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void encaseFillsAirAndLiquidWithoutOverwritingExistingTerrain() {
StructureStart start = start(TerrainAdjustment.BURY, 64);
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
BoundingBox area = new BoundingBox(
bounds.minX() - 1, bounds.minY() - 1, bounds.minZ() - 1,
bounds.maxX() + 1, bounds.maxY() + 1, bounds.maxZ() + 1);
Map<BlockPos, BlockState> blocks = new HashMap<>();
put(blocks, bounds.minX(), bounds.minY(), bounds.minZ(), Blocks.DIRT.defaultBlockState());
put(blocks, bounds.maxX(), bounds.minY(), bounds.maxZ(), Blocks.WATER.defaultBlockState());
NativeStructurePostProcessor.integrateTerrain(
world(blocks), area, "minecraft:stronghold", start,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setHorizontalPadding(1)
.setCeilingPadding(1)
.setFloorPadding(1),
null);
assertEquals(Blocks.DIRT.defaultBlockState(),
state(blocks, bounds.minX(), bounds.minY(), bounds.minZ()));
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.maxX(), bounds.minY(), bounds.maxZ()));
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX() - 1, bounds.minY() - 1, bounds.minZ() - 1));
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.maxX() + 1, bounds.maxY() + 1, bounds.maxZ() + 1));
}
@Test
public void defaultEncasePaletteSplitsStoneAboveZeroAndDeepslateBelow() {
StructureStart start = start(TerrainAdjustment.BURY, -1);
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
Map<BlockPos, BlockState> blocks = new HashMap<>();
NativeStructurePostProcessor.integrateTerrain(
world(blocks), bounds, "minecraft:stronghold", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.ENCASE),
null);
assertEquals(-1, bounds.minY());
assertEquals(Blocks.DEEPSLATE.defaultBlockState(),
state(blocks, bounds.minX(), -1, bounds.minZ()));
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX(), 0, bounds.minZ()));
}
@Test
public void configuredEncasePaletteReplacesTheDefaultMaterial() {
StructureStart start = start(TerrainAdjustment.BURY, 64);
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
Map<BlockPos, BlockState> blocks = new HashMap<>();
IrisMaterialPalette palette = new IrisMaterialPalette().qclear().qadd("minecraft:tuff");
NativeStructurePostProcessor.integrateTerrain(
world(blocks), bounds, "minecraft:stronghold", start,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setEncasePalette(palette),
NativeStructurePostProcessorEncaseTest::tuffBlock);
assertEquals(Blocks.TUFF.defaultBlockState(),
state(blocks, bounds.minX(), bounds.minY(), bounds.minZ()));
}
@Test
public void encaseFillRunsBeforeNativePlacement() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
int prepareTerrain = source.indexOf("NativeStructurePostProcessor.prepareTerrain(");
int placementLoop = source.indexOf("for (NativePlacementGroup group : placementGroups)", prepareTerrain);
int placement = source.indexOf("placeVanillaStructure(world, structureManager, random", placementLoop);
assertTrue(prepareTerrain >= 0);
assertTrue(placementLoop > prepareTerrain);
assertTrue(placement > placementLoop);
}
@Test
public void buryAndEncapsulateAdaptationsAutoDefaultToEncase() {
for (TerrainAdjustment adjustment : List.of(TerrainAdjustment.BURY, TerrainAdjustment.ENCAPSULATE)) {
IrisStructureTerrain resolved = NativeStructurePostProcessor.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());
}
}
@Test
public void otherAdaptationsNeverAutoDefaultToEncase() {
for (TerrainAdjustment adjustment : List.of(
TerrainAdjustment.NONE, TerrainAdjustment.BEARD_THIN, TerrainAdjustment.BEARD_BOX)) {
assertNull(NativeStructurePostProcessor.resolveNativeTerrain(
start(adjustment, 64), null));
}
}
@Test
public void explicitTerrainConfigurationWinsOverAutoEncase() {
IrisStructureTerrain configured = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.SOURCE);
assertSame(configured, NativeStructurePostProcessor.resolveNativeTerrain(
start(TerrainAdjustment.BURY, 64), configured));
}
@Test
public void encaseReservesTheNeighborChunkEnvelope() {
StructureStart generated = start(TerrainAdjustment.BURY, 64);
BoundingBox content = generated.getBoundingBox();
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated,
generated.getStructure(),
0,
null,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setHorizontalPadding(4));
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());
}
@Test
public void yBandRelocatesTheStartMidpointDeterministically() {
IrisStructureYBand band = new IrisStructureYBand().setMin(-120).setMax(-20);
StructureStart first = start(TerrainAdjustment.BURY, 64);
StructureStart second = start(TerrainAdjustment.BURY, 64);
NativeStructurePostProcessor.applyVerticalShift(
first, -64, -256, 320, true, false, band, (x, z) -> 40);
NativeStructurePostProcessor.applyVerticalShift(
second, -64, -256, 320, true, false, band, (x, z) -> 40);
BoundingBox bounds = first.getBoundingBox();
assertEquals(bounds.minY(), second.getBoundingBox().minY());
assertTrue(bounds.minY() >= -120);
assertTrue(bounds.maxY() <= -20);
int repeated = NativeStructurePostProcessor.applyVerticalShift(
first, -64, -256, 320, true, false, band, (x, z) -> 40);
assertEquals(0, repeated);
}
@Test
public void yBandClampsWhenTheBandCannotContainTheStructure() {
IrisStructureYBand band = new IrisStructureYBand().setMin(-50).setMax(-45);
StructureStart start = start(TerrainAdjustment.BURY, 64);
NativeStructurePostProcessor.applyVerticalShift(
start, 0, -256, 320, true, false, band, (x, z) -> 40);
BoundingBox bounds = start.getBoundingBox();
int midpoint = bounds.minY() + (bounds.maxY() - bounds.minY()) / 2;
assertTrue(midpoint >= -50);
assertTrue(midpoint <= -45);
}
@Test
public void yBandReplacesBothTheBlindShiftAndBurial() {
IrisStructureYBand band = new IrisStructureYBand().setMin(-64).setMax(-64);
StructureStart start = start(TerrainAdjustment.BURY, 64);
BoundingBox bounds = start.getBoundingBox();
int height = bounds.maxY() - bounds.minY();
NativeStructurePostProcessor.applyVerticalShift(
start, -200, -256, 320, true, false, band, (x, z) -> 40);
assertEquals(-64 - height / 2, start.getBoundingBox().minY());
}
@Test
public void preserveSourceYWinsOverTheConfiguredBand() {
IrisStructureYBand band = new IrisStructureYBand().setMin(-120).setMax(-20);
StructureStart start = start(TerrainAdjustment.BURY, 64);
int minY = start.getBoundingBox().minY();
int offset = NativeStructurePostProcessor.applyVerticalShift(
start, 0, -256, 320, true, true, band, (x, z) -> 40);
assertEquals(0, offset);
assertEquals(minY, start.getBoundingBox().minY());
}
private static BlockState tuffBlock(IrisMaterialPalette palette, RNG rng, int x, int y, int z) {
assertNotNull(palette);
assertNotNull(rng);
return Blocks.TUFF.defaultBlockState();
}
private static StructureStart start(TerrainAdjustment adjustment, int minY) {
Structure structure = new DesertPyramidStructure(new Structure.StructureSettings(
HolderSet.empty(), Map.of(), GenerationStep.Decoration.STRONGHOLDS, adjustment));
StructurePiece piece = new DesertPyramidPiece(RandomSource.create(7L), 0, 0);
piece.move(0, minY - piece.getBoundingBox().minY(), 0);
return new StructureStart(
structure, new ChunkPos(0, 0), 0, new PiecesContainer(List.of(piece)));
}
private static WorldGenLevel world(Map<BlockPos, BlockState> blocks) {
InvocationHandler handler = (proxy, method, arguments) -> {
String methodName = method.getName();
if (methodName.equals("getBlockState")) {
BlockPos position = (BlockPos) arguments[0];
return state(blocks, position.getX(), position.getY(), position.getZ());
}
if (methodName.equals("setBlock")) {
BlockPos position = (BlockPos) arguments[0];
BlockState blockState = (BlockState) arguments[1];
put(blocks, position.getX(), position.getY(), position.getZ(), blockState);
return true;
}
if (methodName.equals("getSeed")) {
return TEST_SEED;
}
if (methodName.equals("hashCode")) {
return System.identityHashCode(proxy);
}
if (methodName.equals("equals")) {
return proxy == arguments[0];
}
if (methodName.equals("toString")) {
return "encase-test-world";
}
throw new UnsupportedOperationException(method.toString());
};
return (WorldGenLevel) Proxy.newProxyInstance(
WorldGenLevel.class.getClassLoader(), new Class<?>[]{WorldGenLevel.class}, handler);
}
private static void put(Map<BlockPos, BlockState> blocks,
int x, int y, int z, BlockState state) {
blocks.put(new BlockPos(x, y, z), state);
}
private static BlockState state(Map<BlockPos, BlockState> blocks, int x, int y, int z) {
return blocks.getOrDefault(new BlockPos(x, y, z), Blocks.AIR.defaultBlockState());
}
}
@@ -34,7 +34,7 @@ public class NativeStructurePostProcessorMonumentTest {
StructureStart start = monumentStart(1337L);
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
start, "minecraft:monument", 0, 63, -64, 320, false, (x, z) -> 0);
start, "minecraft:monument", 0, 63, -64, 320, false, false, null, (x, z) -> 0);
assertEquals(0, offset);
assertEquals(39, start.getBoundingBox().minY());
@@ -50,7 +50,7 @@ public class NativeStructurePostProcessorMonumentTest {
BoundingBox cachedBounds = start.getBoundingBox();
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
start, "minecraft:monument", 0, 50, -256, 512, false, (x, z) -> 0);
start, "minecraft:monument", 0, 50, -256, 512, false, false, null, (x, z) -> 0);
assertEquals(-13, offset);
assertSame(cachedBounds, start.getBoundingBox());
@@ -64,7 +64,7 @@ public class NativeStructurePostProcessorMonumentTest {
}
int repeatedOffset = NativeStructurePostProcessor.applyVerticalPlacement(
start, "minecraft:monument", 0, 50, -256, 512, false, (x, z) -> 0);
start, "minecraft:monument", 0, 50, -256, 512, false, false, null, (x, z) -> 0);
assertEquals(0, repeatedOffset);
}
@@ -73,7 +73,7 @@ public class NativeStructurePostProcessorMonumentTest {
StructureStart start = monumentStart(1337L);
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
start, "minecraft:monument", 3, 50, -256, 512, false, (x, z) -> 0);
start, "minecraft:monument", 3, 50, -256, 512, false, false, null, (x, z) -> 0);
assertEquals(-10, offset);
assertEquals(29, start.getBoundingBox().minY());
@@ -86,7 +86,7 @@ public class NativeStructurePostProcessorMonumentTest {
ChunkPos chunkPos = new ChunkPos(0, 0);
StructureStart initial = monumentStart(seed);
NativeStructurePostProcessor.applyVerticalPlacement(
initial, "minecraft:monument", 0, 50, -256, 512, false, (x, z) -> 0);
initial, "minecraft:monument", 0, 50, -256, 512, false, false, null, (x, z) -> 0);
PiecesContainer regenerated = OceanMonumentStructure.regeneratePiecesAfterLoad(
chunkPos, seed, new PiecesContainer(initial.getPieces()));
@@ -95,7 +95,7 @@ public class NativeStructurePostProcessorMonumentTest {
assertEquals(39, reloaded.getBoundingBox().minY());
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
reloaded, "minecraft:monument", 0, 50, -256, 512, false, (x, z) -> 0);
reloaded, "minecraft:monument", 0, 50, -256, 512, false, false, null, (x, z) -> 0);
assertEquals(-13, offset);
assertEquals(26, reloaded.getBoundingBox().minY());
assertEquals(48, reloaded.getBoundingBox().maxY());
@@ -106,7 +106,7 @@ public class NativeStructurePostProcessorMonumentTest {
StructureStart start = monumentStart(1337L);
try {
NativeStructurePostProcessor.applyVerticalPlacement(
start, "minecraft:monument", 0, -50, -64, 320, false, (x, z) -> 0);
start, "minecraft:monument", 0, -50, -64, 320, false, false, null, (x, z) -> 0);
} catch (IllegalStateException error) {
assertTrue(error.getMessage().contains("cannot align"));
return;
@@ -39,37 +39,38 @@ public class NativeStructurePostProcessorScatteredFeatureTest {
}
@Test
public void desertPyramidUsesTheHighestFootprintSurfaceAndLocksVanillaReanchor() {
public void desertPyramidUsesTheLowestFootprintSurfaceAndLocksVanillaReanchor() {
TestDesertPyramidPiece piece = new TestDesertPyramidPiece(RandomSource.create(11L), 0, 0);
StructureStart start = desertStart(piece);
BoundingBox cachedBounds = start.getBoundingBox();
BoundingBox footprint = piece.getBoundingBox();
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false,
(x, z) -> x == footprint.maxX() && z == footprint.maxZ() ? 92 : 64);
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false, false, null,
(x, z) -> x == footprint.maxX() && z == footprint.maxZ() ? 64 : 92);
assertEquals(29, offset);
assertEquals(1, offset);
assertSame(cachedBounds, start.getBoundingBox());
assertEquals(93, piece.getBoundingBox().minY());
assertEquals(93, start.getBoundingBox().minY());
assertEquals(65, piece.getBoundingBox().minY());
assertEquals(65, start.getBoundingBox().minY());
assertTrue(piece.attemptVanillaAlignment(-2));
assertEquals(93, piece.getBoundingBox().minY());
assertEquals(65, piece.getBoundingBox().minY());
}
@Test
public void jungleTempleHonorsConfiguredShiftAndLocksVanillaReanchor() {
public void jungleTempleUsesAverageFootprintSurfaceAndHonorsConfiguredShift() {
TestJungleTemplePiece piece = new TestJungleTemplePiece(RandomSource.create(19L), 32, -16);
StructureStart start = jungleStart(piece);
BoundingBox footprint = piece.getBoundingBox();
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
start, "minecraft:jungle_pyramid", 4, 63, -64, 320, false,
(x, z) -> x == 32 && z == -16 ? 88 : 70);
start, "minecraft:jungle_pyramid", 4, 63, -64, 320, false, false, null,
(x, z) -> x == footprint.minX() && z == footprint.minZ() ? 88 : 70);
assertEquals(29, offset);
assertEquals(93, piece.getBoundingBox().minY());
assertEquals(11, offset);
assertEquals(75, piece.getBoundingBox().minY());
assertTrue(piece.attemptVanillaAlignment());
assertEquals(93, piece.getBoundingBox().minY());
assertEquals(75, piece.getBoundingBox().minY());
}
@Test
@@ -78,9 +79,9 @@ public class NativeStructurePostProcessorScatteredFeatureTest {
StructureStart start = desertStart(piece);
int initialOffset = NativeStructurePostProcessor.applyVerticalPlacement(
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false, (x, z) -> 80);
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false, false, null, (x, z) -> 80);
int repeatedOffset = NativeStructurePostProcessor.applyVerticalPlacement(
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false, (x, z) -> 80);
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false, false, null, (x, z) -> 80);
assertEquals(17, initialOffset);
assertEquals(0, repeatedOffset);
@@ -93,7 +94,7 @@ public class NativeStructurePostProcessorScatteredFeatureTest {
StructureStart start = desertStart(piece);
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false, (x, z) -> 318);
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false, false, null, (x, z) -> 318);
assertEquals(241, offset);
assertEquals(305, piece.getBoundingBox().minY());
@@ -101,7 +102,7 @@ public class NativeStructurePostProcessorScatteredFeatureTest {
}
@Test
public void onlyPyramidsAndTemplesOptIntoScatteredSurfaceFitting() {
public void scatteredStructuresDoNotOptIntoFullFootprintSurfaceFitting() {
TestDesertPyramidPiece desert = new TestDesertPyramidPiece(RandomSource.create(31L), 0, 0);
TestJungleTemplePiece jungle = new TestJungleTemplePiece(RandomSource.create(37L), 0, 0);
SwampHutPiece swamp = new SwampHutPiece(RandomSource.create(41L), 0, 0);
@@ -109,13 +110,13 @@ public class NativeStructurePostProcessorScatteredFeatureTest {
StructureStart jungleStart = jungleStart(jungle);
StructureStart swampStart = swampStart(swamp);
assertTrue(NativeStructurePostProcessor.requiresSurfaceTerrain(desertStart));
assertTrue(NativeStructurePostProcessor.requiresSurfaceTerrain(jungleStart));
assertFalse(NativeStructurePostProcessor.requiresSurfaceTerrain(desertStart));
assertFalse(NativeStructurePostProcessor.requiresSurfaceTerrain(jungleStart));
assertFalse(NativeStructurePostProcessor.requiresSurfaceTerrain(swampStart));
AtomicInteger terrainQueries = new AtomicInteger();
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
swampStart, "minecraft:swamp_hut", 0, 63, -64, 320, false,
swampStart, "minecraft:swamp_hut", 0, 63, -64, 320, false, false, null,
(x, z) -> terrainQueries.incrementAndGet());
assertEquals(0, offset);
assertEquals(0, terrainQueries.get());
@@ -1,19 +1,32 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.engine.mantle.components.StructureCarvingFootprint;
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 net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
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.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.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.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.structures.DesertPyramidPiece;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -26,11 +39,20 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.assertTrue;
public class NativeStructurePostProcessorSurfaceTerrainTest {
private static final int SLAB_DEPTH = 128;
private static final int SLAB_MIN_Y = 64;
private static final int SLAB_PADDING = 14;
private static final int SLAB_WIDTH = 16;
private static final long TEST_SEED = 8675309L;
@BeforeClass
public static void bootstrapMinecraft() {
SharedConstants.tryDetectVersion();
@@ -209,6 +231,408 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
assertEquals(Blocks.GRAVEL.defaultBlockState(), state(blocks, 0, 62, 0));
}
@Test
public void preserveSourceYSkipsBurialAndKeepsTheVanillaStartY() {
StructureStart start = desertStart();
int minY = start.getBoundingBox().minY();
int offset = NativeStructurePostProcessor.applyVerticalShift(
start, 0, -64, 320, true, true, null, (x, z) -> 40);
assertEquals(0, offset);
assertEquals(minY, start.getBoundingBox().minY());
}
@Test
public void preserveSourceYStillAppliesAnExplicitShift() {
StructureStart start = desertStart();
int minY = start.getBoundingBox().minY();
int offset = NativeStructurePostProcessor.applyVerticalShift(
start, -8, -64, 320, true, true, null, (x, z) -> 40);
assertEquals(-8, offset);
assertEquals(minY - 8, start.getBoundingBox().minY());
}
@Test
public void burialStillSinksTheStructureBelowTheLowestTerrainColumn() {
StructureStart start = desertStart();
int minY = start.getBoundingBox().minY();
int maxY = start.getBoundingBox().maxY();
int expected = 40 - 1 - maxY;
int offset = NativeStructurePostProcessor.applyVerticalShift(
start, 0, -64, 320, true, false, null, (x, z) -> 40);
assertEquals(expected, offset);
assertEquals(minY + expected, start.getBoundingBox().minY());
}
@Test
public void unfittableBurialClampsToTheWorldFloorInsteadOfAborting() {
StructureStart start = desertStart();
int minY = start.getBoundingBox().minY();
int worldMinY = minY - 4;
int offset = NativeStructurePostProcessor.applyVerticalShift(
start, 0, worldMinY, 320, true, false, null, (x, z) -> worldMinY);
assertEquals(-4, offset);
assertEquals(worldMinY, start.getBoundingBox().minY());
}
@Test
public void nativeVacuumClearsEveryPieceEnvelopeBeforePlacement() {
StructureStart start = desertStart();
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
Map<BlockPos, BlockState> blocks = new HashMap<>();
put(blocks, bounds.minX(), bounds.minY(), bounds.minZ(), Blocks.STONE.defaultBlockState());
NativeStructurePostProcessor.integrateTerrain(
world(blocks), bounds, "minecraft:desert_pyramid", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.VACUUM), null);
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.minX(), bounds.minY(), bounds.minZ()));
}
@Test
public void nativeForceCarveHonorsConfiguredPadding() {
StructureStart start = desertStart();
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
BoundingBox area = new BoundingBox(
bounds.minX() - 1, bounds.minY(), bounds.minZ() - 1,
bounds.maxX() + 1, bounds.maxY() + 1, bounds.maxZ() + 1);
Map<BlockPos, BlockState> blocks = new HashMap<>();
put(blocks, bounds.maxX() + 1, bounds.minY(), bounds.maxZ(),
Blocks.STONE.defaultBlockState());
put(blocks, bounds.maxX(), bounds.maxY() + 1, bounds.maxZ(),
Blocks.STONE.defaultBlockState());
NativeStructurePostProcessor.integrateTerrain(
world(blocks), area, "minecraft:desert_pyramid", start,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(1)
.setCeilingPadding(1), null);
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.maxX() + 1, bounds.minY(), bounds.maxZ()));
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, bounds.maxX(), bounds.maxY() + 1, bounds.maxZ()));
}
@Test
public void nativeForceCarveUsesPieceUnionInsteadOfCombinedBounds() {
Structure structure = new DesertPyramidStructure(
new Structure.StructureSettings(HolderSet.empty()));
DesertPyramidPiece first = new DesertPyramidPiece(RandomSource.create(7L), 0, 0);
DesertPyramidPiece second = new DesertPyramidPiece(RandomSource.create(8L), 0, 0);
second.move(64, 0, 0);
StructureStart start = new StructureStart(
structure, new ChunkPos(0, 0), 0,
new PiecesContainer(List.of(first, second)));
BoundingBox firstBounds = first.getBoundingBox();
BoundingBox secondBounds = second.getBoundingBox();
int gapX = (firstBounds.maxX() + secondBounds.minX()) / 2;
int y = firstBounds.minY();
int z = firstBounds.minZ();
BoundingBox area = new BoundingBox(
firstBounds.minX(), y, z,
secondBounds.maxX(), y, z);
Map<BlockPos, BlockState> blocks = new HashMap<>();
put(blocks, firstBounds.minX(), y, z, Blocks.STONE.defaultBlockState());
put(blocks, gapX, y, z, Blocks.STONE.defaultBlockState());
NativeStructurePostProcessor.integrateTerrain(
world(blocks), area, "minecraft:ancient_city", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.FORCE_CARVE), null);
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, firstBounds.minX(), y, z));
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, gapX, y, z));
}
@Test
public void templateBackedColumnsUseTheTemplateAirComplement() throws Exception {
StructureTemplate template = template(List.of(
new StructureTemplate.StructureBlockInfo(
new BlockPos(0, 0, 0), Blocks.AIR.defaultBlockState(), null),
new StructureTemplate.StructureBlockInfo(
new BlockPos(0, 1, 0), Blocks.AIR.defaultBlockState(), null),
new StructureTemplate.StructureBlockInfo(
new BlockPos(0, 2, 0), Blocks.DEEPSLATE_BRICKS.defaultBlockState(), null),
new StructureTemplate.StructureBlockInfo(
new BlockPos(1, 0, 0), Blocks.STRUCTURE_VOID.defaultBlockState(), null),
new StructureTemplate.StructureBlockInfo(
new BlockPos(1, 1, 0), Blocks.DEEPSLATE.defaultBlockState(), null),
new StructureTemplate.StructureBlockInfo(
new BlockPos(1, 2, 0), Blocks.DEEPSLATE.defaultBlockState(), null)));
Map<Long, int[]> columns = new HashMap<>();
assertTrue(NativeStructurePostProcessor.emitTemplateColumns(
List.of(template), new BlockPos(0, 0, 0), Rotation.NONE,
new BoundingBox(0, 0, 0, 1, 2, 0),
(x, z, minY, maxY) -> columns.put((long) x << 32 | z & 0xffffffffL,
new int[]{minY, maxY})));
assertEquals(2, columns.size());
assertArrayEquals(new int[]{2, 2}, columns.get(0L));
assertArrayEquals(new int[]{1, 2}, columns.get(1L << 32));
}
@Test
public void fullyVoidTemplateColumnsAreNotCarveSources() throws Exception {
StructureTemplate template = template(List.of(
new StructureTemplate.StructureBlockInfo(
new BlockPos(0, 0, 0), Blocks.AIR.defaultBlockState(), null),
new StructureTemplate.StructureBlockInfo(
new BlockPos(0, 1, 0), Blocks.STRUCTURE_VOID.defaultBlockState(), null)));
Map<Long, int[]> columns = new HashMap<>();
assertTrue(NativeStructurePostProcessor.emitTemplateColumns(
List.of(template), new BlockPos(0, 0, 0), Rotation.NONE,
new BoundingBox(0, 0, 0, 0, 1, 0),
(x, z, minY, maxY) -> columns.put((long) x << 32 | z & 0xffffffffL,
new int[]{minY, maxY})));
assertTrue(columns.isEmpty());
}
@Test
public void nonTemplatePiecesFallBackToTheirBoundingBoxColumns() {
StructureStart start = desertStart();
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
StructureCarvingFootprint footprint = NativeStructurePostProcessor.carveFootprint(
start, 4, NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertEquals(bounds.minX() - 4, footprint.minX());
assertEquals(bounds.maxX() + 4, footprint.maxX());
assertEquals(bounds.minZ() - 4, footprint.minZ());
assertEquals(bounds.maxZ() + 4, footprint.maxZ());
assertEquals(0L, footprint.distanceSquaredAt(bounds.minX(), bounds.minZ()));
assertEquals(32L, footprint.distanceSquaredAt(bounds.minX() - 4, bounds.minZ() - 4));
assertEquals(bounds.minY(), footprint.sourceMinYAt(bounds.minX(), bounds.minZ()));
assertEquals(bounds.maxY(), footprint.sourceMaxYAt(bounds.minX(), bounds.minZ()));
}
@Test
public void carveFootprintIsComputedOncePerStartAndPadding() {
StructureStart start = desertStart();
StructureCarvingFootprint first = NativeStructurePostProcessor.carveFootprint(
start, 6, NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
StructureCarvingFootprint repeated = NativeStructurePostProcessor.carveFootprint(
start, 6, NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
StructureCarvingFootprint widened = NativeStructurePostProcessor.carveFootprint(
start, 7, NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
StructureCarvingFootprint other = NativeStructurePostProcessor.carveFootprint(
desertStart(), 6, NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertSame(first, repeated);
assertNotSame(first, widened);
assertNotSame(first, other);
}
@Test
public void organicCarveNeverCutsBelowTheColumnSupportingFloor() {
StructureStart start = desertStart();
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
NativeStructurePostProcessor.OrganicCarve carve = organicCarve(start, 6);
BoundingBox area = new BoundingBox(
bounds.minX() - 6, bounds.minY() - 4, bounds.minZ() - 6,
bounds.maxX() + 6, bounds.maxY() + 12, bounds.maxZ() + 6);
Map<BlockPos, BlockState> blocks = fill(area);
NativeStructurePostProcessor.carveOrganicColumns(world(blocks), area, carve);
int centerX = bounds.minX() + bounds.getXSpan() / 2;
int centerZ = bounds.minZ() + bounds.getZSpan() / 2;
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, centerX, bounds.minY(), centerZ));
for (int x = area.minX(); x <= area.maxX(); x++) {
for (int z = area.minZ(); z <= area.maxZ(); z++) {
for (int y = area.minY(); y < bounds.minY(); y++) {
assertEquals(Blocks.STONE.defaultBlockState(), state(blocks, x, y, z));
}
}
}
}
@Test
public void lobedCarveStaysInsideTheUniformCarveAndRemovesLess() {
StructureStart start = desertStart();
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
BoundingBox area = new BoundingBox(
bounds.minX() - 10, bounds.minY(), bounds.minZ() - 10,
bounds.maxX() + 10, bounds.maxY() + 12, bounds.maxZ() + 10);
Map<BlockPos, BlockState> uniformBlocks = fill(area);
Map<BlockPos, BlockState> lobedBlocks = fill(area);
NativeStructurePostProcessor.carveOrganicColumns(
world(uniformBlocks), area, organicCarve(start, 10, 0D));
NativeStructurePostProcessor.carveOrganicColumns(
world(lobedBlocks), area, organicCarve(start, 10, 0.85D));
int uniform = 0;
int lobed = 0;
for (int x = area.minX(); x <= area.maxX(); x++) {
for (int z = area.minZ(); z <= area.maxZ(); z++) {
for (int y = area.minY(); y <= area.maxY(); y++) {
boolean uniformAir = state(uniformBlocks, x, y, z).isAir();
boolean lobedAir = state(lobedBlocks, x, y, z).isAir();
assertTrue("lobe carved outside the uniform padding at "
+ x + "," + y + "," + z, uniformAir || !lobedAir);
uniform += uniformAir ? 1 : 0;
lobed += lobedAir ? 1 : 0;
}
}
}
assertTrue(lobed > 0);
assertTrue("uniform " + uniform + " lobed " + lobed, lobed < uniform);
}
@Test
public void lobedCarveDepthWandersAlongAStraightFootprintEdge() {
int[] uniform = slabEdgeCarveDepths(0D);
int[] lobed = slabEdgeCarveDepths(0.85D);
assertTrue("uniform depths wander " + span(uniform), span(uniform) <= 2);
assertTrue("lobed depths wander " + span(lobed), span(lobed) >= 6);
for (int depth : lobed) {
assertTrue(depth >= 0 && depth <= SLAB_PADDING);
}
}
@Test
public void organicCarveIsIdenticalAcrossNeighboringChunkContexts() {
StructureStart start = desertStart();
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
BoundingBox wide = new BoundingBox(
bounds.minX() - 6, bounds.minY(), bounds.minZ() - 6,
bounds.maxX() + 6, bounds.maxY() + 12, bounds.maxZ() + 6);
BoundingBox narrow = new BoundingBox(
bounds.maxX() - 3, bounds.minY(), bounds.maxZ() - 3,
bounds.maxX() + 6, bounds.maxY() + 12, bounds.maxZ() + 6);
Map<BlockPos, BlockState> wideBlocks = fill(wide);
Map<BlockPos, BlockState> narrowBlocks = fill(narrow);
// Each chunk context rebuilds its own noise channels from the shared start identity.
NativeStructurePostProcessor.carveOrganicColumns(
world(wideBlocks), wide, organicCarve(start, 6));
NativeStructurePostProcessor.carveOrganicColumns(
world(narrowBlocks), narrow, organicCarve(start, 6));
int carved = 0;
for (int x = narrow.minX(); x <= narrow.maxX(); x++) {
for (int z = narrow.minZ(); z <= narrow.maxZ(); z++) {
for (int y = narrow.minY(); y <= narrow.maxY(); y++) {
BlockState expected = state(wideBlocks, x, y, z);
assertEquals(expected, state(narrowBlocks, x, y, z));
if (expected.isAir()) {
carved++;
}
}
}
}
assertTrue(carved > 0);
}
@Test
public void erodedForceCarveShrinkwrapsWithoutUnderminingTheFloor() {
StructureStart start = desertStart();
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
BoundingBox area = new BoundingBox(
bounds.minX() - 6, bounds.minY() - 2, bounds.minZ() - 6,
bounds.maxX() + 6, bounds.maxY() + 12, bounds.maxZ() + 6);
Map<BlockPos, BlockState> blocks = fill(area);
int centerX = bounds.minX() + bounds.getXSpan() / 2;
int centerZ = bounds.minZ() + bounds.getZSpan() / 2;
NativeStructurePostProcessor.integrateTerrain(
world(blocks), area, "minecraft:ancient_city", start,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setShape(IrisStructureCarveShape.ERODED)
.setHorizontalPadding(6)
.setCeilingPadding(8)
.setFloorPadding(0)
.setErosionStrength(1D)
.setErosionFrequency(0.05D), null);
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, centerX, bounds.minY(), centerZ));
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, centerX, bounds.minY() - 1, centerZ));
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, area.minX(), area.maxY(), area.minZ()));
}
@Test
public void sparseStiltGridIsDeterministicAndPreflightsGround() {
assertTrue(NativeStructurePostProcessor.isStiltColumn(0, 0, 4));
assertTrue(NativeStructurePostProcessor.isStiltColumn(-4, 8, 4));
assertFalse(NativeStructurePostProcessor.isStiltColumn(1, 0, 4));
assertTrue(NativeStructurePostProcessor.isStiltColumn(1, 1, 1));
Map<BlockPos, BlockState> blocks = new HashMap<>();
put(blocks, 0, 7, 0, Blocks.DEEPSLATE.defaultBlockState());
put(blocks, 0, 8, 0, Blocks.SCULK_VEIN.defaultBlockState());
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
assertEquals(7, NativeStructurePostProcessor.findStiltAnchorY(
world(blocks), 0, 0, 10, 2, -64, -64, position));
assertEquals(Integer.MIN_VALUE, NativeStructurePostProcessor.findStiltAnchorY(
world(blocks), 0, 0, 10, 1, -64, -64, position));
assertEquals(Integer.MIN_VALUE, NativeStructurePostProcessor.findStiltAnchorY(
world(new HashMap<>()), 0, 0, 10, 64, -64, -64, position));
}
@Test
public void nativeTerrainEnvelopePersistsHorizontalReferenceCoverage() {
StructureStart generated = desertStart();
BoundingBox content = generated.getBoundingBox();
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated,
generated.getStructure(),
0,
null,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(24));
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());
}
@Test
public void nativeTerrainEnvelopeClipsToMinecraftReferenceCoverage() {
StructureStart generated = desertStart();
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated,
generated.getStructure(),
0,
null,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(128));
assertEquals(-128, wrapped.getBoundingBox().minX());
assertEquals(-128, wrapped.getBoundingBox().minZ());
assertEquals(143, wrapped.getBoundingBox().maxX());
assertEquals(143, wrapped.getBoundingBox().maxZ());
}
@Test
public void singlePoolTemplateFieldMatchesTheRuntimeContract() {
Field field = NativeStructurePostProcessor.resolveSinglePoolTemplateField();
@@ -302,6 +726,100 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
return new NativeStructurePostProcessor.SurfaceAnchor(0, 4, 0, 4, meetY, strength);
}
private static NativeStructurePostProcessor.OrganicCarve organicCarve(StructureStart start,
int horizontalPadding) {
return organicCarve(start, horizontalPadding, 0.85D);
}
private static NativeStructurePostProcessor.OrganicCarve organicCarve(StructureStart start,
int horizontalPadding,
double lobeStrength) {
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setShape(IrisStructureCarveShape.ERODED)
.setHorizontalPadding(horizontalPadding)
.setCeilingPadding(8)
.setFloorPadding(0)
.setErosionStrength(1D)
.setErosionFrequency(0.05D)
.setLobeStrength(lobeStrength);
return NativeStructurePostProcessor.organicCarve(
NativeStructurePostProcessor.carveFootprint(start, horizontalPadding,
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager),
terrain, IrisStructureCarveShape.ERODED, TEST_SEED);
}
/**
* Outward carve depth for every column along the straight +X edge of a slab footprint, measured
* inside the source vertical span so the boundary is decided purely by the horizontal threshold.
* The slab spans several lobe wavelengths so a lobed boundary is distinguishable from a uniform one.
*/
private static int[] slabEdgeCarveDepths(double lobeStrength) {
StructureCarvingFootprint footprint = StructureCarvingFootprint.fromColumns(sink -> {
for (int x = 0; x < SLAB_WIDTH; x++) {
for (int z = 0; z < SLAB_DEPTH; z++) {
sink.column(x, z, SLAB_MIN_Y, SLAB_MIN_Y + 8);
}
}
return true;
}, SLAB_PADDING, 1_000_000);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setShape(IrisStructureCarveShape.ERODED)
.setHorizontalPadding(SLAB_PADDING)
.setCeilingPadding(8)
.setFloorPadding(0)
.setErosionStrength(1D)
.setErosionFrequency(0.05D)
.setLobeStrength(lobeStrength);
int transectY = SLAB_MIN_Y + 4;
BoundingBox area = new BoundingBox(
SLAB_WIDTH, transectY, 0,
SLAB_WIDTH - 1 + SLAB_PADDING, transectY, SLAB_DEPTH - 1);
Map<BlockPos, BlockState> blocks = fill(area);
NativeStructurePostProcessor.carveOrganicColumns(world(blocks), area,
NativeStructurePostProcessor.organicCarve(
footprint, terrain, IrisStructureCarveShape.ERODED, TEST_SEED));
int[] depths = new int[SLAB_DEPTH];
for (int z = 0; z < SLAB_DEPTH; z++) {
int depth = 0;
while (depth < SLAB_PADDING
&& state(blocks, SLAB_WIDTH + depth, transectY, z).isAir()) {
depth++;
}
depths[z] = depth;
}
return depths;
}
private static int span(int[] values) {
int lowest = Integer.MAX_VALUE;
int highest = Integer.MIN_VALUE;
for (int value : values) {
lowest = Math.min(lowest, value);
highest = Math.max(highest, value);
}
return highest - lowest;
}
private static StructureTemplateManager forbiddenTemplateManager() {
throw new AssertionError("Bounding-box carve columns must not resolve templates");
}
private static Map<BlockPos, BlockState> fill(BoundingBox area) {
Map<BlockPos, BlockState> blocks = new HashMap<>();
for (int x = area.minX(); x <= area.maxX(); x++) {
for (int z = area.minZ(); z <= area.maxZ(); z++) {
for (int y = area.minY(); y <= area.maxY(); y++) {
put(blocks, x, y, z, Blocks.STONE.defaultBlockState());
}
}
}
return blocks;
}
private static StructureTemplate template(
List<StructureTemplate.StructureBlockInfo> blocks) throws Exception {
Constructor<StructureTemplate.Palette> constructor =
@@ -313,6 +831,14 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
return template;
}
private static StructureStart desertStart() {
Structure structure = new DesertPyramidStructure(
new Structure.StructureSettings(HolderSet.empty()));
DesertPyramidPiece piece = new DesertPyramidPiece(RandomSource.create(7L), 0, 0);
return new StructureStart(
structure, new ChunkPos(0, 0), 0, new PiecesContainer(List.of(piece)));
}
private static WorldGenLevel world(Map<BlockPos, BlockState> blocks) {
InvocationHandler handler = (proxy, method, arguments) -> {
String methodName = method.getName();
@@ -326,6 +852,9 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
put(blocks, position.getX(), position.getY(), position.getZ(), blockState);
return true;
}
if (methodName.equals("getSeed")) {
return TEST_SEED;
}
if (methodName.equals("hashCode")) {
return System.identityHashCode(proxy);
}
@@ -63,15 +63,9 @@ public class NativeStructurePostProcessorVegetationTest {
}
@Test
public void undergroundStructuresFailWhenBurialWouldCrossWorldFloor() {
public void undergroundBurialClampsToTheWorldFloorInsteadOfFailing() {
BoundingBox bounds = new BoundingBox(0, 60, 0, 1, 80, 0);
try {
NativeStructurePostProcessor.resolveBuriedOffset(
bounds, 0, 58, 320, (x, z) -> x == 1 ? 76 : 100);
} catch (IllegalStateException e) {
assertTrue(e.getMessage().contains("world minimum"));
return;
}
throw new AssertionError("Expected underground burial to fail below the world floor");
assertEquals(-2, NativeStructurePostProcessor.resolveBuriedOffset(
bounds, 0, 58, 320, (x, z) -> x == 1 ? 76 : 100));
}
}
@@ -81,6 +81,8 @@ import art.arcane.volmlib.util.exceptions.IrisException;
import art.arcane.iris.util.common.format.C;
import art.arcane.volmlib.util.function.NastyRunnable;
import art.arcane.volmlib.util.hotload.ConfigHotloadEngine;
import art.arcane.volmlib.util.hud.HudBossBarLane;
import art.arcane.volmlib.util.hud.HudSlotService;
import art.arcane.volmlib.util.io.IO;
import art.arcane.volmlib.util.io.InstanceState;
import art.arcane.volmlib.util.io.JarScanner;
@@ -619,6 +621,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
SimdSupport.install();
services = new KMap<>();
setupAudience();
BukkitPlatform.hostHud(new HudSlotService(this), new HudBossBarLane());
Bindings.setupSentry();
initialize("art.arcane.iris.core.service", IrisService.class).forEach((i) -> {
Class<? extends IrisService> serviceType = i.getClass().asSubclass(IrisService.class);
@@ -1016,6 +1019,10 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
services.values().forEach(IrisService::onDisable);
}
IrisServices.clear();
if (BukkitPlatform.hasHud()) {
BukkitPlatform.hudSlots().shutdown();
BukkitPlatform.hudLanes().shutdown();
}
if (configHotloadEngine != null) {
configHotloadEngine.clear();
configHotloadEngine = null;
@@ -76,12 +76,18 @@ import art.arcane.iris.util.common.plugin.VolmitSender;
import art.arcane.iris.util.common.scheduling.J;
import art.arcane.volmlib.util.scheduling.O;
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import art.arcane.volmlib.util.hud.HudPriority;
import art.arcane.volmlib.util.hud.HudSlotClaim;
import art.arcane.volmlib.util.hud.HudSlotRequest;
import art.arcane.volmlib.util.hud.HudSurface;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.FluidCollisionMode;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarStyle;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
@@ -96,8 +102,10 @@ import java.nio.file.attribute.FileTime;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
import art.arcane.iris.core.localization.IrisLanguage;
@@ -337,10 +345,30 @@ public class CommandStudio implements DirectorExecutor {
var loc = player.getLocation();
int totalTasks = d * d;
AtomicInteger completedTasks = new AtomicInteger(0);
int c = J.ar(() -> sender.sendProgress(
(double) completedTasks.get() / totalTasks,
IrisLanguage.text(RuntimeUiMessages.FINDING_REGIONS)
), 0);
HudSlotClaim titleClaim = BukkitPlatform.hudSlots().open(player, new HudSlotRequest("iris:job", HudPriority.PROGRESS, 1200L, List.of(HudSurface.TITLE)));
HudSlotClaim barClaim = BukkitPlatform.hudSlots().open(player, new HudSlotRequest("iris:job", HudPriority.PROGRESS, 1200L, List.of(HudSurface.ACTION_BAR, HudSurface.BOSS_BAR)));
AtomicLong lastResolveMs = new AtomicLong(0L);
int c = J.ar(() -> {
long now = System.currentTimeMillis();
if (now - lastResolveMs.get() >= 250L) {
lastResolveMs.set(now);
titleClaim.resolve();
barClaim.resolve();
}
double jobProgress = (double) completedTasks.get() / totalTasks;
HudSurface barSurface = barClaim.granted();
sender.sendProgress(
jobProgress,
IrisLanguage.text(RuntimeUiMessages.FINDING_REGIONS),
titleClaim.granted(),
barSurface
);
if (barSurface == HudSurface.BOSS_BAR) {
BukkitPlatform.hudLanes().show(player, "iris:job", IrisLanguage.text(RuntimeUiMessages.FINDING_REGIONS) + " " + Form.pc(jobProgress, 0), jobProgress, BarColor.BLUE, BarStyle.SOLID, 4000L);
} else if (barSurface == HudSurface.ACTION_BAR) {
BukkitPlatform.hudLanes().hide(player, "iris:job");
}
}, 0);
new Spiraler(d, d, (x, z) -> executor.queue(() -> {
var region = engine.getRegion((x << 4) + 8, (z << 4) + 8);
data.computeIfAbsent(region.getLoadKey(), (k) -> new AtomicInteger(0))
@@ -350,6 +378,9 @@ public class CommandStudio implements DirectorExecutor {
executor.complete();
multiBurst.close();
J.car(c);
titleClaim.release();
barClaim.release();
BukkitPlatform.hudLanes().hide(player, "iris:job");
sender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_STUDIO_DONE));
var loader = engine.getData().getRegionLoader();