mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 04:37:47 +00:00
Changes
This commit is contained in:
@@ -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
|
||||
|
||||
+117
-40
@@ -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) {
|
||||
|
||||
+32
@@ -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<>();
|
||||
|
||||
+106
@@ -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);
|
||||
}
|
||||
}
|
||||
+36
@@ -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")));
|
||||
|
||||
+6
-9
@@ -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);
|
||||
|
||||
+303
@@ -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);
|
||||
}
|
||||
}
|
||||
+174
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+301
@@ -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());
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -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;
|
||||
|
||||
+21
-20
@@ -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());
|
||||
|
||||
+529
@@ -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);
|
||||
}
|
||||
|
||||
+3
-9
@@ -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;
|
||||
|
||||
+35
-4
@@ -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();
|
||||
|
||||
@@ -1,2 +1,420 @@
|
||||
[15:08:50] [Test worker/INFO]: Iris object undo service ready (bounded to 32 paste(s) per player)
|
||||
[15:08:50] [Test worker/INFO]: [STDOUT]: [Iris/INFO] Iris /iris command tree registered
|
||||
[23:26:54] [Test worker/INFO]: Iris registered custom content provider 'iris_deferred_test'
|
||||
[23:26:54] [Test worker/ERROR]: Iris persistent dimension registry entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial9922444042409438775/iris-dimensions.json is invalid; skipping only that entry
|
||||
java.lang.IllegalArgumentException: entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial9922444042409438775/iris-dimensions.json has no dimension
|
||||
at art.arcane.iris.modded.ModdedDimensionRegistryStore.required(ModdedDimensionRegistryStore.java:117)
|
||||
at art.arcane.iris.modded.ModdedDimensionRegistryStore.load(ModdedDimensionRegistryStore.java:68)
|
||||
at art.arcane.iris.modded.ModdedDimensionRegistryStoreTest.malformedEntryDoesNotDiscardHealthyEntries(ModdedDimensionRegistryStoreTest.java:51)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
||||
[23:26:54] [Test worker/ERROR]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
||||
java.lang.RuntimeException: second disable failed
|
||||
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:55)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
||||
[23:26:54] [Test worker/ERROR]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService
|
||||
java.lang.RuntimeException: first disable failed
|
||||
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:54)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
||||
[23:26:54] [Test worker/ERROR]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
||||
java.lang.RuntimeException: cleanup failed
|
||||
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
||||
[23:26:54] [Test worker/ERROR]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
||||
java.lang.RuntimeException: enable failed
|
||||
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:31)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
||||
Suppressed: java.lang.RuntimeException: cleanup failed
|
||||
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32)
|
||||
... 42 more
|
||||
[23:26:54] [Test worker/ERROR]: [worldcheck] server stop request failed
|
||||
java.lang.IllegalStateException: stop request failed
|
||||
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$stopRequestFailureForcesNonzeroResult$1(ModdedWorldCheckTest.java:235)
|
||||
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:179)
|
||||
at art.arcane.iris.modded.ModdedWorldCheckTest.stopRequestFailureForcesNonzeroResult(ModdedWorldCheckTest.java:232)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
||||
[23:26:54] [Test worker/ERROR]: [worldcheck] waiting for server shutdown failed
|
||||
java.lang.IllegalStateException: shutdown wait failed
|
||||
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$shutdownWaitFailureForcesNonzeroExit$0(ModdedWorldCheckTest.java:261)
|
||||
at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:194)
|
||||
at art.arcane.iris.modded.ModdedWorldCheckTest.shutdownWaitFailureForcesNonzeroExit(ModdedWorldCheckTest.java:259)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
||||
[23:26:54] [Test worker/ERROR]: [worldcheck] check failed
|
||||
java.lang.IllegalStateException: check failed
|
||||
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$thrownCheckStillRequestsStop$0(ModdedWorldCheckTest.java:221)
|
||||
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:174)
|
||||
at art.arcane.iris.modded.ModdedWorldCheckTest.thrownCheckStillRequestsStop(ModdedWorldCheckTest.java:219)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
||||
[23:26:54] [Test worker/INFO]: Iris registered custom content provider 'iris_discovery_success'
|
||||
[23:26:54] [Test worker/ERROR]: Iris custom content provider discovery failed
|
||||
java.lang.RuntimeException: provider init failed
|
||||
at art.arcane.iris.modded.api.ModdedCustomContentRegistryTest.failedDiscoveryPublishesNothingAndPreservesTheCause(ModdedCustomContentRegistryTest.java:39)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
||||
[23:26:54] [Test worker/INFO]: Iris object undo service ready (bounded to 32 paste(s) per player)
|
||||
[23:26:54] [Test worker/INFO]: [STDOUT]: [Iris/INFO] Iris /iris command tree registered
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,9 +1,9 @@
|
||||
[26Jul2026 15:03:10.625] [Test worker/DEBUG] [io.netty.util.internal.logging.InternalLoggerFactory/]: Using SLF4J as the default logging framework
|
||||
[26Jul2026 15:03:10.627] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.level: simple
|
||||
[26Jul2026 15:03:10.627] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.targetRecords: 4
|
||||
[26Jul2026 15:03:12.446] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test'
|
||||
[26Jul2026 15:03:12.461] [Test worker/ERROR] [Iris/]: Iris persistent dimension registry entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial9764216152837868358/iris-dimensions.json is invalid; skipping only that entry
|
||||
java.lang.IllegalArgumentException: entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial9764216152837868358/iris-dimensions.json has no dimension
|
||||
[29Jul2026 23:26:59.053] [Test worker/DEBUG] [io.netty.util.internal.logging.InternalLoggerFactory/]: Using SLF4J as the default logging framework
|
||||
[29Jul2026 23:26:59.054] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.level: simple
|
||||
[29Jul2026 23:26:59.054] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.targetRecords: 4
|
||||
[29Jul2026 23:27:01.214] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test'
|
||||
[29Jul2026 23:27:01.233] [Test worker/ERROR] [Iris/]: Iris persistent dimension registry entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial3400051221726996941/iris-dimensions.json is invalid; skipping only that entry
|
||||
java.lang.IllegalArgumentException: entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial3400051221726996941/iris-dimensions.json has no dimension
|
||||
at art.arcane.iris.modded.ModdedDimensionRegistryStore.required(ModdedDimensionRegistryStore.java:117) ~[main/:?]
|
||||
at art.arcane.iris.modded.ModdedDimensionRegistryStore.load(ModdedDimensionRegistryStore.java:68) ~[main/:?]
|
||||
at art.arcane.iris.modded.ModdedDimensionRegistryStoreTest.malformedEntryDoesNotDiscardHealthyEntries(ModdedDimensionRegistryStoreTest.java:51) ~[test/:?]
|
||||
@@ -49,7 +49,7 @@ java.lang.IllegalArgumentException: entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtf
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.522] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
||||
[29Jul2026 23:27:01.309] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
||||
java.lang.RuntimeException: second disable failed
|
||||
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:55) ~[test/:?]
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
|
||||
@@ -94,7 +94,7 @@ java.lang.RuntimeException: second disable failed
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.524] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService
|
||||
[29Jul2026 23:27:01.313] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService
|
||||
java.lang.RuntimeException: first disable failed
|
||||
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:54) ~[test/:?]
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
|
||||
@@ -139,7 +139,7 @@ java.lang.RuntimeException: first disable failed
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.528] [Test worker/ERROR] [Iris/]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
||||
[29Jul2026 23:27:01.316] [Test worker/ERROR] [Iris/]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
||||
java.lang.RuntimeException: cleanup failed
|
||||
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
|
||||
@@ -184,7 +184,7 @@ java.lang.RuntimeException: cleanup failed
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.531] [Test worker/ERROR] [Iris/]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
||||
[29Jul2026 23:27:01.320] [Test worker/ERROR] [Iris/]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
||||
java.lang.RuntimeException: enable failed
|
||||
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:31) ~[test/:?]
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
|
||||
@@ -232,7 +232,7 @@ java.lang.RuntimeException: enable failed
|
||||
Suppressed: java.lang.RuntimeException: cleanup failed
|
||||
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
|
||||
... 42 more
|
||||
[26Jul2026 15:03:12.561] [Test worker/ERROR] [Iris/]: [worldcheck] server stop request failed
|
||||
[29Jul2026 23:27:01.357] [Test worker/ERROR] [Iris/]: [worldcheck] server stop request failed
|
||||
java.lang.IllegalStateException: stop request failed
|
||||
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$stopRequestFailureForcesNonzeroResult$1(ModdedWorldCheckTest.java:235) ~[test/:?]
|
||||
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:179) ~[main/:?]
|
||||
@@ -279,7 +279,7 @@ java.lang.IllegalStateException: stop request failed
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.564] [Test worker/ERROR] [Iris/]: [worldcheck] waiting for server shutdown failed
|
||||
[29Jul2026 23:27:01.360] [Test worker/ERROR] [Iris/]: [worldcheck] waiting for server shutdown failed
|
||||
java.lang.IllegalStateException: shutdown wait failed
|
||||
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$shutdownWaitFailureForcesNonzeroExit$0(ModdedWorldCheckTest.java:261) ~[test/:?]
|
||||
at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:194) ~[main/:?]
|
||||
@@ -326,7 +326,7 @@ java.lang.IllegalStateException: shutdown wait failed
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.571] [Test worker/ERROR] [Iris/]: [worldcheck] check failed
|
||||
[29Jul2026 23:27:01.365] [Test worker/ERROR] [Iris/]: [worldcheck] check failed
|
||||
java.lang.IllegalStateException: check failed
|
||||
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$thrownCheckStillRequestsStop$0(ModdedWorldCheckTest.java:221) ~[test/:?]
|
||||
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:174) ~[main/:?]
|
||||
@@ -373,8 +373,8 @@ java.lang.IllegalStateException: check failed
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.577] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success'
|
||||
[26Jul2026 15:03:12.578] [Test worker/ERROR] [Iris/]: Iris custom content provider discovery failed
|
||||
[29Jul2026 23:27:01.373] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success'
|
||||
[29Jul2026 23:27:01.373] [Test worker/ERROR] [Iris/]: Iris custom content provider discovery failed
|
||||
java.lang.RuntimeException: provider init failed
|
||||
at art.arcane.iris.modded.api.ModdedCustomContentRegistryTest.failedDiscoveryPublishesNothingAndPreservesTheCause(ModdedCustomContentRegistryTest.java:39) ~[test/:?]
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
|
||||
@@ -419,4 +419,4 @@ java.lang.RuntimeException: provider init failed
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.591] [Test worker/INFO] [Iris/]: Iris object undo service ready (bounded to 32 paste(s) per player)
|
||||
[29Jul2026 23:27:01.397] [Test worker/INFO] [Iris/]: Iris object undo service ready (bounded to 32 paste(s) per player)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[26Jul2026 15:03:12.446] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test'
|
||||
[26Jul2026 15:03:12.461] [Test worker/ERROR] [Iris/]: Iris persistent dimension registry entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial9764216152837868358/iris-dimensions.json is invalid; skipping only that entry
|
||||
java.lang.IllegalArgumentException: entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial9764216152837868358/iris-dimensions.json has no dimension
|
||||
[29Jul2026 23:27:01.214] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test'
|
||||
[29Jul2026 23:27:01.233] [Test worker/ERROR] [Iris/]: Iris persistent dimension registry entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial3400051221726996941/iris-dimensions.json is invalid; skipping only that entry
|
||||
java.lang.IllegalArgumentException: entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial3400051221726996941/iris-dimensions.json has no dimension
|
||||
at art.arcane.iris.modded.ModdedDimensionRegistryStore.required(ModdedDimensionRegistryStore.java:117) ~[main/:?]
|
||||
at art.arcane.iris.modded.ModdedDimensionRegistryStore.load(ModdedDimensionRegistryStore.java:68) ~[main/:?]
|
||||
at art.arcane.iris.modded.ModdedDimensionRegistryStoreTest.malformedEntryDoesNotDiscardHealthyEntries(ModdedDimensionRegistryStoreTest.java:51) ~[test/:?]
|
||||
@@ -46,7 +46,7 @@ java.lang.IllegalArgumentException: entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtf
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.522] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
||||
[29Jul2026 23:27:01.309] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
||||
java.lang.RuntimeException: second disable failed
|
||||
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:55) ~[test/:?]
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
|
||||
@@ -91,7 +91,7 @@ java.lang.RuntimeException: second disable failed
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.524] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService
|
||||
[29Jul2026 23:27:01.313] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService
|
||||
java.lang.RuntimeException: first disable failed
|
||||
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:54) ~[test/:?]
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
|
||||
@@ -136,7 +136,7 @@ java.lang.RuntimeException: first disable failed
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.528] [Test worker/ERROR] [Iris/]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
||||
[29Jul2026 23:27:01.316] [Test worker/ERROR] [Iris/]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
||||
java.lang.RuntimeException: cleanup failed
|
||||
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
|
||||
@@ -181,7 +181,7 @@ java.lang.RuntimeException: cleanup failed
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.531] [Test worker/ERROR] [Iris/]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
||||
[29Jul2026 23:27:01.320] [Test worker/ERROR] [Iris/]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
||||
java.lang.RuntimeException: enable failed
|
||||
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:31) ~[test/:?]
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
|
||||
@@ -229,7 +229,7 @@ java.lang.RuntimeException: enable failed
|
||||
Suppressed: java.lang.RuntimeException: cleanup failed
|
||||
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
|
||||
... 42 more
|
||||
[26Jul2026 15:03:12.561] [Test worker/ERROR] [Iris/]: [worldcheck] server stop request failed
|
||||
[29Jul2026 23:27:01.357] [Test worker/ERROR] [Iris/]: [worldcheck] server stop request failed
|
||||
java.lang.IllegalStateException: stop request failed
|
||||
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$stopRequestFailureForcesNonzeroResult$1(ModdedWorldCheckTest.java:235) ~[test/:?]
|
||||
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:179) ~[main/:?]
|
||||
@@ -276,7 +276,7 @@ java.lang.IllegalStateException: stop request failed
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.564] [Test worker/ERROR] [Iris/]: [worldcheck] waiting for server shutdown failed
|
||||
[29Jul2026 23:27:01.360] [Test worker/ERROR] [Iris/]: [worldcheck] waiting for server shutdown failed
|
||||
java.lang.IllegalStateException: shutdown wait failed
|
||||
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$shutdownWaitFailureForcesNonzeroExit$0(ModdedWorldCheckTest.java:261) ~[test/:?]
|
||||
at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:194) ~[main/:?]
|
||||
@@ -323,7 +323,7 @@ java.lang.IllegalStateException: shutdown wait failed
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.571] [Test worker/ERROR] [Iris/]: [worldcheck] check failed
|
||||
[29Jul2026 23:27:01.365] [Test worker/ERROR] [Iris/]: [worldcheck] check failed
|
||||
java.lang.IllegalStateException: check failed
|
||||
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$thrownCheckStillRequestsStop$0(ModdedWorldCheckTest.java:221) ~[test/:?]
|
||||
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:174) ~[main/:?]
|
||||
@@ -370,8 +370,8 @@ java.lang.IllegalStateException: check failed
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.577] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success'
|
||||
[26Jul2026 15:03:12.578] [Test worker/ERROR] [Iris/]: Iris custom content provider discovery failed
|
||||
[29Jul2026 23:27:01.373] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success'
|
||||
[29Jul2026 23:27:01.373] [Test worker/ERROR] [Iris/]: Iris custom content provider discovery failed
|
||||
java.lang.RuntimeException: provider init failed
|
||||
at art.arcane.iris.modded.api.ModdedCustomContentRegistryTest.failedDiscoveryPublishesNothingAndPreservesTheCause(ModdedCustomContentRegistryTest.java:39) ~[test/:?]
|
||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
|
||||
@@ -416,4 +416,4 @@ java.lang.RuntimeException: provider init failed
|
||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
|
||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
|
||||
[26Jul2026 15:03:12.591] [Test worker/INFO] [Iris/]: Iris object undo service ready (bounded to 32 paste(s) per player)
|
||||
[29Jul2026 23:27:01.397] [Test worker/INFO] [Iris/]: Iris object undo service ready (bounded to 32 paste(s) per player)
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package art.arcane.iris.nativegen;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
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 java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
final class ForcedStructureChunkGenerator extends ChunkGenerator {
|
||||
private static final int MIN_SAFE_STRUCTURE_Y = 80;
|
||||
private static final int VERTICAL_MARGIN = 32;
|
||||
|
||||
private final ChunkGenerator delegate;
|
||||
private final int targetY;
|
||||
|
||||
ForcedStructureChunkGenerator(ChunkGenerator delegate, Holder<Biome> sourceBiome, int targetY) {
|
||||
super(new FixedBiomeSource(sourceBiome));
|
||||
this.delegate = delegate;
|
||||
this.targetY = targetY;
|
||||
}
|
||||
|
||||
@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) {
|
||||
delegate.applyCarvers(region, seed, randomState, biomeManager, structureManager, chunk);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildSurface(WorldGenRegion level, StructureManager structureManager,
|
||||
RandomState randomState, ChunkAccess protoChunk) {
|
||||
delegate.buildSurface(level, structureManager, randomState, protoChunk);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawnOriginalMobs(WorldGenRegion worldGenRegion) {
|
||||
delegate.spawnOriginalMobs(worldGenRegion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGenDepth() {
|
||||
return delegate.getGenDepth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ChunkAccess> fillFromNoise(Blender blender, RandomState randomState,
|
||||
StructureManager structureManager,
|
||||
ChunkAccess centerChunk) {
|
||||
return delegate.fillFromNoise(
|
||||
blender, randomState, structureManager, centerChunk);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSeaLevel() {
|
||||
return delegate.getMinY() + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinY() {
|
||||
return delegate.getMinY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBaseHeight(int x, int z, Heightmap.Types type,
|
||||
LevelHeightAccessor heightAccessor, RandomState randomState) {
|
||||
return safeOccupiedY(heightAccessor) + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NoiseColumn getBaseColumn(int x, int z, LevelHeightAccessor heightAccessor,
|
||||
RandomState randomState) {
|
||||
int minY = heightAccessor.getMinY();
|
||||
BlockState[] states = new BlockState[heightAccessor.getHeight()];
|
||||
for (int index = 0; index < states.length; index++) {
|
||||
int y = minY + index;
|
||||
states[index] = (y & 1) == 0
|
||||
? Blocks.STONE.defaultBlockState()
|
||||
: Blocks.AIR.defaultBlockState();
|
||||
}
|
||||
return new NoiseColumn(minY, states);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDebugScreenInfo(List<String> result, RandomState randomState,
|
||||
BlockPos feetPos) {
|
||||
delegate.addDebugScreenInfo(result, randomState, feetPos);
|
||||
}
|
||||
|
||||
private int safeOccupiedY(LevelHeightAccessor heightAccessor) {
|
||||
int minY = heightAccessor.getMinY() + VERTICAL_MARGIN;
|
||||
int maxY = heightAccessor.getMaxY() - VERTICAL_MARGIN;
|
||||
if (minY > maxY) {
|
||||
return heightAccessor.getMinY()
|
||||
+ Math.max(0, heightAccessor.getHeight() / 2);
|
||||
}
|
||||
return Math.max(minY, Math.min(maxY, Math.max(MIN_SAFE_STRUCTURE_Y, targetY)));
|
||||
}
|
||||
}
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
package art.arcane.iris.nativegen;
|
||||
|
||||
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
|
||||
import art.arcane.iris.engine.object.IrisJigsawConfiguration;
|
||||
import art.arcane.iris.engine.object.IrisJigsawHeightmap;
|
||||
import art.arcane.iris.engine.object.IrisJigsawLiquidSettings;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtOps;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.resources.RegistryOps;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.RandomState;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.JigsawStructure;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.function.IntBinaryOperator;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public final class NativeStructureFactory {
|
||||
private NativeStructureFactory() {
|
||||
}
|
||||
|
||||
public static StructureStart generate(GenerationContext context, Holder<Structure> sourceHolder,
|
||||
NativeStructureStartPlan plan, int references) {
|
||||
Structure source = sourceHolder.value();
|
||||
Structure configured = configure(
|
||||
context.registryAccess(), source, plan.source().getJigsaw(),
|
||||
plan.placement().isUnderground(), plan.baseY());
|
||||
Holder<Biome> sourceBiome = source.biomes().stream().findFirst()
|
||||
.or(() -> context.biomeSource().possibleBiomes().stream().findFirst())
|
||||
.orElseThrow(() -> new IllegalStateException("Configured native structure '"
|
||||
+ plan.source().getStructure() + "' has no usable generation biome"));
|
||||
ChunkGenerator forcedGenerator = new ForcedStructureChunkGenerator(
|
||||
context.generator(), sourceBiome, plan.baseY());
|
||||
StructureStart generated = configured.generate(
|
||||
sourceHolder,
|
||||
context.levelKey(),
|
||||
context.registryAccess(),
|
||||
forcedGenerator,
|
||||
forcedGenerator.getBiomeSource(),
|
||||
context.randomState(),
|
||||
context.templateManager(),
|
||||
context.levelSeed(),
|
||||
new ChunkPos(plan.chunkX(), plan.chunkZ()),
|
||||
references,
|
||||
context.heightAccessor(),
|
||||
context.biomePredicate()
|
||||
);
|
||||
if (generated == null || !generated.isValid()) {
|
||||
return StructureStart.INVALID_START;
|
||||
}
|
||||
StructureStart positioned;
|
||||
if (plan.placement().isUnderground()) {
|
||||
positioned = NativeStructurePostProcessor.relocateToMinY(
|
||||
generated, source, plan.baseY(), context.heightAccessor());
|
||||
} else {
|
||||
NativeStructurePostProcessor.applyVerticalPlacement(
|
||||
generated,
|
||||
plan.source().getStructure(),
|
||||
0,
|
||||
context.seaLevel(),
|
||||
context.heightAccessor().getMinY(),
|
||||
Math.addExact(context.heightAccessor().getMinY(), context.heightAccessor().getHeight()),
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
context.surfaceHeight());
|
||||
positioned = generated;
|
||||
}
|
||||
if (!positioned.isValid()) {
|
||||
return StructureStart.INVALID_START;
|
||||
}
|
||||
return NativeStructureReferenceEnvelope.wrap(
|
||||
positioned,
|
||||
source,
|
||||
references,
|
||||
context.templateManager(),
|
||||
plan.placement().resolvedTerrain()
|
||||
);
|
||||
}
|
||||
|
||||
static Structure configure(RegistryAccess registryAccess, Structure source,
|
||||
IrisJigsawConfiguration configuration,
|
||||
boolean underground, int baseY) {
|
||||
Objects.requireNonNull(source, "Native structure source must not be null");
|
||||
if (!(source instanceof JigsawStructure sourceJigsaw)) {
|
||||
if (configuration != null) {
|
||||
throw new IllegalStateException("Jigsaw options require a registered jigsaw structure, found "
|
||||
+ source.getClass().getName());
|
||||
}
|
||||
return source;
|
||||
}
|
||||
RegistryOps<Tag> registryOps = RegistryOps.create(NbtOps.INSTANCE, registryAccess);
|
||||
Tag encoded = Structure.DIRECT_CODEC.encodeStart(registryOps, sourceJigsaw).getOrThrow();
|
||||
if (!(encoded instanceof CompoundTag structureTag)) {
|
||||
throw new IllegalStateException("Native jigsaw codec did not produce a compound");
|
||||
}
|
||||
CompoundTag configuredTag = structureTag.copy();
|
||||
if (underground) {
|
||||
CompoundTag height = new CompoundTag();
|
||||
height.putInt("absolute", baseY);
|
||||
configuredTag.put("start_height", height);
|
||||
configuredTag.remove("project_start_to_heightmap");
|
||||
}
|
||||
applyConfiguration(configuredTag, configuration);
|
||||
Structure decoded = Structure.DIRECT_CODEC.parse(registryOps, configuredTag).getOrThrow();
|
||||
if (!(decoded instanceof JigsawStructure configured)) {
|
||||
throw new IllegalStateException("Configured native jigsaw codec produced "
|
||||
+ decoded.getClass().getName());
|
||||
}
|
||||
return configured;
|
||||
}
|
||||
|
||||
private static void applyConfiguration(CompoundTag tag, IrisJigsawConfiguration configuration) {
|
||||
if (configuration == null) {
|
||||
return;
|
||||
}
|
||||
if (configuration.getStartPool() != null && !configuration.getStartPool().isBlank()) {
|
||||
tag.putString("start_pool", configuration.getStartPool().trim());
|
||||
}
|
||||
if (configuration.getStartJigsawName() != null
|
||||
&& !configuration.getStartJigsawName().isBlank()) {
|
||||
if ("NONE".equalsIgnoreCase(configuration.getStartJigsawName().trim())) {
|
||||
tag.remove("start_jigsaw_name");
|
||||
} else {
|
||||
tag.putString("start_jigsaw_name", configuration.getStartJigsawName().trim());
|
||||
}
|
||||
}
|
||||
if (configuration.getMaxDepth() != null) {
|
||||
tag.putInt("size", configuration.getMaxDepth());
|
||||
}
|
||||
applyDistance(tag, configuration);
|
||||
if (configuration.getUseExpansionHack() != null) {
|
||||
tag.putBoolean("use_expansion_hack", configuration.getUseExpansionHack());
|
||||
}
|
||||
applyHeightmap(tag, configuration.getProjectStartToHeightmap());
|
||||
applyDimensionPadding(tag, configuration);
|
||||
applyLiquidSettings(tag, configuration.getLiquidSettings());
|
||||
}
|
||||
|
||||
private static void applyDistance(CompoundTag tag, IrisJigsawConfiguration configuration) {
|
||||
Integer horizontal = configuration.getMaxDistanceHorizontal();
|
||||
Integer vertical = configuration.getMaxDistanceVertical();
|
||||
if (horizontal == null && vertical == null) {
|
||||
return;
|
||||
}
|
||||
int resolvedHorizontal = horizontal == null ? sourceDistance(tag, "horizontal") : horizontal;
|
||||
int resolvedVertical = vertical == null ? sourceDistance(tag, "vertical") : vertical;
|
||||
if (resolvedHorizontal == resolvedVertical) {
|
||||
tag.putInt("max_distance_from_center", resolvedHorizontal);
|
||||
return;
|
||||
}
|
||||
CompoundTag distance = new CompoundTag();
|
||||
distance.putInt("horizontal", resolvedHorizontal);
|
||||
distance.putInt("vertical", resolvedVertical);
|
||||
tag.put("max_distance_from_center", distance);
|
||||
}
|
||||
|
||||
private static int sourceDistance(CompoundTag tag, String axis) {
|
||||
Tag raw = tag.get("max_distance_from_center");
|
||||
if (raw instanceof CompoundTag compound) {
|
||||
return compound.getIntOr(axis, 128);
|
||||
}
|
||||
return tag.getIntOr("max_distance_from_center", 128);
|
||||
}
|
||||
|
||||
private static void applyHeightmap(CompoundTag tag, IrisJigsawHeightmap heightmap) {
|
||||
if (heightmap == null || heightmap == IrisJigsawHeightmap.SOURCE) {
|
||||
return;
|
||||
}
|
||||
if (heightmap == IrisJigsawHeightmap.NONE) {
|
||||
tag.remove("project_start_to_heightmap");
|
||||
return;
|
||||
}
|
||||
tag.putString("project_start_to_heightmap", heightmap.name());
|
||||
}
|
||||
|
||||
private static void applyDimensionPadding(CompoundTag tag, IrisJigsawConfiguration configuration) {
|
||||
Integer bottom = configuration.getDimensionPaddingBottom();
|
||||
Integer top = configuration.getDimensionPaddingTop();
|
||||
if (bottom == null && top == null) {
|
||||
return;
|
||||
}
|
||||
int resolvedBottom = bottom == null ? sourcePadding(tag, "bottom") : bottom;
|
||||
int resolvedTop = top == null ? sourcePadding(tag, "top") : top;
|
||||
if (resolvedBottom == resolvedTop) {
|
||||
tag.putInt("dimension_padding", resolvedBottom);
|
||||
return;
|
||||
}
|
||||
CompoundTag padding = new CompoundTag();
|
||||
padding.putInt("bottom", resolvedBottom);
|
||||
padding.putInt("top", resolvedTop);
|
||||
tag.put("dimension_padding", padding);
|
||||
}
|
||||
|
||||
private static int sourcePadding(CompoundTag tag, String side) {
|
||||
Tag raw = tag.get("dimension_padding");
|
||||
if (raw instanceof CompoundTag compound) {
|
||||
return compound.getIntOr(side, 0);
|
||||
}
|
||||
return tag.getIntOr("dimension_padding", 0);
|
||||
}
|
||||
|
||||
private static void applyLiquidSettings(CompoundTag tag, IrisJigsawLiquidSettings settings) {
|
||||
if (settings == null || settings == IrisJigsawLiquidSettings.SOURCE) {
|
||||
return;
|
||||
}
|
||||
tag.putString("liquid_settings", settings.name().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
public record GenerationContext(
|
||||
RegistryAccess registryAccess,
|
||||
ChunkGenerator generator,
|
||||
BiomeSource biomeSource,
|
||||
RandomState randomState,
|
||||
StructureTemplateManager templateManager,
|
||||
long levelSeed,
|
||||
ResourceKey<Level> levelKey,
|
||||
LevelHeightAccessor heightAccessor,
|
||||
Predicate<Holder<Biome>> biomePredicate,
|
||||
int seaLevel,
|
||||
IntBinaryOperator surfaceHeight
|
||||
) {
|
||||
}
|
||||
}
|
||||
+639
-63
@@ -1,19 +1,33 @@
|
||||
package art.arcane.iris.nativegen;
|
||||
|
||||
import art.arcane.iris.engine.framework.StructureVerticalBounds;
|
||||
import art.arcane.iris.engine.mantle.components.StructureCarveEnvelope;
|
||||
import art.arcane.iris.engine.mantle.components.StructureCarvingFootprint;
|
||||
import art.arcane.iris.engine.object.IrisMaterialPalette;
|
||||
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
|
||||
import art.arcane.iris.engine.object.IrisObjectVacuum;
|
||||
import art.arcane.iris.engine.object.IrisStructureCarveShape;
|
||||
import art.arcane.iris.engine.object.IrisStructureStiltSettings;
|
||||
import art.arcane.iris.engine.object.IrisStructureTerrain;
|
||||
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
|
||||
import art.arcane.iris.engine.object.IrisStructureYBand;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.StructureManager;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
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.SupportType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
@@ -26,6 +40,8 @@ import net.minecraft.world.level.levelgen.structure.ScatteredFeaturePiece;
|
||||
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.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.JigsawJunction;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.LegacySinglePoolElement;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.ListPoolElement;
|
||||
@@ -44,21 +60,41 @@ import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.IntBinaryOperator;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class NativeStructurePostProcessor {
|
||||
private static final int AUTO_ENCASE_PADDING = 3;
|
||||
private static final long CARVE_CEILING_ROLL_SIGNATURE = 0x2A17L;
|
||||
private static final long CARVE_FLOOR_ROLL_SIGNATURE = 0x5B3DL;
|
||||
private static final long CARVE_LOBE_SIGNATURE = 0x7C41L;
|
||||
private static final String DESERT_PYRAMID_ID = "minecraft:desert_pyramid";
|
||||
private static final int FOUNDATION_VERTICAL_TOLERANCE = 1;
|
||||
private static final String JUNGLE_PYRAMID_ID = "minecraft:jungle_pyramid";
|
||||
private static final int MAX_BURIAL_COLUMNS = 2_000_000;
|
||||
private static final int MAX_CACHED_CARVE_FOOTPRINTS = 4;
|
||||
private static final int MAX_CARVE_COLUMNS = 2_000_000;
|
||||
private static final int MAX_TEMPLATE_OCCUPANCY_CELLS = 4_194_304;
|
||||
private static final int MONUMENT_BASE_BELOW_SEA_LEVEL = 24;
|
||||
private static final String OCEAN_MONUMENT_ID = "minecraft:monument";
|
||||
private static final double SURFACE_TERRAIN_FALLOFF = 2.0;
|
||||
private static final long SURFACE_TERRAIN_INFLUENCE_SCALE = 1_000_000L;
|
||||
private static final int SURFACE_TERRAIN_RADIUS = 12;
|
||||
private static final List<Block> TEMPLATE_VOID_BLOCKS = List.of(Blocks.AIR, Blocks.STRUCTURE_VOID);
|
||||
private static final int UNDERGROUND_SURFACE_CLEARANCE = 1;
|
||||
private static final Map<CarveFootprintKey, StructureCarvingFootprint> CARVE_FOOTPRINTS =
|
||||
Collections.synchronizedMap(new LinkedHashMap<>(16, 0.75F, true) {
|
||||
@Override
|
||||
protected boolean removeEldestEntry(
|
||||
Map.Entry<CarveFootprintKey, StructureCarvingFootprint> eldest) {
|
||||
return size() > MAX_CACHED_CARVE_FOOTPRINTS;
|
||||
}
|
||||
});
|
||||
|
||||
private NativeStructurePostProcessor() {
|
||||
}
|
||||
@@ -66,31 +102,456 @@ public final class NativeStructurePostProcessor {
|
||||
public static void place(WorldGenLevel world, StructureManager structureManager, ChunkGenerator generator,
|
||||
WorldgenRandom random, BoundingBox area, ChunkPos chunkPos, String structureId,
|
||||
StructureStart start, IrisNativeStructureDecision decision,
|
||||
StiltBlockResolver stiltBlockResolver,
|
||||
PaletteBlockResolver paletteBlockResolver,
|
||||
IntBinaryOperator surfaceHeight) {
|
||||
IrisStructureStiltSettings stilt = decision.stilt();
|
||||
ensureMonumentSeaLevelAlignment(start, structureId, decision.yShift(), generator.getSeaLevel(),
|
||||
area.minY(), area.maxY() + 1);
|
||||
start.placeInChunk(world, structureManager, generator, random, area, chunkPos);
|
||||
if (stilt != null) {
|
||||
placeStilts(world, area, structureId, start, stilt, stiltBlockResolver, surfaceHeight,
|
||||
placeStilts(world, area, structureId, start, stilt, paletteBlockResolver, surfaceHeight,
|
||||
!isUndergroundStep(start.getStructure().step()));
|
||||
}
|
||||
}
|
||||
|
||||
public static void prepareTerrain(WorldGenLevel world, BoundingBox area,
|
||||
List<TerrainTarget> targets,
|
||||
PaletteBlockResolver paletteBlockResolver) {
|
||||
if (targets == null || targets.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (TerrainTarget target : targets) {
|
||||
integrateTerrain(world, area, target.structureId(), target.start(), target.terrain(),
|
||||
paletteBlockResolver);
|
||||
}
|
||||
}
|
||||
|
||||
public static IrisStructureTerrain resolveNativeTerrain(StructureStart start,
|
||||
IrisStructureTerrain configuredTerrain) {
|
||||
if (configuredTerrain != null) {
|
||||
return configuredTerrain;
|
||||
}
|
||||
if (start == null || !start.isValid()
|
||||
|| !encasesTerrain(start.getStructure().terrainAdaptation())) {
|
||||
return null;
|
||||
}
|
||||
return new IrisStructureTerrain()
|
||||
.setMode(IrisStructureTerrainMode.ENCASE)
|
||||
.setHorizontalPadding(AUTO_ENCASE_PADDING)
|
||||
.setCeilingPadding(AUTO_ENCASE_PADDING)
|
||||
.setFloorPadding(AUTO_ENCASE_PADDING);
|
||||
}
|
||||
|
||||
static boolean encasesTerrain(TerrainAdjustment adjustment) {
|
||||
return adjustment == TerrainAdjustment.BURY || adjustment == TerrainAdjustment.ENCAPSULATE;
|
||||
}
|
||||
|
||||
static void integrateTerrain(WorldGenLevel world, BoundingBox area, String structureId,
|
||||
StructureStart start, IrisStructureTerrain configuredTerrain,
|
||||
PaletteBlockResolver paletteBlockResolver) {
|
||||
IrisStructureTerrain terrain = configuredTerrain == null
|
||||
? new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE)
|
||||
: configuredTerrain;
|
||||
IrisStructureTerrainMode mode = terrain.resolvedMode();
|
||||
if (mode == IrisStructureTerrainMode.SOURCE || mode == IrisStructureTerrainMode.PRESERVE) {
|
||||
return;
|
||||
}
|
||||
if (mode == IrisStructureTerrainMode.VACUUM) {
|
||||
carvePieceBoxes(world, area, start, terrain);
|
||||
return;
|
||||
}
|
||||
if (mode == IrisStructureTerrainMode.ENCASE) {
|
||||
encasePieces(world, area, structureId, start, terrain, paletteBlockResolver);
|
||||
return;
|
||||
}
|
||||
if (mode != IrisStructureTerrainMode.BORE && mode != IrisStructureTerrainMode.FORCE_CARVE) {
|
||||
throw new IllegalStateException("Native structure terrain mode " + mode
|
||||
+ " is not implemented for '" + structureId + "'");
|
||||
}
|
||||
IrisStructureCarveShape shape = mode == IrisStructureTerrainMode.BORE
|
||||
? IrisStructureCarveShape.BOX : terrain.resolvedShape();
|
||||
if (shape == IrisStructureCarveShape.BOX) {
|
||||
carvePieceBoxes(world, area, start, terrain);
|
||||
return;
|
||||
}
|
||||
carveOrganicColumns(world, area, organicCarve(
|
||||
carveFootprint(start, Math.max(0, terrain.getHorizontalPadding()),
|
||||
() -> world.getLevel().getStructureManager()),
|
||||
terrain, shape, carveNoiseIdentity(world, structureId, start)));
|
||||
}
|
||||
|
||||
static List<BoundingBox> contentPieceBounds(StructureStart start) {
|
||||
List<BoundingBox> bounds = new ArrayList<>(start.getPieces().size());
|
||||
for (StructurePiece piece : start.getPieces()) {
|
||||
if (!NativeStructureReferenceEnvelope.isMarker(piece)) {
|
||||
bounds.add(piece.getBoundingBox());
|
||||
}
|
||||
}
|
||||
return List.copyOf(bounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-column occupancy of the whole start, cached because a single structure spans many chunks and
|
||||
* every one of them carves against the same shrinkwrapped footprint.
|
||||
*/
|
||||
static StructureCarvingFootprint carveFootprint(StructureStart start, int horizontalPadding,
|
||||
Supplier<StructureTemplateManager> templates) {
|
||||
CarveFootprintKey key = new CarveFootprintKey(start, horizontalPadding);
|
||||
StructureCarvingFootprint cached = CARVE_FOOTPRINTS.get(key);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
StructureCarvingFootprint footprint = StructureCarvingFootprint.fromColumns(
|
||||
sink -> emitCarveColumns(start, templates, sink), horizontalPadding, MAX_CARVE_COLUMNS);
|
||||
if (footprint == null) {
|
||||
throw new IllegalStateException("Native structure carve footprint is empty or exceeds "
|
||||
+ MAX_CARVE_COLUMNS + " columns");
|
||||
}
|
||||
CARVE_FOOTPRINTS.put(key, footprint);
|
||||
return footprint;
|
||||
}
|
||||
|
||||
static OrganicCarve organicCarve(StructureCarvingFootprint footprint, IrisStructureTerrain terrain,
|
||||
IrisStructureCarveShape shape, long identity) {
|
||||
double strength = terrain.resolvedErosionStrength();
|
||||
double lobeStrength = terrain.resolvedLobeStrength();
|
||||
RNG noiseRng = new RNG(identity);
|
||||
CNG blob = null;
|
||||
CNG ceilingRoll = null;
|
||||
CNG floorRoll = null;
|
||||
CNG lobe = null;
|
||||
if (shape == IrisStructureCarveShape.ERODED && strength > 0D) {
|
||||
blob = CNG.signature(noiseRng);
|
||||
ceilingRoll = CNG.signature(noiseRng.nextParallelRNG(CARVE_CEILING_ROLL_SIGNATURE));
|
||||
floorRoll = CNG.signature(noiseRng.nextParallelRNG(CARVE_FLOOR_ROLL_SIGNATURE));
|
||||
}
|
||||
if (shape == IrisStructureCarveShape.ERODED && lobeStrength > 0D) {
|
||||
// A plain single octave channel: the fractured signature noise has no usable low frequency band.
|
||||
lobe = new CNG(noiseRng.nextParallelRNG(CARVE_LOBE_SIGNATURE), 1D, 1);
|
||||
}
|
||||
return new OrganicCarve(footprint, shape,
|
||||
Math.max(0, terrain.getHorizontalPadding()),
|
||||
Math.max(0, terrain.getCeilingPadding()),
|
||||
Math.max(0, terrain.getFloorPadding()),
|
||||
strength, terrain.resolvedErosionFrequency(), blob, ceilingRoll, floorRoll,
|
||||
lobe, terrain.resolvedLobeFrequency(), lobeStrength);
|
||||
}
|
||||
|
||||
static void carveOrganicColumns(WorldGenLevel world, BoundingBox area, OrganicCarve carve) {
|
||||
StructureCarvingFootprint footprint = carve.footprint();
|
||||
int minX = Math.max(area.minX(), footprint.minX());
|
||||
int maxX = Math.min(area.maxX(), footprint.maxX());
|
||||
int minZ = Math.max(area.minZ(), footprint.minZ());
|
||||
int maxZ = Math.min(area.maxZ(), footprint.maxZ());
|
||||
if (minX > maxX || minZ > maxZ) {
|
||||
return;
|
||||
}
|
||||
boolean eroded = carve.blob() != null;
|
||||
BlockState air = Blocks.AIR.defaultBlockState();
|
||||
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
int index = footprint.indexAt(x, z);
|
||||
long horizontalDistanceSquared = footprint.distanceSquaredAt(index);
|
||||
double sideReach = StructureCarveEnvelope.lobedSideReach(carve.lobe(),
|
||||
carve.lobeFrequency(), carve.lobeStrength(), x, z, carve.horizontalPadding());
|
||||
double sideReachSquared = sideReach * sideReach;
|
||||
if (horizontalDistanceSquared > sideReachSquared) {
|
||||
continue;
|
||||
}
|
||||
double normalizedHorizontal = sideReachSquared == 0D
|
||||
? 0D : horizontalDistanceSquared / sideReachSquared;
|
||||
int sourceMinY = footprint.sourceMinYAt(index);
|
||||
int sourceMaxY = footprint.sourceMaxYAt(index);
|
||||
double upReach = eroded
|
||||
? StructureCarveEnvelope.lobedUpReach(carve.lobe(), carve.lobeFrequency(),
|
||||
carve.lobeStrength(), x, z,
|
||||
StructureCarveEnvelope.erodedUpReach(carve.ceilingRoll(),
|
||||
carve.frequency(), carve.strength(), x, z,
|
||||
carve.ceilingPadding()))
|
||||
: Math.max(1D, carve.ceilingPadding());
|
||||
double floorReach = eroded
|
||||
? StructureCarveEnvelope.erodedDownReach(carve.floorRoll(), carve.frequency(),
|
||||
carve.strength(), x, z, carve.floorPadding())
|
||||
: carve.floorPadding();
|
||||
int columnMinY = Math.max(area.minY(), sourceMinY - (int) Math.floor(floorReach));
|
||||
int columnMaxY = Math.min(area.maxY(),
|
||||
sourceMaxY + (eroded ? (int) Math.ceil(upReach) : carve.ceilingPadding()));
|
||||
double downReach = Math.max(1D, floorReach);
|
||||
for (int y = columnMinY; y <= columnMaxY; y++) {
|
||||
double normalizedVertical = StructureCarveEnvelope.normalizedVerticalDistance(
|
||||
y, sourceMinY, sourceMaxY, upReach, downReach);
|
||||
double distanceSquared = normalizedHorizontal
|
||||
+ normalizedVertical * normalizedVertical;
|
||||
if (distanceSquared > 1D) {
|
||||
continue;
|
||||
}
|
||||
if (distanceSquared > 0D && eroded) {
|
||||
double noise = carve.blob().fitDouble(0D, 1D,
|
||||
x * carve.frequency(), y * carve.frequency(), z * carve.frequency());
|
||||
if (!StructureCarveEnvelope.shouldCarveOverboreCell(
|
||||
carve.shape(), distanceSquared, noise, carve.strength())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
world.setBlock(position.set(x, y, z), air, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean emitCarveColumns(StructureStart start,
|
||||
Supplier<StructureTemplateManager> templates,
|
||||
StructureCarvingFootprint.ColumnSink sink) {
|
||||
for (StructurePiece piece : start.getPieces()) {
|
||||
if (NativeStructureReferenceEnvelope.isMarker(piece)) {
|
||||
continue;
|
||||
}
|
||||
BoundingBox bounds = piece.getBoundingBox();
|
||||
if (!(piece instanceof PoolElementStructurePiece poolPiece)
|
||||
|| !emitTemplateColumns(pieceTemplates(poolPiece, templates),
|
||||
poolPiece.getPosition(), poolPiece.getRotation(), bounds, sink)) {
|
||||
emitBoxColumns(bounds, sink);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives per-column vertical extents from the complement of the template's air and structure-void
|
||||
* cells, so a carve tracks the actual silhouette instead of the piece's rectangular bounding box.
|
||||
*/
|
||||
static boolean emitTemplateColumns(List<StructureTemplate> templates, BlockPos position,
|
||||
Rotation rotation, BoundingBox bounds,
|
||||
StructureCarvingFootprint.ColumnSink sink) {
|
||||
if (templates.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
int width = bounds.getXSpan();
|
||||
int depth = bounds.getZSpan();
|
||||
long cells = (long) width * depth * bounds.getYSpan();
|
||||
if (cells < 1L || cells > MAX_TEMPLATE_OCCUPANCY_CELLS) {
|
||||
return false;
|
||||
}
|
||||
StructurePlaceSettings settings = new StructurePlaceSettings().setRotation(rotation);
|
||||
boolean[] voidCells = null;
|
||||
for (StructureTemplate template : templates) {
|
||||
boolean[] templateVoid = new boolean[(int) cells];
|
||||
for (Block ignored : TEMPLATE_VOID_BLOCKS) {
|
||||
for (StructureTemplate.StructureBlockInfo info
|
||||
: template.filterBlocks(position, settings, ignored)) {
|
||||
BlockPos voidPosition = info.pos();
|
||||
if (bounds.isInside(voidPosition)) {
|
||||
templateVoid[templateCellIndex(bounds, width, depth,
|
||||
voidPosition.getX(), voidPosition.getY(), voidPosition.getZ())] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (voidCells == null) {
|
||||
voidCells = templateVoid;
|
||||
continue;
|
||||
}
|
||||
for (int cell = 0; cell < voidCells.length; cell++) {
|
||||
voidCells[cell] &= templateVoid[cell];
|
||||
}
|
||||
}
|
||||
for (int z = bounds.minZ(); z <= bounds.maxZ(); z++) {
|
||||
for (int x = bounds.minX(); x <= bounds.maxX(); x++) {
|
||||
int minY = Integer.MAX_VALUE;
|
||||
int maxY = Integer.MIN_VALUE;
|
||||
for (int y = bounds.minY(); y <= bounds.maxY(); y++) {
|
||||
if (voidCells[templateCellIndex(bounds, width, depth, x, y, z)]) {
|
||||
continue;
|
||||
}
|
||||
if (minY == Integer.MAX_VALUE) {
|
||||
minY = y;
|
||||
}
|
||||
maxY = y;
|
||||
}
|
||||
if (minY != Integer.MAX_VALUE) {
|
||||
sink.column(x, z, minY, maxY);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void emitBoxColumns(BoundingBox bounds, StructureCarvingFootprint.ColumnSink sink) {
|
||||
for (int z = bounds.minZ(); z <= bounds.maxZ(); z++) {
|
||||
for (int x = bounds.minX(); x <= bounds.maxX(); x++) {
|
||||
sink.column(x, z, bounds.minY(), bounds.maxY());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int templateCellIndex(BoundingBox bounds, int width, int depth,
|
||||
int x, int y, int z) {
|
||||
return ((y - bounds.minY()) * depth + z - bounds.minZ()) * width + x - bounds.minX();
|
||||
}
|
||||
|
||||
private static List<StructureTemplate> pieceTemplates(PoolElementStructurePiece poolPiece,
|
||||
Supplier<StructureTemplateManager> templates) {
|
||||
List<StructureTemplate> resolved = new ArrayList<>(1);
|
||||
collectElementTemplates(poolPiece.getElement(), templates, resolved);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private static void collectElementTemplates(StructurePoolElement element,
|
||||
Supplier<StructureTemplateManager> templates,
|
||||
List<StructureTemplate> resolved) {
|
||||
if (element instanceof ListPoolElement listElement) {
|
||||
for (StructurePoolElement child : listElement.getElements()) {
|
||||
collectElementTemplates(child, templates, resolved);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (element instanceof SinglePoolElement singleElement) {
|
||||
resolved.add(resolveTemplate(singleElement, templates));
|
||||
}
|
||||
}
|
||||
|
||||
private static long carveNoiseIdentity(WorldGenLevel world, String structureId,
|
||||
StructureStart start) {
|
||||
return world.getSeed()
|
||||
^ ((long) start.getChunkPos().x() * 341873128712L)
|
||||
^ ((long) start.getChunkPos().z() * 132897987541L)
|
||||
^ (structureId == null ? 0 : structureId.hashCode());
|
||||
}
|
||||
|
||||
private static void encasePieces(WorldGenLevel world, BoundingBox area, String structureId,
|
||||
StructureStart start, IrisStructureTerrain terrain,
|
||||
PaletteBlockResolver paletteBlockResolver) {
|
||||
IrisMaterialPalette palette = terrain.getEncasePalette();
|
||||
RNG rng = null;
|
||||
if (palette != null) {
|
||||
Objects.requireNonNull(paletteBlockResolver,
|
||||
"Native structure encase palette requires a platform block resolver");
|
||||
rng = new RNG(world.getSeed() ^ (structureId == null ? 0 : structureId.hashCode()));
|
||||
}
|
||||
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
|
||||
for (BoundingBox bounds : contentPieceBounds(start)) {
|
||||
BoundingBox shell = paddedPieceArea(area, bounds, terrain);
|
||||
if (shell == null) {
|
||||
continue;
|
||||
}
|
||||
for (int x = shell.minX(); x <= shell.maxX(); x++) {
|
||||
for (int z = shell.minZ(); z <= shell.maxZ(); z++) {
|
||||
for (int y = shell.minY(); y <= shell.maxY(); y++) {
|
||||
BlockState existing = world.getBlockState(position.set(x, y, z));
|
||||
if (!isEncaseable(existing)) {
|
||||
continue;
|
||||
}
|
||||
BlockState fill = palette == null
|
||||
? defaultEncaseBlock(y)
|
||||
: Objects.requireNonNull(
|
||||
paletteBlockResolver.resolve(palette, rng, x, y, z),
|
||||
"Encase palette returned no block for " + structureId + " at "
|
||||
+ x + "," + y + "," + z);
|
||||
world.setBlock(position, fill, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isEncaseable(BlockState state) {
|
||||
return state.isAir() || !state.getFluidState().isEmpty();
|
||||
}
|
||||
|
||||
static BlockState defaultEncaseBlock(int y) {
|
||||
return y < 0 ? Blocks.DEEPSLATE.defaultBlockState() : Blocks.STONE.defaultBlockState();
|
||||
}
|
||||
|
||||
private static void carvePieceBoxes(WorldGenLevel world, BoundingBox area, StructureStart start,
|
||||
IrisStructureTerrain terrain) {
|
||||
BlockState air = Blocks.AIR.defaultBlockState();
|
||||
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
|
||||
for (BoundingBox bounds : contentPieceBounds(start)) {
|
||||
BoundingBox carve = paddedPieceArea(area, bounds, terrain);
|
||||
if (carve == null) {
|
||||
continue;
|
||||
}
|
||||
for (int x = carve.minX(); x <= carve.maxX(); x++) {
|
||||
for (int z = carve.minZ(); z <= carve.maxZ(); z++) {
|
||||
for (int y = carve.minY(); y <= carve.maxY(); y++) {
|
||||
world.setBlock(position.set(x, y, z), air, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static BoundingBox paddedPieceArea(BoundingBox area, BoundingBox bounds,
|
||||
IrisStructureTerrain terrain) {
|
||||
int horizontalPadding = Math.max(0, terrain.getHorizontalPadding());
|
||||
int minX = Math.max(area.minX(), bounds.minX() - horizontalPadding);
|
||||
int minY = Math.max(area.minY(), bounds.minY() - Math.max(0, terrain.getFloorPadding()));
|
||||
int minZ = Math.max(area.minZ(), bounds.minZ() - horizontalPadding);
|
||||
int maxX = Math.min(area.maxX(), bounds.maxX() + horizontalPadding);
|
||||
int maxY = Math.min(area.maxY(), bounds.maxY() + Math.max(0, terrain.getCeilingPadding()));
|
||||
int maxZ = Math.min(area.maxZ(), bounds.maxZ() + horizontalPadding);
|
||||
if (minX > maxX || minY > maxY || minZ > maxZ) {
|
||||
return null;
|
||||
}
|
||||
return new BoundingBox(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
public static int applyVerticalPlacement(StructureStart start, String structureId, int requestedOffset,
|
||||
int seaLevel, int worldMinY, int worldMaxYExclusive,
|
||||
boolean underground, IntBinaryOperator surfaceHeight) {
|
||||
boolean underground, boolean preserveSourceY,
|
||||
IrisStructureYBand yBand,
|
||||
IntBinaryOperator surfaceHeight) {
|
||||
if (isOceanMonument(structureId)) {
|
||||
return alignOceanMonumentToSeaLevel(
|
||||
start, requestedOffset, seaLevel, worldMinY, worldMaxYExclusive);
|
||||
}
|
||||
if (isRaisedScatteredStructure(structureId)) {
|
||||
if (isAdjustedScatteredStructure(structureId)) {
|
||||
return alignScatteredStructureToSurface(
|
||||
start, structureId, requestedOffset, worldMinY, worldMaxYExclusive, surfaceHeight);
|
||||
}
|
||||
return applyVerticalShift(
|
||||
start, requestedOffset, worldMinY, worldMaxYExclusive, underground, surfaceHeight);
|
||||
return applyVerticalShift(start, requestedOffset, worldMinY, worldMaxYExclusive,
|
||||
underground, preserveSourceY, yBand, surfaceHeight);
|
||||
}
|
||||
|
||||
public static StructureStart relocateToMinY(StructureStart start, Structure source, int targetMinY,
|
||||
LevelHeightAccessor heightAccessor) {
|
||||
Objects.requireNonNull(start, "Native structure start must not be null");
|
||||
Objects.requireNonNull(source, "Native structure source must not be null");
|
||||
Objects.requireNonNull(heightAccessor, "Native structure height accessor must not be null");
|
||||
if (!start.isValid()) {
|
||||
return StructureStart.INVALID_START;
|
||||
}
|
||||
List<StructurePiece> pieces = start.getPieces();
|
||||
int minY = Integer.MAX_VALUE;
|
||||
for (StructurePiece piece : pieces) {
|
||||
minY = Math.min(minY, piece.getBoundingBox().minY());
|
||||
}
|
||||
if (minY == Integer.MAX_VALUE) {
|
||||
return StructureStart.INVALID_START;
|
||||
}
|
||||
int offsetY = Math.subtractExact(targetMinY, minY);
|
||||
if (offsetY != 0) {
|
||||
for (StructurePiece piece : pieces) {
|
||||
moveStructurePiece(piece, offsetY);
|
||||
}
|
||||
}
|
||||
int worldMinY = heightAccessor.getMinY() + 1;
|
||||
int worldMaxYExclusive = Math.addExact(
|
||||
heightAccessor.getMinY(), heightAccessor.getHeight());
|
||||
for (StructurePiece piece : pieces) {
|
||||
BoundingBox bounds = piece.getBoundingBox();
|
||||
if (bounds.minY() < worldMinY || bounds.maxY() >= worldMaxYExclusive) {
|
||||
throw new IllegalStateException("Native structure cannot fit target minimum Y "
|
||||
+ targetMinY + " inside world bounds [" + worldMinY + ","
|
||||
+ worldMaxYExclusive + ")");
|
||||
}
|
||||
}
|
||||
return new StructureStart(
|
||||
source,
|
||||
start.getChunkPos(),
|
||||
start.getReferences(),
|
||||
new PiecesContainer(List.copyOf(pieces))
|
||||
);
|
||||
}
|
||||
|
||||
static int alignScatteredStructureToSurface(StructureStart start, String structureId,
|
||||
@@ -98,10 +559,10 @@ public final class NativeStructurePostProcessor {
|
||||
int worldMaxYExclusive,
|
||||
IntBinaryOperator surfaceHeight) {
|
||||
Objects.requireNonNull(surfaceHeight, "Scattered native structure requires a terrain height resolver");
|
||||
ScatteredFeaturePiece piece = requireRaisedScatteredPiece(start, structureId);
|
||||
ScatteredFeaturePiece piece = requireAdjustedScatteredPiece(start, structureId);
|
||||
BoundingBox bounds = start.getBoundingBox();
|
||||
BoundingBox pieceBounds = piece.getBoundingBox();
|
||||
int surfaceY = highestSurfaceY(pieceBounds, surfaceHeight);
|
||||
int surfaceY = representativeScatteredSurfaceY(structureId, pieceBounds, surfaceHeight);
|
||||
int targetMinY = Math.addExact(Math.addExact(surfaceY, 1), configuredOffset);
|
||||
int requestedMove = Math.subtractExact(targetMinY, pieceBounds.minY());
|
||||
int offsetY = StructureVerticalBounds.clampOffset(
|
||||
@@ -115,16 +576,13 @@ public final class NativeStructurePostProcessor {
|
||||
|
||||
public static int applyVerticalShift(StructureStart start, int requestedOffset, int worldMinY,
|
||||
int worldMaxYExclusive, boolean underground,
|
||||
boolean preserveSourceY, IrisStructureYBand yBand,
|
||||
IntBinaryOperator surfaceHeight) {
|
||||
BoundingBox bounds = start.getBoundingBox();
|
||||
int resolvedOffset = underground
|
||||
? resolveBuriedOffset(bounds, requestedOffset, worldMinY, worldMaxYExclusive, surfaceHeight)
|
||||
: requestedOffset;
|
||||
int resolvedOffset = resolveShiftOffset(start, bounds, requestedOffset, worldMinY,
|
||||
worldMaxYExclusive, underground, preserveSourceY, yBand, surfaceHeight);
|
||||
int offsetY = StructureVerticalBounds.clampOffset(
|
||||
bounds.minY(), bounds.maxY(), resolvedOffset, worldMinY, worldMaxYExclusive);
|
||||
if (underground && offsetY > resolvedOffset) {
|
||||
throw new IllegalStateException("Underground native structure cannot fit below terrain and inside world bounds");
|
||||
}
|
||||
if (offsetY == 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -132,6 +590,23 @@ public final class NativeStructurePostProcessor {
|
||||
return offsetY;
|
||||
}
|
||||
|
||||
private static int resolveShiftOffset(StructureStart start, BoundingBox bounds, int requestedOffset,
|
||||
int worldMinY, int worldMaxYExclusive, boolean underground,
|
||||
boolean preserveSourceY, IrisStructureYBand yBand,
|
||||
IntBinaryOperator surfaceHeight) {
|
||||
if (preserveSourceY) {
|
||||
return requestedOffset;
|
||||
}
|
||||
if (yBand != null) {
|
||||
return resolveYBandOffset(bounds, yBand, start.getChunkPos());
|
||||
}
|
||||
if (underground) {
|
||||
return resolveBuriedOffset(
|
||||
bounds, requestedOffset, worldMinY, worldMaxYExclusive, surfaceHeight);
|
||||
}
|
||||
return requestedOffset;
|
||||
}
|
||||
|
||||
static int alignOceanMonumentToSeaLevel(StructureStart start, int configuredOffset, int seaLevel,
|
||||
int worldMinY, int worldMaxYExclusive) {
|
||||
OceanMonumentPieces.MonumentBuilding building = requireOceanMonumentBuilding(start);
|
||||
@@ -166,12 +641,12 @@ public final class NativeStructurePostProcessor {
|
||||
return OCEAN_MONUMENT_ID.equals(structureId);
|
||||
}
|
||||
|
||||
private static boolean isRaisedScatteredStructure(String structureId) {
|
||||
private static boolean isAdjustedScatteredStructure(String structureId) {
|
||||
return DESERT_PYRAMID_ID.equals(structureId) || JUNGLE_PYRAMID_ID.equals(structureId);
|
||||
}
|
||||
|
||||
private static ScatteredFeaturePiece requireRaisedScatteredPiece(StructureStart start,
|
||||
String structureId) {
|
||||
private static ScatteredFeaturePiece requireAdjustedScatteredPiece(StructureStart start,
|
||||
String structureId) {
|
||||
Objects.requireNonNull(start, "Scattered native structure start must not be null");
|
||||
List<StructurePiece> pieces = start.getPieces();
|
||||
if (pieces.size() != 1) {
|
||||
@@ -189,17 +664,43 @@ public final class NativeStructurePostProcessor {
|
||||
+ piece.getClass().getName());
|
||||
}
|
||||
|
||||
private static int highestSurfaceY(BoundingBox bounds, IntBinaryOperator surfaceHeight) {
|
||||
int highestY = Integer.MIN_VALUE;
|
||||
private static int representativeScatteredSurfaceY(String structureId, BoundingBox bounds,
|
||||
IntBinaryOperator surfaceHeight) {
|
||||
if (DESERT_PYRAMID_ID.equals(structureId)) {
|
||||
return lowestSurfaceY(bounds, surfaceHeight);
|
||||
}
|
||||
if (JUNGLE_PYRAMID_ID.equals(structureId)) {
|
||||
return averageSurfaceY(bounds, surfaceHeight);
|
||||
}
|
||||
throw new IllegalStateException("Unsupported scattered native structure " + structureId);
|
||||
}
|
||||
|
||||
private static int lowestSurfaceY(BoundingBox bounds, IntBinaryOperator surfaceHeight) {
|
||||
int lowestY = Integer.MAX_VALUE;
|
||||
for (int x = bounds.minX(); x <= bounds.maxX(); x++) {
|
||||
for (int z = bounds.minZ(); z <= bounds.maxZ(); z++) {
|
||||
highestY = Math.max(highestY, surfaceHeight.applyAsInt(x, z));
|
||||
lowestY = Math.min(lowestY, surfaceHeight.applyAsInt(x, z));
|
||||
}
|
||||
}
|
||||
if (highestY == Integer.MIN_VALUE) {
|
||||
if (lowestY == Integer.MAX_VALUE) {
|
||||
throw new IllegalStateException("Scattered native structure has an empty terrain footprint");
|
||||
}
|
||||
return highestY;
|
||||
return lowestY;
|
||||
}
|
||||
|
||||
private static int averageSurfaceY(BoundingBox bounds, IntBinaryOperator surfaceHeight) {
|
||||
long totalY = 0L;
|
||||
long columns = 0L;
|
||||
for (int x = bounds.minX(); x <= bounds.maxX(); x++) {
|
||||
for (int z = bounds.minZ(); z <= bounds.maxZ(); z++) {
|
||||
totalY += surfaceHeight.applyAsInt(x, z);
|
||||
columns++;
|
||||
}
|
||||
}
|
||||
if (columns == 0L) {
|
||||
throw new IllegalStateException("Scattered native structure has an empty terrain footprint");
|
||||
}
|
||||
return Math.toIntExact(totalY / columns);
|
||||
}
|
||||
|
||||
private static void setScatteredHeightPosition(ScatteredFeaturePiece piece, int heightPosition) {
|
||||
@@ -315,6 +816,34 @@ public final class NativeStructurePostProcessor {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
static int resolveYBandOffset(BoundingBox bounds, IrisStructureYBand yBand, ChunkPos startChunk) {
|
||||
Objects.requireNonNull(bounds, "Native structure bounds must not be null");
|
||||
Objects.requireNonNull(startChunk, "Native structure Y band requires a start chunk");
|
||||
int target = yBandTargetMidpointY(
|
||||
bounds.minY(), bounds.maxY(), yBand.resolvedMin(), yBand.resolvedMax(), startChunk);
|
||||
return Math.subtractExact(target, midpointY(bounds.minY(), bounds.maxY()));
|
||||
}
|
||||
|
||||
static int yBandTargetMidpointY(int minY, int maxY, int bandMin, int bandMax, ChunkPos startChunk) {
|
||||
int height = Math.subtractExact(maxY, minY);
|
||||
int lowest = Math.addExact(bandMin, height / 2);
|
||||
int highest = Math.subtractExact(bandMax, height - height / 2);
|
||||
if (lowest > highest) {
|
||||
// The band is shorter than the structure, so only its midpoint can be honoured.
|
||||
return Math.floorDiv(Math.addExact(bandMin, bandMax), 2);
|
||||
}
|
||||
int span = highest - lowest + 1;
|
||||
if (span == 1) {
|
||||
return lowest;
|
||||
}
|
||||
long identity = (long) startChunk.x() * 341873128712L ^ (long) startChunk.z() * 132897987541L;
|
||||
return lowest + new RNG(identity).nextInt(span);
|
||||
}
|
||||
|
||||
private static int midpointY(int minY, int maxY) {
|
||||
return minY + (maxY - minY) / 2;
|
||||
}
|
||||
|
||||
static int resolveBuriedOffset(BoundingBox bounds, int requestedOffset, int worldMinY,
|
||||
int worldMaxYExclusive, IntBinaryOperator surfaceHeight) {
|
||||
Objects.requireNonNull(bounds, "Native structure bounds must not be null");
|
||||
@@ -335,7 +864,8 @@ public final class NativeStructurePostProcessor {
|
||||
int clampedOffset = StructureVerticalBounds.clampOffset(
|
||||
bounds.minY(), bounds.maxY(), maximumOffset, worldMinY, worldMaxYExclusive);
|
||||
if (clampedOffset > maximumOffset) {
|
||||
throw new IllegalStateException("Underground native structure cannot be buried without crossing world minimum Y");
|
||||
IrisLogging.warn("Native structure burial at " + bounds.minX() + "," + bounds.minZ()
|
||||
+ " clamped to world floor: wanted " + maximumOffset + ", used " + clampedOffset);
|
||||
}
|
||||
return clampedOffset;
|
||||
}
|
||||
@@ -362,10 +892,10 @@ public final class NativeStructurePostProcessor {
|
||||
if (!anchors.isEmpty()) {
|
||||
fitSurfaceTerrain(world, area, anchors, surfaceHeight);
|
||||
}
|
||||
StructureTemplateManager templateManager = world.getLevel().getStructureManager();
|
||||
Supplier<StructureTemplateManager> templates = () -> world.getLevel().getStructureManager();
|
||||
for (StructureStart start : starts) {
|
||||
if (requiresSurfaceTerrain(start)) {
|
||||
clearLegacyTemplateAir(world, area, start, templateManager);
|
||||
clearLegacyTemplateAir(world, area, start, templates);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -454,6 +984,9 @@ public final class NativeStructurePostProcessor {
|
||||
continue;
|
||||
}
|
||||
for (StructurePiece piece : start.getPieces()) {
|
||||
if (NativeStructureReferenceEnvelope.isMarker(piece)) {
|
||||
continue;
|
||||
}
|
||||
if (piece instanceof PoolElementStructurePiece poolPiece) {
|
||||
if (poolPiece.getElement().getProjection() == StructureTemplatePool.Projection.RIGID) {
|
||||
BoundingBox bounds = poolPiece.getBoundingBox();
|
||||
@@ -481,18 +1014,8 @@ public final class NativeStructurePostProcessor {
|
||||
static boolean requiresSurfaceTerrain(StructureStart start) {
|
||||
return start != null
|
||||
&& start.isValid()
|
||||
&& (shouldPrepareSurfaceTerrain(
|
||||
start.getStructure().terrainAdaptation(), start.getStructure().step())
|
||||
|| containsRaisedScatteredPiece(start));
|
||||
}
|
||||
|
||||
private static boolean containsRaisedScatteredPiece(StructureStart start) {
|
||||
for (StructurePiece piece : start.getPieces()) {
|
||||
if (piece instanceof DesertPyramidPiece || piece instanceof JungleTemplePiece) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
&& shouldPrepareSurfaceTerrain(
|
||||
start.getStructure().terrainAdaptation(), start.getStructure().step());
|
||||
}
|
||||
|
||||
private static void fitSurfaceTerrain(WorldGenLevel world, BoundingBox area,
|
||||
@@ -602,8 +1125,11 @@ public final class NativeStructurePostProcessor {
|
||||
|
||||
private static void clearLegacyTemplateAir(WorldGenLevel world, BoundingBox area,
|
||||
StructureStart start,
|
||||
StructureTemplateManager templateManager) {
|
||||
Supplier<StructureTemplateManager> templates) {
|
||||
for (StructurePiece piece : start.getPieces()) {
|
||||
if (NativeStructureReferenceEnvelope.isMarker(piece)) {
|
||||
continue;
|
||||
}
|
||||
if (!(piece instanceof PoolElementStructurePiece poolPiece)
|
||||
|| poolPiece.getElement().getProjection() != StructureTemplatePool.Projection.RIGID
|
||||
|| !intersects(poolPiece.getBoundingBox(), area)) {
|
||||
@@ -614,24 +1140,24 @@ public final class NativeStructurePostProcessor {
|
||||
.setRotation(poolPiece.getRotation())
|
||||
.setBoundingBox(area);
|
||||
clearLegacyTemplateAir(world, poolPiece.getElement(), poolPiece.getPosition(),
|
||||
groundY, settings, templateManager);
|
||||
groundY, settings, templates);
|
||||
}
|
||||
}
|
||||
|
||||
private static void clearLegacyTemplateAir(WorldGenLevel world, StructurePoolElement element,
|
||||
BlockPos position, int groundY,
|
||||
StructurePlaceSettings settings,
|
||||
StructureTemplateManager templateManager) {
|
||||
Supplier<StructureTemplateManager> templates) {
|
||||
if (element instanceof ListPoolElement listElement) {
|
||||
for (StructurePoolElement child : listElement.getElements()) {
|
||||
clearLegacyTemplateAir(world, child, position, groundY, settings, templateManager);
|
||||
clearLegacyTemplateAir(world, child, position, groundY, settings, templates);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!(element instanceof LegacySinglePoolElement legacyElement)) {
|
||||
return;
|
||||
}
|
||||
StructureTemplate template = resolveTemplate(legacyElement, templateManager);
|
||||
StructureTemplate template = resolveTemplate(legacyElement, templates);
|
||||
clearTemplateAir(world, template, position, groundY, settings);
|
||||
}
|
||||
|
||||
@@ -663,7 +1189,7 @@ public final class NativeStructurePostProcessor {
|
||||
}
|
||||
|
||||
private static StructureTemplate resolveTemplate(SinglePoolElement element,
|
||||
StructureTemplateManager templateManager) {
|
||||
Supplier<StructureTemplateManager> templates) {
|
||||
Object value;
|
||||
try {
|
||||
value = SinglePoolTemplateAccess.FIELD.get(element);
|
||||
@@ -673,23 +1199,24 @@ public final class NativeStructurePostProcessor {
|
||||
if (!(value instanceof Either<?, ?> reference)) {
|
||||
throw new IllegalStateException("Native structure pool template field is not an Either");
|
||||
}
|
||||
return resolveTemplateReference(reference, templateManager);
|
||||
return resolveTemplateReference(reference, templates);
|
||||
}
|
||||
|
||||
static StructureTemplate resolveTemplateReference(Either<?, ?> reference,
|
||||
StructureTemplateManager templateManager) {
|
||||
Supplier<StructureTemplateManager> templates) {
|
||||
return reference.map(
|
||||
location -> resolveNamedTemplate(location, templateManager),
|
||||
location -> resolveNamedTemplate(location, templates),
|
||||
NativeStructurePostProcessor::requireRuntimeTemplate);
|
||||
}
|
||||
|
||||
private static StructureTemplate resolveNamedTemplate(Object value,
|
||||
StructureTemplateManager templateManager) {
|
||||
Supplier<StructureTemplateManager> templates) {
|
||||
if (!(value instanceof Identifier identifier)) {
|
||||
throw new IllegalStateException("Native structure pool template identifier is "
|
||||
+ (value == null ? "null" : value.getClass().getName()));
|
||||
}
|
||||
return templateManager.getOrCreate(identifier);
|
||||
return Objects.requireNonNull(templates == null ? null : templates.get(),
|
||||
"Native structure template manager is unavailable").getOrCreate(identifier);
|
||||
}
|
||||
|
||||
private static StructureTemplate requireRuntimeTemplate(Object value) {
|
||||
@@ -790,6 +1317,9 @@ public final class NativeStructurePostProcessor {
|
||||
int[] pieceTops = new int[clearColumns.length];
|
||||
Arrays.fill(pieceTops, Integer.MIN_VALUE);
|
||||
for (StructurePiece piece : target.start().getPieces()) {
|
||||
if (NativeStructureReferenceEnvelope.isMarker(piece)) {
|
||||
continue;
|
||||
}
|
||||
BoundingBox bounds = piece.getBoundingBox();
|
||||
int minX = Math.max(area.minX(), bounds.minX());
|
||||
int maxX = Math.min(area.maxX(), bounds.maxX());
|
||||
@@ -854,7 +1384,7 @@ public final class NativeStructurePostProcessor {
|
||||
}
|
||||
|
||||
private static List<FoundationColumn> foundationEnvelope(BoundingBox area, StructureStart start) {
|
||||
BoundingBox structure = start.getBoundingBox();
|
||||
BoundingBox structure = NativeStructureReferenceEnvelope.contentBounds(start);
|
||||
int minX = Math.max(area.minX(), structure.minX());
|
||||
int minZ = Math.max(area.minZ(), structure.minZ());
|
||||
int maxX = Math.min(area.maxX(), structure.maxX());
|
||||
@@ -887,6 +1417,9 @@ public final class NativeStructurePostProcessor {
|
||||
private static void markFoundationEnvelope(BitSet envelope, List<StructurePiece> pieces, BoundingBox area,
|
||||
int x, int z) {
|
||||
for (StructurePiece piece : pieces) {
|
||||
if (NativeStructureReferenceEnvelope.isMarker(piece)) {
|
||||
continue;
|
||||
}
|
||||
BoundingBox bounds = piece.getBoundingBox();
|
||||
if (x < bounds.minX() || x > bounds.maxX() || z < bounds.minZ() || z > bounds.maxZ()) {
|
||||
continue;
|
||||
@@ -908,7 +1441,7 @@ public final class NativeStructurePostProcessor {
|
||||
|
||||
private static void placeStilts(WorldGenLevel world, BoundingBox area, String structureId,
|
||||
StructureStart start, IrisStructureStiltSettings settings,
|
||||
StiltBlockResolver stiltBlockResolver,
|
||||
PaletteBlockResolver paletteBlockResolver,
|
||||
IntBinaryOperator surfaceHeight,
|
||||
boolean surfaceStructure) {
|
||||
Objects.requireNonNull(surfaceHeight, "Structure stilts require an Iris terrain height resolver");
|
||||
@@ -916,6 +1449,9 @@ public final class NativeStructurePostProcessor {
|
||||
int structureHash = structureId == null ? 0 : structureId.hashCode();
|
||||
RNG rng = new RNG(world.getSeed() ^ structureHash);
|
||||
for (FoundationColumn column : foundationEnvelope(area, start)) {
|
||||
if (!isStiltColumn(column.x(), column.z(), settings.getSpacing())) {
|
||||
continue;
|
||||
}
|
||||
int foundationY = findFoundationY(world, column, position);
|
||||
if (foundationY == Integer.MIN_VALUE) {
|
||||
continue;
|
||||
@@ -924,23 +1460,49 @@ public final class NativeStructurePostProcessor {
|
||||
? Math.max(area.minY(), Math.min(
|
||||
area.maxY(), surfaceHeight.applyAsInt(column.x(), column.z())))
|
||||
: area.minY() - 1;
|
||||
for (int depth = 0, y = foundationY - 1;
|
||||
depth < settings.getMaxDepth() && y > terrainY; depth++, y--) {
|
||||
int anchorY = findStiltAnchorY(
|
||||
world, column.x(), column.z(), foundationY,
|
||||
Math.max(1, settings.getMaxDepth()), terrainY, area.minY(), position);
|
||||
if (anchorY == Integer.MIN_VALUE) {
|
||||
continue;
|
||||
}
|
||||
for (int y = foundationY - 1; y > anchorY; y--) {
|
||||
position.set(column.x(), y, column.z());
|
||||
BlockState existingState = world.getBlockState(position);
|
||||
boolean vegetation = existingState.is(BlockTags.LOGS) || existingState.is(BlockTags.LEAVES);
|
||||
if (existingState.isSolid() && !vegetation) {
|
||||
break;
|
||||
}
|
||||
BlockState stilt = Objects.requireNonNull(
|
||||
stiltBlockResolver.resolve(settings, rng, column.x(), y, column.z()),
|
||||
"Stilt palette returned no block for " + structureId + " at "
|
||||
+ column.x() + "," + y + "," + column.z());
|
||||
BlockState stilt = settings.getPalette() == null
|
||||
? Blocks.COBBLESTONE.defaultBlockState()
|
||||
: Objects.requireNonNull(
|
||||
paletteBlockResolver.resolve(
|
||||
settings.getPalette(), rng, column.x(), y, column.z()),
|
||||
"Stilt palette returned no block for " + structureId + " at "
|
||||
+ column.x() + "," + y + "," + column.z());
|
||||
world.setBlock(position, stilt, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isStiltColumn(int x, int z, int spacing) {
|
||||
int resolvedSpacing = Math.max(1, spacing);
|
||||
return resolvedSpacing == 1
|
||||
|| Math.floorMod(x, resolvedSpacing) == 0
|
||||
&& Math.floorMod(z, resolvedSpacing) == 0;
|
||||
}
|
||||
|
||||
static int findStiltAnchorY(
|
||||
WorldGenLevel world, int x, int z, int foundationY, int maxDepth,
|
||||
int terrainY, int areaMinY, BlockPos.MutableBlockPos position) {
|
||||
int minimumAnchorY = Math.max(
|
||||
areaMinY, Math.max(terrainY, foundationY - maxDepth - 1));
|
||||
for (int y = foundationY - 1; y >= minimumAnchorY; y--) {
|
||||
BlockState state = world.getBlockState(position.set(x, y, z));
|
||||
boolean vegetation = state.is(BlockTags.LOGS) || state.is(BlockTags.LEAVES);
|
||||
if (!vegetation && state.isFaceSturdy(
|
||||
world, position, Direction.UP, SupportType.FULL)) {
|
||||
return y;
|
||||
}
|
||||
}
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
public static StiltSupportAudit auditStiltSupport(WorldGenLevel world, BoundingBox area,
|
||||
StructureStart start, BlockState expectedStilt,
|
||||
IntBinaryOperator surfaceHeight) {
|
||||
@@ -1002,16 +1564,30 @@ public final class NativeStructurePostProcessor {
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface StiltBlockResolver {
|
||||
BlockState resolve(IrisStructureStiltSettings settings, RNG rng, int x, int y, int z);
|
||||
public interface PaletteBlockResolver {
|
||||
BlockState resolve(IrisMaterialPalette palette, RNG rng, int x, int y, int z);
|
||||
}
|
||||
|
||||
private record FoundationColumn(int x, int z, int[] ys) {
|
||||
}
|
||||
|
||||
// StructureStart has no value equality, so the key pins the exact start instance a chunk carves against.
|
||||
private record CarveFootprintKey(StructureStart start, int padding) {
|
||||
}
|
||||
|
||||
record OrganicCarve(StructureCarvingFootprint footprint, IrisStructureCarveShape shape,
|
||||
int horizontalPadding, int ceilingPadding, int floorPadding,
|
||||
double strength, double frequency, CNG blob, CNG ceilingRoll, CNG floorRoll,
|
||||
CNG lobe, double lobeFrequency, double lobeStrength) {
|
||||
}
|
||||
|
||||
public record VegetationTarget(StructureStart start, boolean force) {
|
||||
}
|
||||
|
||||
public record TerrainTarget(String structureId, StructureStart start,
|
||||
IrisStructureTerrain terrain) {
|
||||
}
|
||||
|
||||
public record StiltSupportAudit(int baseColumns, int stiltBlocks, int stiltColumns,
|
||||
int unsupportedColumns) {
|
||||
}
|
||||
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
package art.arcane.iris.nativegen;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisStructureTerrain;
|
||||
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.block.Rotation;
|
||||
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.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.templatesystem.BlockIgnoreProcessor;
|
||||
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.StructureTemplateManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class NativeStructureReferenceEnvelope {
|
||||
private static final int MARKER_GROUND_LEVEL_DELTA = Integer.MIN_VALUE;
|
||||
private static final int MAX_REFERENCE_DISTANCE_CHUNKS = 8;
|
||||
private static final StructurePoolElement MARKER_ELEMENT = StructurePoolElement.single(
|
||||
"minecraft:empty",
|
||||
Holder.direct(new StructureProcessorList(List.of(
|
||||
BlockIgnoreProcessor.STRUCTURE_AND_AIR))),
|
||||
LiquidSettings.APPLY_WATERLOGGING
|
||||
).apply(StructureTemplatePool.Projection.RIGID);
|
||||
|
||||
private NativeStructureReferenceEnvelope() {
|
||||
}
|
||||
|
||||
public static StructureStart wrap(StructureStart generated, Structure source, int references,
|
||||
StructureTemplateManager templateManager,
|
||||
IrisStructureTerrain terrain) {
|
||||
List<StructurePiece> pieces = new ArrayList<>(generated.getPieces());
|
||||
IrisStructureTerrainMode mode = terrain == null
|
||||
? IrisStructureTerrainMode.PRESERVE : terrain.resolvedMode();
|
||||
boolean usesEnvelope = mode == IrisStructureTerrainMode.BORE
|
||||
|| mode == IrisStructureTerrainMode.FORCE_CARVE
|
||||
|| mode == IrisStructureTerrainMode.VACUUM
|
||||
|| mode == IrisStructureTerrainMode.ENCASE;
|
||||
int horizontalPadding = usesEnvelope ? Math.max(0, terrain.getHorizontalPadding()) : 0;
|
||||
if (horizontalPadding > 0) {
|
||||
BoundingBox content = contentBounds(pieces);
|
||||
BoundingBox envelope = new BoundingBox(
|
||||
Math.subtractExact(content.minX(), horizontalPadding),
|
||||
content.minY(),
|
||||
Math.subtractExact(content.minZ(), horizontalPadding),
|
||||
Math.addExact(content.maxX(), horizontalPadding),
|
||||
content.maxY(),
|
||||
Math.addExact(content.maxZ(), horizontalPadding)
|
||||
);
|
||||
BoundingBox referencedEnvelope = clampReferenceRange(generated.getChunkPos(), envelope);
|
||||
if (!sameHorizontalBounds(envelope, referencedEnvelope)) {
|
||||
IrisLogging.warn("Native structure terrain envelope at "
|
||||
+ generated.getChunkPos().x() + "," + generated.getChunkPos().z()
|
||||
+ " was clipped to Minecraft's " + MAX_REFERENCE_DISTANCE_CHUNKS
|
||||
+ "-chunk structure reference range");
|
||||
}
|
||||
pieces.add(marker(templateManager, referencedEnvelope.minX(),
|
||||
referencedEnvelope.minY(), referencedEnvelope.minZ()));
|
||||
pieces.add(marker(templateManager, referencedEnvelope.maxX(),
|
||||
referencedEnvelope.maxY(), referencedEnvelope.maxZ()));
|
||||
}
|
||||
return new StructureStart(
|
||||
source,
|
||||
generated.getChunkPos(),
|
||||
references,
|
||||
new PiecesContainer(List.copyOf(pieces))
|
||||
);
|
||||
}
|
||||
|
||||
public static BoundingBox contentBounds(StructureStart start) {
|
||||
return contentBounds(start.getPieces());
|
||||
}
|
||||
|
||||
public static boolean isMarker(StructurePiece piece) {
|
||||
return piece instanceof PoolElementStructurePiece poolPiece
|
||||
&& poolPiece.getGroundLevelDelta() == MARKER_GROUND_LEVEL_DELTA;
|
||||
}
|
||||
|
||||
private static BoundingBox contentBounds(List<StructurePiece> pieces) {
|
||||
BoundingBox bounds = null;
|
||||
for (StructurePiece piece : pieces) {
|
||||
if (isMarker(piece)) {
|
||||
continue;
|
||||
}
|
||||
bounds = bounds == null
|
||||
? copy(piece.getBoundingBox())
|
||||
: bounds.encapsulate(piece.getBoundingBox());
|
||||
}
|
||||
if (bounds == null) {
|
||||
throw new IllegalStateException("Native jigsaw generated no content pieces");
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
|
||||
private static PoolElementStructurePiece marker(StructureTemplateManager templateManager,
|
||||
int x, int y, int z) {
|
||||
BlockPos position = new BlockPos(x, y, z);
|
||||
return new PoolElementStructurePiece(
|
||||
templateManager,
|
||||
MARKER_ELEMENT,
|
||||
position,
|
||||
MARKER_GROUND_LEVEL_DELTA,
|
||||
Rotation.NONE,
|
||||
new BoundingBox(position),
|
||||
LiquidSettings.APPLY_WATERLOGGING
|
||||
);
|
||||
}
|
||||
|
||||
private static BoundingBox clampReferenceRange(ChunkPos startChunk, BoundingBox envelope) {
|
||||
int minX = (startChunk.x() - MAX_REFERENCE_DISTANCE_CHUNKS) << 4;
|
||||
int maxX = ((startChunk.x() + MAX_REFERENCE_DISTANCE_CHUNKS) << 4) + 15;
|
||||
int minZ = (startChunk.z() - MAX_REFERENCE_DISTANCE_CHUNKS) << 4;
|
||||
int maxZ = ((startChunk.z() + MAX_REFERENCE_DISTANCE_CHUNKS) << 4) + 15;
|
||||
return new BoundingBox(
|
||||
Math.max(envelope.minX(), minX),
|
||||
envelope.minY(),
|
||||
Math.max(envelope.minZ(), minZ),
|
||||
Math.min(envelope.maxX(), maxX),
|
||||
envelope.maxY(),
|
||||
Math.min(envelope.maxZ(), maxZ)
|
||||
);
|
||||
}
|
||||
|
||||
private static boolean sameHorizontalBounds(BoundingBox left, BoundingBox right) {
|
||||
return left.minX() == right.minX()
|
||||
&& left.minZ() == right.minZ()
|
||||
&& left.maxX() == right.maxX()
|
||||
&& left.maxZ() == right.maxZ();
|
||||
}
|
||||
|
||||
private static BoundingBox copy(BoundingBox bounds) {
|
||||
return new BoundingBox(
|
||||
bounds.minX(), bounds.minY(), bounds.minZ(),
|
||||
bounds.maxX(), bounds.maxY(), bounds.maxZ());
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package art.arcane.iris.nativegen;
|
||||
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.NativeStructurePlacementPlanner;
|
||||
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
|
||||
import art.arcane.iris.engine.object.NativeStructureSuppression;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.StructureManager;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class NativeStructureStartInjector {
|
||||
private NativeStructureStartInjector() {
|
||||
}
|
||||
|
||||
public static Map<Structure, NativeStructureStartPlan> inject(InjectionContext context) {
|
||||
Objects.requireNonNull(context, "Native structure injection context must not be null");
|
||||
ChunkAccess chunk = context.chunk();
|
||||
Registry<Structure> registry = context.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||
SectionPos section = SectionPos.bottomOf(chunk);
|
||||
Map<Structure, NativeStructureStartPlan> configuredStarts = new LinkedHashMap<>();
|
||||
for (NativeStructureStartPlan plan : NativeStructurePlacementPlanner.plansAt(
|
||||
context.engine(), chunk.getPos().x(), chunk.getPos().z())) {
|
||||
Identifier identifier = Identifier.tryParse(plan.source().getStructure());
|
||||
if (identifier == null) {
|
||||
throw new IllegalStateException("Configured native structure key is invalid: "
|
||||
+ plan.source().getStructure());
|
||||
}
|
||||
Structure structure = registry.getValue(identifier);
|
||||
if (structure == null) {
|
||||
throw new IllegalStateException("Configured native structure is not registered: " + identifier);
|
||||
}
|
||||
Holder<Structure> holder = registry.wrapAsHolder(structure);
|
||||
if (configuredStarts.containsKey(structure)) {
|
||||
throw new IllegalStateException("Multiple configured native structure placements selected '"
|
||||
+ identifier + "' in chunk " + chunk.getPos().x() + "," + chunk.getPos().z()
|
||||
+ "; Minecraft can persist only one start per registered structure per chunk");
|
||||
}
|
||||
StructureStart existing = context.structureManager().getStartForStructure(
|
||||
section, structure, chunk);
|
||||
boolean replacement = plan.placement().getNativeSuppression()
|
||||
== NativeStructureSuppression.REPLACE_SOURCE;
|
||||
if (!replacement && existing != null && existing.isValid()) {
|
||||
continue;
|
||||
}
|
||||
int references = existing != null && existing.isValid() ? existing.getReferences() : 0;
|
||||
NativeStructureFactory.GenerationContext generationContext =
|
||||
new NativeStructureFactory.GenerationContext(
|
||||
context.registryAccess(),
|
||||
context.generator(),
|
||||
context.biomeSource(),
|
||||
context.structureState().randomState(),
|
||||
context.templateManager(),
|
||||
context.structureState().getLevelSeed(),
|
||||
context.levelKey(),
|
||||
chunk,
|
||||
biome -> true,
|
||||
context.generator().getSeaLevel(),
|
||||
(x, z) -> context.engine().getHeight(x, z, true)
|
||||
+ context.engine().getMinHeight()
|
||||
);
|
||||
StructureStart generated = NativeStructureFactory.generate(
|
||||
generationContext, holder, plan, references);
|
||||
if (!generated.isValid()) {
|
||||
throw new IllegalStateException("Configured native structure '" + identifier
|
||||
+ "' produced no valid start in chunk " + chunk.getPos().x()
|
||||
+ "," + chunk.getPos().z());
|
||||
}
|
||||
context.structureManager().setStartForStructure(
|
||||
section, structure, generated, chunk);
|
||||
configuredStarts.put(structure, plan);
|
||||
}
|
||||
return Map.copyOf(configuredStarts);
|
||||
}
|
||||
|
||||
public record InjectionContext(
|
||||
Engine engine,
|
||||
RegistryAccess registryAccess,
|
||||
ChunkGeneratorStructureState structureState,
|
||||
StructureManager structureManager,
|
||||
ChunkAccess chunk,
|
||||
StructureTemplateManager templateManager,
|
||||
ResourceKey<Level> levelKey,
|
||||
ChunkGenerator generator,
|
||||
BiomeSource biomeSource
|
||||
) {
|
||||
}
|
||||
}
|
||||
+83
-34
@@ -24,15 +24,18 @@ 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.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisBiomeCustom;
|
||||
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.IrisRegion;
|
||||
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.IrisPlatforms;
|
||||
@@ -363,9 +366,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
throw new IllegalStateException("Native structure locate received an unregistered structure holder");
|
||||
}
|
||||
String structureId = id.toString();
|
||||
IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(current,
|
||||
structureId, NativeStructurePostProcessor.isUndergroundStep(holder.value().step()));
|
||||
if (decision.status() != NativeStructureGenerationStatus.REPLACED_BY_IRIS) {
|
||||
if (!IrisStructureLocator.isPlaced(current, structureId)) {
|
||||
continue;
|
||||
}
|
||||
IrisStructureLocator.LocateResult result = IrisStructureLocator.locate(
|
||||
@@ -954,7 +955,20 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
IrisContext.Scope ignored = IrisContext.open(current, lease.sessionId(), null)) {
|
||||
Map<Structure, StructureStart> previousStarts = new HashMap<>(chunk.getAllStarts());
|
||||
super.createStructures(registryAccess, structureState, structureManager, chunk, templateManager, levelKey);
|
||||
adjustGeneratedStructures(registryAccess, chunk, previousStarts, current);
|
||||
Map<Structure, NativeStructureStartPlan> configuredStarts = NativeStructureStartInjector.inject(
|
||||
new NativeStructureStartInjector.InjectionContext(
|
||||
current,
|
||||
registryAccess,
|
||||
structureState,
|
||||
structureManager,
|
||||
chunk,
|
||||
templateManager,
|
||||
levelKey,
|
||||
this,
|
||||
structureBiomeSource
|
||||
));
|
||||
adjustGeneratedStructures(
|
||||
registryAccess, chunk, previousStarts, configuredStarts, current, templateManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -968,7 +982,10 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
}
|
||||
|
||||
private void adjustGeneratedStructures(RegistryAccess registryAccess, ChunkAccess chunk,
|
||||
Map<Structure, StructureStart> previousStarts, Engine current) {
|
||||
Map<Structure, StructureStart> previousStarts,
|
||||
Map<Structure, NativeStructureStartPlan> configuredStarts,
|
||||
Engine current,
|
||||
StructureTemplateManager templateManager) {
|
||||
Registry<Structure> registry = registryAccess.lookupOrThrow(Registries.STRUCTURE);
|
||||
ChunkPos chunkPos = chunk.getPos();
|
||||
for (Map.Entry<Structure, StructureStart> entry : chunk.getAllStarts().entrySet()) {
|
||||
@@ -977,6 +994,11 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
if (!start.isValid() || previousStarts.get(structure) == start) {
|
||||
continue;
|
||||
}
|
||||
if (configuredStarts.containsKey(structure)) {
|
||||
recordWorldCheckStructureShift(
|
||||
configuredStarts.get(structure).source().getStructure(), start.getChunkPos(), 0);
|
||||
continue;
|
||||
}
|
||||
Identifier id = registry.getKey(structure);
|
||||
String structureId = id == null ? null : id.toString();
|
||||
if (structureId == null) {
|
||||
@@ -1006,7 +1028,13 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
chunk.getMinY(),
|
||||
chunk.getMinY() + chunk.getHeight(),
|
||||
undergroundStep,
|
||||
decision.preserveSourceY(),
|
||||
decision.yBand(),
|
||||
(x, z) -> current.getHeight(x, z, true) + current.getMinHeight());
|
||||
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
|
||||
start, structure, start.getReferences(), templateManager,
|
||||
NativeStructurePostProcessor.resolveNativeTerrain(start, decision.terrain()));
|
||||
chunk.setStartForStructure(structure, wrapped);
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"vertical adjustment", structureId, chunkPos.x(), chunkPos.z(), error);
|
||||
@@ -1036,6 +1064,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
List<NativePlacementGroup> placementGroups = 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)) {
|
||||
@@ -1046,23 +1075,35 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
"resolution", null, chunkPos.x(), chunkPos.z());
|
||||
}
|
||||
try {
|
||||
IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(current,
|
||||
IrisNativeStructureDecision sourceDecision = NativeStructureGenerationPolicy.resolve(current,
|
||||
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(
|
||||
current, 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));
|
||||
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(
|
||||
@@ -1088,12 +1129,20 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
"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(decorationSeed, 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(
|
||||
@@ -1121,7 +1170,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -1169,14 +1218,11 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
return worldCheckStructureShifts.get(new NativeStructureStartKey(structureId, startChunk.pack()));
|
||||
}
|
||||
|
||||
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 BlockState blockState)) {
|
||||
throw new IllegalStateException("Configured native structure stilt palette did not resolve a Minecraft block at "
|
||||
throw new IllegalStateException("Configured native structure palette did not resolve a Minecraft block at "
|
||||
+ x + "," + y + "," + z);
|
||||
}
|
||||
return blockState;
|
||||
@@ -1296,8 +1342,11 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
private record NativeStructureStartKey(String structureId, long chunkPosition) {
|
||||
}
|
||||
|
||||
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) {
|
||||
}
|
||||
|
||||
record DimensionMetadata(int minY, int maxY, int seaLevel) {
|
||||
|
||||
@@ -46,6 +46,7 @@ import net.minecraft.world.level.levelgen.feature.TreeFeature;
|
||||
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.structures.JigsawStructure;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -69,6 +70,24 @@ public final class ModdedStructureHooks implements PlatformStructureHooks {
|
||||
return registryKeys(Registries.STRUCTURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> jigsawStructureKeys() {
|
||||
List<String> keys = new ArrayList<>();
|
||||
MinecraftServer instance = requireServer("read registered jigsaw structures");
|
||||
Registry<Structure> structures = instance.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||
for (Map.Entry<ResourceKey<Structure>, Structure> entry : structures.entrySet()) {
|
||||
if (entry.getValue() instanceof JigsawStructure) {
|
||||
keys.add(entry.getKey().identifier().toString());
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> templatePoolKeys() {
|
||||
return registryKeys(Registries.TEMPLATE_POOL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> structureSetKeys() {
|
||||
return registryKeys(Registries.STRUCTURE_SET);
|
||||
|
||||
+6
@@ -59,12 +59,18 @@ public class ModdedStructureHooksTest {
|
||||
|
||||
IllegalStateException structureKeys = assertThrows(
|
||||
IllegalStateException.class, hooks::structureKeys);
|
||||
IllegalStateException jigsawStructureKeys = assertThrows(
|
||||
IllegalStateException.class, hooks::jigsawStructureKeys);
|
||||
IllegalStateException templatePoolKeys = assertThrows(
|
||||
IllegalStateException.class, hooks::templatePoolKeys);
|
||||
IllegalStateException structureSetKeys = assertThrows(
|
||||
IllegalStateException.class, hooks::structureSetKeys);
|
||||
IllegalStateException structureBiomeKeys = assertThrows(
|
||||
IllegalStateException.class, () -> hooks.structureBiomeKeys("minecraft:village"));
|
||||
|
||||
assertTrue(structureKeys.getMessage().contains("before the Minecraft server is available"));
|
||||
assertTrue(jigsawStructureKeys.getMessage().contains("before the Minecraft server is available"));
|
||||
assertTrue(templatePoolKeys.getMessage().contains("before the Minecraft server is available"));
|
||||
assertTrue(structureSetKeys.getMessage().contains("before the Minecraft server is available"));
|
||||
assertTrue(structureBiomeKeys.getMessage().contains("before the Minecraft server is available"));
|
||||
}
|
||||
|
||||
+6
-8
@@ -34,23 +34,21 @@ public class IrisModdedStructureCommandTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generatorLocateUsesIrisOnlyForExplicitReplacement() throws IOException {
|
||||
public void generatorLocateUsesEveryIrisPlacedNativeStructure() throws IOException {
|
||||
String source = moddedSource("IrisModdedChunkGenerator.java");
|
||||
int methodStart = source.indexOf("private Pair<BlockPos, Holder<Structure>> findNearestIrisStructure(");
|
||||
int methodEnd = source.indexOf("private HolderSet<Structure> filterReachableNativeStructures(", methodStart);
|
||||
String method = source.substring(methodStart, methodEnd);
|
||||
int unexploredGuard = method.indexOf("if (findUnexplored)");
|
||||
int registryLookup = method.indexOf("level.registryAccess().lookupOrThrow(Registries.STRUCTURE)");
|
||||
int policyResolution = method.indexOf("NativeStructureGenerationPolicy.resolve(current,");
|
||||
int replacementCheck = method.indexOf(
|
||||
"decision.status() != NativeStructureGenerationStatus.REPLACED_BY_IRIS", policyResolution);
|
||||
int irisLocate = method.indexOf("IrisStructureLocator.locate(", replacementCheck);
|
||||
int placedCheck = method.indexOf(
|
||||
"if (!IrisStructureLocator.isPlaced(current, structureId))");
|
||||
int irisLocate = method.indexOf("IrisStructureLocator.locate(", placedCheck);
|
||||
|
||||
assertTrue(unexploredGuard >= 0);
|
||||
assertTrue(registryLookup > unexploredGuard);
|
||||
assertTrue(policyResolution > registryLookup);
|
||||
assertTrue(replacementCheck > policyResolution);
|
||||
assertTrue(irisLocate > replacementCheck);
|
||||
assertTrue(placedCheck > registryLookup);
|
||||
assertTrue(irisLocate > placedCheck);
|
||||
assertTrue(method.contains("LocateStatus.SEARCH_LIMIT_REACHED"));
|
||||
assertTrue(method.contains("new BlockPos(result.originX(), result.baseY(), result.originZ())"));
|
||||
assertFalse(method.contains("NativeStructureLocateCapability"));
|
||||
|
||||
Reference in New Issue
Block a user