This commit is contained in:
Brian Neumann-Fopiano
2026-07-30 02:36:15 -04:00
parent 7ad4909518
commit 0afbfcb7a2
110 changed files with 7157 additions and 846 deletions
@@ -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);
@@ -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"));
}
@@ -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"));