This commit is contained in:
Brian Neumann-Fopiano
2026-08-11 20:35:27 -04:00
parent 82290a3090
commit 623a02025c
76 changed files with 4820 additions and 280 deletions
@@ -26,6 +26,7 @@ import art.arcane.iris.engine.framework.GenerationSessionException;
import art.arcane.iris.engine.framework.GenerationSessionLease;
import art.arcane.iris.engine.framework.NativeStructureStartPlan;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisImportedStructureControl;
import art.arcane.iris.nativegen.NativeStructureStartInjector;
import art.arcane.iris.nativegen.NativeStructureReferenceRepair;
import art.arcane.iris.nativegen.NativeStructureVanillaLocator;
@@ -284,8 +285,21 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
@Override
public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> structureSets, RandomState randomState, long seed) {
return ChunkGeneratorStructureState.createForNormal(
ChunkGeneratorStructureState state = ChunkGeneratorStructureState.createForNormal(
randomState, seed, structureBiomeSource.forStructureState(structureSets), structureSets);
return ModdedStructureSetFrequencyOverrides.apply(state, configuredImportedStructures());
}
private IrisImportedStructureControl configuredImportedStructures() {
Engine current = engine;
if (current != null && !current.isClosed() && !current.isClosing()) {
IrisDimension dimension = current.getDimension();
if (dimension != null && dimension.getImportedStructures() != null) {
return dimension.getImportedStructures();
}
}
IrisImportedStructureControl importedStructures = configuredPack().dimension().getImportedStructures();
return importedStructures == null ? new IrisImportedStructureControl() : importedStructures;
}
@Override
@@ -444,10 +458,11 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
return;
}
String remedy = integratedEnvironment()
? "enable 'Generate Structures' for this world; Iris requires it, and individual structures "
+ "are denied through importedStructures.disabled"
? "enable 'Generate Structures' for this world; Iris requires it, then deny families through "
+ "importedStructures.disabled or complete keys through importedStructures.disabledExact"
: "set generate-structures=true in server.properties, restart the server, "
+ "and deny individual structures through importedStructures.disabled";
+ "then deny families through importedStructures.disabled or complete keys through "
+ "importedStructures.disabledExact";
throw new IllegalStateException("Iris generator '" + dimensionKey
+ "' cannot bind while generate-structures=false; " + remedy);
}
@@ -287,7 +287,8 @@ final class ModdedNativeStructureStage {
+ " because generate-structures=false disables them outside the pack. That flag is fixed when "
+ "the world is created (server.properties generate-structures, or the Generate Structures "
+ "toggle in singleplayer), so it cannot be changed for this world: create a new world with "
+ "structures enabled, and deny individual structures through importedStructures.disabled");
+ "structures enabled, then deny families through importedStructures.disabled or complete keys "
+ "through importedStructures.disabledExact");
}
ChunkPos chunkPos = chunk.getPos();
SectionPos sectionPos = SectionPos.of(chunkPos, world.getMinSectionY());
@@ -0,0 +1,285 @@
/*
* Iris is a World Generator for Minecraft Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.modded;
import art.arcane.iris.engine.framework.NativeStructureFrequencyScale;
import art.arcane.iris.engine.object.IrisImportedStructureControl;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderOwner;
import net.minecraft.core.Vec3i;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
final class ModdedStructureSetFrequencyOverrides {
private ModdedStructureSetFrequencyOverrides() {
}
static ChunkGeneratorStructureState apply(
ChunkGeneratorStructureState state,
IrisImportedStructureControl importedStructures
) {
if (!importedStructures.hasFrequencyOverrides()) {
return state;
}
List<Holder<StructureSet>> original = state.possibleStructureSets();
List<Holder<StructureSet>> scaled = scaleSets(original, importedStructures);
if (scaled == original) {
return state;
}
Field possibleSetsField = declaredField(ChunkGeneratorStructureState.class, List.class);
try {
possibleSetsField.set(state, scaled);
return state;
} catch (IllegalAccessException error) {
throw new IllegalStateException(
"Could not apply native structure-set frequency overrides", error);
}
}
static List<Holder<StructureSet>> scaleSets(
List<Holder<StructureSet>> structureSets,
IrisImportedStructureControl importedStructures
) {
Map<String, Holder<StructureSet>> holdersByKey = new HashMap<>();
for (Holder<StructureSet> holder : structureSets) {
String key = structureSetKey(holder);
if (key != null) {
holdersByKey.putIfAbsent(key, holder);
}
}
Map<Holder<StructureSet>, Holder<StructureSet>> scaledByIdentity = new IdentityHashMap<>();
List<Holder<StructureSet>> scaledSets = new ArrayList<>(structureSets.size());
boolean changed = false;
for (Holder<StructureSet> holder : structureSets) {
Holder<StructureSet> scaled = scaleHolder(
holder, importedStructures, holdersByKey, scaledByIdentity);
scaledSets.add(scaled);
changed |= scaled != holder;
}
return changed ? List.copyOf(scaledSets) : structureSets;
}
private static Holder<StructureSet> scaleHolder(
Holder<StructureSet> holder,
IrisImportedStructureControl importedStructures,
Map<String, Holder<StructureSet>> holdersByKey,
Map<Holder<StructureSet>, Holder<StructureSet>> scaledByIdentity
) {
if (scaledByIdentity.containsKey(holder)) {
return scaledByIdentity.get(holder);
}
if (!dependsOnOverride(holder, importedStructures, holdersByKey)) {
scaledByIdentity.put(holder, holder);
return holder;
}
ResourceKey<StructureSet> holderKey = holder.unwrapKey().orElseThrow(() ->
new IllegalStateException("An affected native structure-set exclusion graph has an unkeyed holder"));
ScaledStructureSetHolder scaledHolder = new ScaledStructureSetHolder(holderKey);
scaledByIdentity.put(holder, scaledHolder);
StructureSet originalSet = holder.value();
StructurePlacement originalPlacement = originalSet.placement();
Optional<StructurePlacement.ExclusionZone> originalZone = exclusionZone(originalPlacement);
Optional<StructurePlacement.ExclusionZone> scaledZone = originalZone;
if (originalZone.isPresent()) {
Holder<StructureSet> target = canonicalHolder(
originalZone.get().otherSet(), holdersByKey);
Holder<StructureSet> scaledTarget = scaleHolder(
target, importedStructures, holdersByKey, scaledByIdentity);
if (scaledTarget != originalZone.get().otherSet()) {
scaledZone = Optional.of(new StructurePlacement.ExclusionZone(
scaledTarget, originalZone.get().chunkCount()));
}
}
double multiplier = importedStructures.frequencyMultiplier(structureSetKey(holder));
StructurePlacement scaledPlacement = multiplier == 1D && scaledZone.equals(originalZone)
? originalPlacement
: scalePlacement(originalPlacement, originalZone, scaledZone, multiplier);
scaledHolder.bind(new StructureSet(originalSet.structures(), scaledPlacement));
return scaledHolder;
}
private static boolean dependsOnOverride(
Holder<StructureSet> holder,
IrisImportedStructureControl importedStructures,
Map<String, Holder<StructureSet>> holdersByKey
) {
Set<Holder<StructureSet>> visited = Collections.newSetFromMap(new IdentityHashMap<>());
Holder<StructureSet> current = holder;
while (visited.add(current)) {
if (importedStructures.frequencyMultiplier(structureSetKey(current)) != 1D) {
return true;
}
Optional<StructurePlacement.ExclusionZone> zone =
exclusionZone(current.value().placement());
if (zone.isEmpty()) {
return false;
}
current = canonicalHolder(zone.get().otherSet(), holdersByKey);
}
return false;
}
private static Holder<StructureSet> canonicalHolder(
Holder<StructureSet> holder,
Map<String, Holder<StructureSet>> holdersByKey
) {
String key = structureSetKey(holder);
return key == null ? holder : holdersByKey.getOrDefault(key, holder);
}
private static StructurePlacement scalePlacement(
StructurePlacement placement,
Optional<StructurePlacement.ExclusionZone> originalZone,
Optional<StructurePlacement.ExclusionZone> scaledZone,
double multiplier
) {
Vec3i locateOffset = (Vec3i) declaredFieldValue(
StructurePlacement.class, placement, Vec3i.class);
StructurePlacement.FrequencyReductionMethod reductionMethod =
(StructurePlacement.FrequencyReductionMethod) declaredFieldValue(
StructurePlacement.class,
placement,
StructurePlacement.FrequencyReductionMethod.class);
float frequency = (float) declaredFieldValue(
StructurePlacement.class, placement, float.class);
int salt = (int) declaredFieldValue(
StructurePlacement.class, placement, int.class);
if (placement.getClass() == RandomSpreadStructurePlacement.class) {
RandomSpreadStructurePlacement randomSpread =
(RandomSpreadStructurePlacement) placement;
NativeStructureFrequencyScale scale = NativeStructureFrequencyScale.randomSpread(
frequency, randomSpread.spacing(), randomSpread.separation(), multiplier);
if (scale.frequency() == frequency
&& scale.spacing() == randomSpread.spacing()
&& scaledZone.equals(originalZone)) {
return placement;
}
return new RandomSpreadStructurePlacement(
locateOffset,
reductionMethod,
scale.frequency(),
salt,
scaledZone,
scale.spacing(),
randomSpread.separation(),
randomSpread.spreadType());
}
if (placement.getClass() == ConcentricRingsStructurePlacement.class) {
ConcentricRingsStructurePlacement rings =
(ConcentricRingsStructurePlacement) placement;
float scaledFrequency = NativeStructureFrequencyScale.probability(frequency, multiplier);
if (scaledFrequency == frequency && scaledZone.equals(originalZone)) {
return placement;
}
return new ConcentricRingsStructurePlacement(
locateOffset,
reductionMethod,
scaledFrequency,
salt,
scaledZone,
rings.distance(),
rings.spread(),
rings.count(),
rings.preferredBiomes());
}
throw new IllegalStateException("Unsupported native structure placement: "
+ placement.getClass().getName());
}
private static Optional<StructurePlacement.ExclusionZone> exclusionZone(
StructurePlacement placement
) {
Object value = declaredFieldValue(StructurePlacement.class, placement, Optional.class);
if (value instanceof Optional<?> optional && (optional.isEmpty()
|| optional.get() instanceof StructurePlacement.ExclusionZone)) {
@SuppressWarnings("unchecked")
Optional<StructurePlacement.ExclusionZone> resolved =
(Optional<StructurePlacement.ExclusionZone>) optional;
return resolved;
}
throw new IllegalStateException("Could not read native structure exclusion zone from "
+ placement.getClass().getName());
}
private static String structureSetKey(Holder<StructureSet> holder) {
Optional<ResourceKey<StructureSet>> key = holder.unwrapKey();
return key.map(resourceKey -> resourceKey.identifier().toString()).orElse(null);
}
private static Object declaredFieldValue(
Class<?> declaringType,
Object target,
Class<?> fieldType
) {
Field field = declaredField(declaringType, fieldType);
try {
return field.get(target);
} catch (IllegalAccessException error) {
throw new IllegalStateException("Could not read " + fieldType.getName()
+ " field on " + declaringType.getName(), error);
}
}
private static Field declaredField(Class<?> declaringType, Class<?> fieldType) {
Field match = null;
for (Field field : declaringType.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) || field.getType() != fieldType) {
continue;
}
if (match != null) {
throw new IllegalStateException("Ambiguous " + fieldType.getName() + " field on "
+ declaringType.getName());
}
match = field;
}
if (match == null || !match.trySetAccessible()) {
throw new IllegalStateException("Missing accessible " + fieldType.getName()
+ " field on " + declaringType.getName());
}
return match;
}
private static final class ScaledStructureSetHolder extends Holder.Reference<StructureSet> {
private ScaledStructureSetHolder(ResourceKey<StructureSet> key) {
super(Type.STAND_ALONE, new HolderOwner<>() {
}, key, null);
}
private void bind(StructureSet structureSet) {
bindValue(structureSet);
}
}
}