[VERY VERY VERY BAD IDEA]

This commit is contained in:
Brian Neumann-Fopiano
2026-09-20 04:24:42 -04:00
parent f0e7fea13b
commit 2fa0853e42
892 changed files with 13482 additions and 53554 deletions
@@ -1,509 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.pack.datapack.DatapackStructureScopeIndex;
import art.arcane.iris.structure.nativegen.NativeStructureFrequencyScale;
import art.arcane.iris.structure.nativegen.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.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 DatapackStructureStateFilter {
private DatapackStructureStateFilter() {
}
static Selection filter(
List<Holder<StructureSet>> structureSets,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources
) {
return filter(structureSets, scopeIndex, declaredSources, new IrisImportedStructureControl());
}
static Selection filter(
List<Holder<StructureSet>> structureSets,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources,
IrisImportedStructureControl importedStructures
) {
return filter(
structureSets,
scopeIndex,
declaredSources,
importedStructures,
keyIndex(structureSets));
}
static Selection filter(
List<Holder<StructureSet>> structureSets,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources,
IrisImportedStructureControl importedStructures,
StructureSetKeyIndex keyIndex
) {
if (scopeIndex.isEmpty()) {
return new Selection(
scaleFrequencyOverrides(structureSets, importedStructures, keyIndex),
0,
0);
}
Map<String, Holder<StructureSet>> holdersByKey = new HashMap<>();
for (Holder<StructureSet> holder : structureSets) {
String key = structureSetKey(holder, keyIndex);
if (key != null) {
holdersByKey.putIfAbsent(key, holder);
}
}
Map<Holder<StructureSet>, Holder<StructureSet>> scopedByIdentity = new IdentityHashMap<>();
Set<Holder<StructureSet>> visiting = Collections.newSetFromMap(new IdentityHashMap<>());
List<Holder<StructureSet>> filteredSets = new ArrayList<>(structureSets.size());
int retainedManagedSets = 0;
int excludedManagedSets = 0;
for (Holder<StructureSet> holder : structureSets) {
String setKey = structureSetKey(holder, keyIndex);
boolean managedSet = setKey != null && scopeIndex.isManagedStructureSet(setKey);
Holder<StructureSet> scopedHolder = scopeHolder(
holder,
scopeIndex,
declaredSources,
holdersByKey,
scopedByIdentity,
visiting,
keyIndex);
if (scopedHolder == null) {
if (managedSet) {
excludedManagedSets++;
}
continue;
}
if (managedSet) {
retainedManagedSets++;
}
filteredSets.add(scopedHolder);
}
return new Selection(
scaleFrequencyOverrides(
List.copyOf(filteredSets), importedStructures, keyIndex),
retainedManagedSets,
excludedManagedSets);
}
static String structureSetKey(Holder<StructureSet> holder) {
return structureSetKey(holder, keyIndex(List.of(holder)));
}
static StructureSetKeyIndex keyIndex(
Iterable<? extends Holder<StructureSet>> registeredStructureSets
) {
return StructureSetKeyIndex.create(registeredStructureSets);
}
private static String structureSetKey(
Holder<StructureSet> holder,
StructureSetKeyIndex keyIndex
) {
return keyIndex.resolve(holder)
.map(key -> key.identifier().toString())
.orElse(null);
}
private static List<StructureSet.StructureSelectionEntry> filterEntries(
List<StructureSet.StructureSelectionEntry> entries,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources
) {
List<StructureSet.StructureSelectionEntry> filtered = new ArrayList<>(entries.size());
for (StructureSet.StructureSelectionEntry entry : entries) {
String structureKey = entry.structure().unwrapKey()
.map(key -> key.identifier().toString())
.orElse(null);
if (structureKey == null || scopeIndex.allowsStructure(structureKey, declaredSources)) {
filtered.add(entry);
}
}
return filtered;
}
private static Holder<StructureSet> scopeHolder(
Holder<StructureSet> holder,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources,
Map<String, Holder<StructureSet>> holdersByKey,
Map<Holder<StructureSet>, Holder<StructureSet>> scopedByIdentity,
Set<Holder<StructureSet>> visiting,
StructureSetKeyIndex keyIndex
) {
if (scopedByIdentity.containsKey(holder)) {
return scopedByIdentity.get(holder);
}
if (!visiting.add(holder)) {
return null;
}
try {
String setKey = structureSetKey(holder, keyIndex);
if (setKey != null && scopeIndex.isManagedStructureSet(setKey)
&& !scopeIndex.allowsStructureSet(setKey, declaredSources)) {
scopedByIdentity.put(holder, null);
return null;
}
StructureSet originalSet = holder.value();
List<StructureSet.StructureSelectionEntry> entries = filterEntries(
originalSet.structures(), scopeIndex, declaredSources);
if (entries.isEmpty()) {
scopedByIdentity.put(holder, null);
return null;
}
StructurePlacement placement = scopePlacement(
originalSet.placement(),
scopeIndex,
declaredSources,
holdersByKey,
scopedByIdentity,
visiting,
keyIndex);
if (entries.size() == originalSet.structures().size()
&& placement == originalSet.placement()) {
scopedByIdentity.put(holder, holder);
return holder;
}
Holder<StructureSet> scoped = replacementHolder(
holder, new StructureSet(entries, placement), keyIndex);
scopedByIdentity.put(holder, scoped);
return scoped;
} finally {
visiting.remove(holder);
}
}
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 structure placement exclusion zone from "
+ placement.getClass().getName());
}
private static StructurePlacement scopePlacement(
StructurePlacement placement,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources,
Map<String, Holder<StructureSet>> holdersByKey,
Map<Holder<StructureSet>, Holder<StructureSet>> scopedByIdentity,
Set<Holder<StructureSet>> visiting,
StructureSetKeyIndex keyIndex
) {
Optional<StructurePlacement.ExclusionZone> currentZone = exclusionZone(placement);
if (currentZone.isEmpty()) {
return placement;
}
Holder<StructureSet> target = currentZone.get().otherSet();
String targetKey = structureSetKey(target, keyIndex);
if (targetKey != null) {
target = holdersByKey.getOrDefault(targetKey, target);
}
Holder<StructureSet> scopedTarget = scopeHolder(
target,
scopeIndex,
declaredSources,
holdersByKey,
scopedByIdentity,
visiting,
keyIndex);
Optional<StructurePlacement.ExclusionZone> scopedZone = Optional.empty();
if (scopedTarget != null) {
scopedZone = Optional.of(new StructurePlacement.ExclusionZone(
scopedTarget,
currentZone.get().chunkCount()));
}
if (scopedTarget == currentZone.get().otherSet()) {
return placement;
}
return copyPlacement(placement, scopedZone, 1D);
}
private static List<Holder<StructureSet>> scaleFrequencyOverrides(
List<Holder<StructureSet>> structureSets,
IrisImportedStructureControl importedStructures,
StructureSetKeyIndex keyIndex
) {
if (!importedStructures.hasFrequencyOverrides()) {
return structureSets;
}
Map<String, Holder<StructureSet>> holdersByKey = new HashMap<>();
for (Holder<StructureSet> holder : structureSets) {
String key = structureSetKey(holder, keyIndex);
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 = scaleFrequencyHolder(
holder,
importedStructures,
holdersByKey,
scaledByIdentity,
keyIndex);
scaledSets.add(scaled);
changed |= scaled != holder;
}
return changed ? List.copyOf(scaledSets) : structureSets;
}
private static Holder<StructureSet> scaleFrequencyHolder(
Holder<StructureSet> holder,
IrisImportedStructureControl importedStructures,
Map<String, Holder<StructureSet>> holdersByKey,
Map<Holder<StructureSet>, Holder<StructureSet>> scaledByIdentity,
StructureSetKeyIndex keyIndex
) {
if (scaledByIdentity.containsKey(holder)) {
return scaledByIdentity.get(holder);
}
if (!dependsOnFrequencyOverride(
holder, importedStructures, holdersByKey, keyIndex)) {
scaledByIdentity.put(holder, holder);
return holder;
}
ResourceKey<StructureSet> holderKey = keyIndex.resolve(holder).orElseThrow(() ->
new IllegalStateException(
"An affected native structure-set exclusion graph has an unkeyed holder"));
ReboundStructureSetHolder scaledHolder = new ReboundStructureSetHolder(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, keyIndex);
Holder<StructureSet> scaledTarget = scaleFrequencyHolder(
target,
importedStructures,
holdersByKey,
scaledByIdentity,
keyIndex);
if (scaledTarget != originalZone.get().otherSet()) {
scaledZone = Optional.of(new StructurePlacement.ExclusionZone(
scaledTarget, originalZone.get().chunkCount()));
}
}
double multiplier = importedStructures.frequencyMultiplier(
structureSetKey(holder, keyIndex));
StructurePlacement scaledPlacement = multiplier == 1D && scaledZone.equals(originalZone)
? originalPlacement
: copyPlacement(originalPlacement, scaledZone, multiplier);
scaledHolder.bind(new StructureSet(originalSet.structures(), scaledPlacement));
return scaledHolder;
}
private static boolean dependsOnFrequencyOverride(
Holder<StructureSet> holder,
IrisImportedStructureControl importedStructures,
Map<String, Holder<StructureSet>> holdersByKey,
StructureSetKeyIndex keyIndex
) {
Set<Holder<StructureSet>> visited = Collections.newSetFromMap(new IdentityHashMap<>());
Holder<StructureSet> current = holder;
while (visited.add(current)) {
if (importedStructures.frequencyMultiplier(
structureSetKey(current, keyIndex)) != 1D) {
return true;
}
Optional<StructurePlacement.ExclusionZone> zone =
exclusionZone(current.value().placement());
if (zone.isEmpty()) {
return false;
}
current = canonicalHolder(zone.get().otherSet(), holdersByKey, keyIndex);
}
return false;
}
private static Holder<StructureSet> canonicalHolder(
Holder<StructureSet> holder,
Map<String, Holder<StructureSet>> holdersByKey,
StructureSetKeyIndex keyIndex
) {
String key = structureSetKey(holder, keyIndex);
return key == null ? holder : holdersByKey.getOrDefault(key, holder);
}
private static Holder<StructureSet> replacementHolder(
Holder<StructureSet> original,
StructureSet replacement,
StructureSetKeyIndex keyIndex
) {
Optional<ResourceKey<StructureSet>> key = keyIndex.resolve(original);
if (key.isEmpty()) {
return Holder.direct(replacement);
}
ReboundStructureSetHolder holder = new ReboundStructureSetHolder(key.get());
holder.bind(replacement);
return holder;
}
private static StructurePlacement copyPlacement(
StructurePlacement placement,
Optional<StructurePlacement.ExclusionZone> exclusionZone,
double frequencyMultiplier
) {
Vec3i locateOffset = (Vec3i) declaredFieldValue(
StructurePlacement.class, placement, Vec3i.class);
StructurePlacement.FrequencyReductionMethod frequencyReductionMethod =
(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 instanceof RandomSpreadStructurePlacement randomSpread) {
NativeStructureFrequencyScale scale = NativeStructureFrequencyScale.randomSpread(
frequency, randomSpread.spacing(), randomSpread.separation(), frequencyMultiplier);
return new RandomSpreadStructurePlacement(
locateOffset,
frequencyReductionMethod,
scale.frequency(),
salt,
exclusionZone,
scale.spacing(),
randomSpread.separation(),
randomSpread.spreadType());
}
if (placement.getClass() == ConcentricRingsStructurePlacement.class) {
ConcentricRingsStructurePlacement rings =
(ConcentricRingsStructurePlacement) placement;
float scaledFrequency = NativeStructureFrequencyScale.probability(
frequency, frequencyMultiplier);
return new ConcentricRingsStructurePlacement(
locateOffset,
frequencyReductionMethod,
scaledFrequency,
salt,
exclusionZone,
rings.distance(),
rings.spread(),
rings.count(),
rings.preferredBiomes());
}
throw new IllegalStateException("Unsupported native structure placement in an affected "
+ "frequency-override or datapack-scope graph: "
+ placement.getClass().getName());
}
private static Object declaredFieldValue(
Class<?> declaringType,
Object target,
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) {
throw new IllegalStateException("Missing " + fieldType.getName() + " field on "
+ declaringType.getName());
}
try {
match.setAccessible(true);
return match.get(target);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Could not read " + fieldType.getName() + " field on "
+ declaringType.getName(), e);
}
}
static final class StructureSetKeyIndex {
private final Map<List<StructureSet.StructureSelectionEntry>, ResourceKey<StructureSet>>
keysByEntries;
private StructureSetKeyIndex(
Map<List<StructureSet.StructureSelectionEntry>, ResourceKey<StructureSet>>
keysByEntries
) {
this.keysByEntries = keysByEntries;
}
private static StructureSetKeyIndex create(
Iterable<? extends Holder<StructureSet>> registeredStructureSets
) {
Map<List<StructureSet.StructureSelectionEntry>, ResourceKey<StructureSet>> keys =
new IdentityHashMap<>();
for (Holder<StructureSet> holder : registeredStructureSets) {
Optional<ResourceKey<StructureSet>> key = holder.unwrapKey();
if (key.isEmpty()) {
continue;
}
List<StructureSet.StructureSelectionEntry> entries = holder.value().structures();
ResourceKey<StructureSet> previous = keys.put(entries, key.get());
if (previous != null && !previous.equals(key.get())) {
throw new IllegalStateException(
"Multiple registered structure sets share the same entry list");
}
}
return new StructureSetKeyIndex(keys);
}
private Optional<ResourceKey<StructureSet>> resolve(Holder<StructureSet> holder) {
Optional<ResourceKey<StructureSet>> key = holder.unwrapKey();
if (key.isPresent()) {
return key;
}
return Optional.ofNullable(keysByEntries.get(holder.value().structures()));
}
}
record Selection(
List<Holder<StructureSet>> structureSets,
int retainedManagedSets,
int excludedManagedSets
) {
}
private static final class ReboundStructureSetHolder extends Holder.Reference<StructureSet> {
private ReboundStructureSetHolder(ResourceKey<StructureSet> key) {
super(Type.STAND_ALONE, new HolderOwner<>() {
}, key, null);
}
private void bind(StructureSet structureSet) {
bindValue(structureSet);
}
}
}
@@ -1,440 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.generation.runtime.DimensionStackContext;
import art.arcane.iris.generation.runtime.DimensionStackLayout;
import art.arcane.iris.nativegen.NativeGenerationWriteGuard;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.structure.nativegen.NativeFeatureGenerationPolicy;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.biome.IrisBiomeCustom;
import art.arcane.iris.generation.decoration.IrisDecorationStep;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.structure.nativegen.IrisImportedFeatureControl;
import art.arcane.iris.structure.object.IrisStaticObjectLayer;
import art.arcane.iris.spi.IrisLogging;
import it.unimi.dsi.fastutil.ints.IntArraySet;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.Registry;
import net.minecraft.core.SectionPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeGenerationSettings;
import net.minecraft.world.level.biome.FeatureSorter;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.RandomSupport;
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.placement.PlacedFeature;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.IntBinaryOperator;
/**
* Bukkit twin of the modded imported-feature stage. Same control, same semantics, same seeds: with
* {@code importedFeatures.enabled} the vanilla placed-feature pass runs over Iris terrain, and with the
* control off nothing here allocates or runs.
*
* <p>Iris does its own native structure pass and calls the delegate with {@code addVanillaDecorations=false},
* so the vanilla feature half never runs on its own. This reproduces that half only - structures are never
* placed twice.
*
* <p>Threading: called from {@code applyBiomeDecoration} on the worldgen thread that owns the chunk. The
* FEATURES chunk step is not parallel-safe.
*/
final class ImportedFeatureStage {
private static final String CYCLE_MARKER = "Feature order cycle found";
private final Engine engine;
private final ConcurrentHashMap<Integer, RuntimeFeatureState> runtimeStates = new ConcurrentHashMap<>();
ImportedFeatureStage(Engine engine) {
this.engine = engine;
}
/**
* Generation settings for one biome holder, mapping Iris custom biomes onto their vanilla derivative.
* Pass-through while the control is off, which is what keeps {@code BiomeFilter} behaving exactly as it
* does today.
*/
BiomeGenerationSettings generationSettings(Holder<Biome> biome) {
RuntimeFeatureState state = currentState();
FeatureTable table = state == null ? null : state.featureTable();
if (table == null) {
return null;
}
Holder<Biome> mapped = table.derivatives().get(holderKey(biome));
return mapped == null ? null : mapped.value().getGenerationSettings();
}
/**
* Builds the table on the first decorated chunk, or leaves the stage inert. A feature-order cycle is
* reported once here and degrades to features-off; it never reaches chunk generation as a crash.
*/
void prepare(WorldGenLevel level) {
IrisDimension dimension = engine.getDimension();
int runtimeId = engine.getCacheID();
if (settled(runtimeStates.get(runtimeId), dimension)) {
return;
}
synchronized (this) {
if (settled(runtimeStates.get(runtimeId), dimension)) {
return;
}
runtimeStates.put(runtimeId, build(level, dimension));
}
}
void evictRuntime(int runtimeId) {
runtimeStates.remove(runtimeId);
}
private RuntimeFeatureState currentState() {
IrisDimension dimension = engine.getDimension();
RuntimeFeatureState state = runtimeStates.get(engine.getCacheID());
return settled(state, dimension) ? state : null;
}
private static boolean settled(RuntimeFeatureState state, IrisDimension dimension) {
return state != null && state.dimension() == dimension;
}
private RuntimeFeatureState build(WorldGenLevel level, IrisDimension dimension) {
IrisImportedFeatureControl control;
try {
control = NativeFeatureGenerationPolicy.control(engine);
} catch (RuntimeException error) {
IrisLogging.error("Iris could not read importedFeatures for this dimension; features off: "
+ error);
return new RuntimeFeatureState(dimension, null);
}
if (!control.shouldGenerateFeatures()) {
return new RuntimeFeatureState(dimension, null);
}
FeatureTable built;
try {
built = buildTable(level, control, dimension);
} catch (Throwable error) {
IrisLogging.error("Iris importedFeatures is off for " + dimensionKey()
+ ": feature table construction failed: " + error);
return new RuntimeFeatureState(dimension, null);
}
if (built == null) {
return new RuntimeFeatureState(dimension, null);
}
IrisLogging.info("Iris importedFeatures on for " + dimensionKey() + ": " + built.biomes().size()
+ " biomes, " + built.steps().size() + " steps, " + built.derivatives().size()
+ " custom-biome derivative maps");
return new RuntimeFeatureState(dimension, built);
}
private FeatureTable buildTable(WorldGenLevel level, IrisImportedFeatureControl control,
IrisDimension dimension) {
Registry<Biome> registry = level.registryAccess().lookupOrThrow(Registries.BIOME);
Set<String> visibleKeys = visibleBiomeKeys();
// Registry-ordered walk, never a hash-ordered set: FeatureSorter's cycle detection walks this list and
// an unordered walk makes detection depend on JVM hash order.
List<Holder<Biome>> biomes = new ArrayList<>();
Map<String, Holder<Biome>> byKey = new HashMap<>();
registry.listElements().forEach((Holder.Reference<Biome> reference) -> {
String key = holderKey(reference);
if (key != null && visibleKeys.contains(key)) {
biomes.add(reference);
byKey.put(key, reference);
}
});
if (biomes.isEmpty()) {
IrisLogging.error("Iris importedFeatures is on but " + dimensionKey()
+ " exposes no registered biomes; features off");
return null;
}
Map<String, Holder<Biome>> derivatives = customBiomeDerivatives(registry, byKey);
List<FeatureSorter.StepFeatureData> steps;
try {
steps = FeatureSorter.buildFeaturesPerStep(biomes,
(Holder<Biome> biome) -> settingsFor(biome, derivatives).features(), true);
} catch (IllegalStateException error) {
String message = error.getMessage();
if (message == null || !message.contains(CYCLE_MARKER)) {
throw error;
}
IrisLogging.error("Iris importedFeatures is off for " + dimensionKey()
+ ": the registered placed features cannot be ordered. " + message
+ ". Remove or reorder the conflicting content, or leave importedFeatures.enabled false.");
return null;
}
boolean filtered = control.getDisabled() != null && !control.getDisabled().isEmpty();
return new FeatureTable(dimension, control, List.copyOf(biomes), Set.copyOf(biomes),
Map.copyOf(byKey), steps, Map.copyOf(derivatives), filtered);
}
/**
* Every biome key Iris can write into a chunk section: the structure derivative, the raw derivative, every
* scatter entry, and every generated custom biome.
*/
private Set<String> visibleBiomeKeys() {
Set<String> keys = new LinkedHashSet<>();
for (IrisBiome irisBiome : engine.getDimension().getReachableBiomes(engine)) {
addKey(keys, irisBiome.getStructureDerivativeKey());
addKey(keys, irisBiome.getDerivativeKey());
addKey(keys, irisBiome.getVanillaDerivativeKey());
for (String scatter : irisBiome.getBiomeScatter()) {
addKey(keys, scatter);
}
for (String scatter : irisBiome.getBiomeSkyScatter()) {
addKey(keys, scatter);
}
if (!irisBiome.isCustom()) {
continue;
}
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
addKey(keys, engine.getData().customBiomeResourceKey(engine.getDimension(), customBiome));
}
}
return keys;
}
/**
* Maps every generated Iris custom biome key onto the registry holder of its Iris biome's vanilla
* derivative. The custom biome's own datapack JSON carries no features by design.
*/
private Map<String, Holder<Biome>> customBiomeDerivatives(Registry<Biome> registry,
Map<String, Holder<Biome>> byKey) {
Map<String, Holder<Biome>> derivatives = new HashMap<>();
for (IrisBiome irisBiome : engine.getDimension().getReachableBiomes(engine)) {
if (!irisBiome.isCustom()) {
continue;
}
String derivativeKey = normalizeKey(irisBiome.getVanillaDerivativeKey());
// Resolve from the registry, not only from the visible biome set: a sea or shore biome's structure
// derivative is rewritten away from its vanilla derivative, so the derivative whose features we
// want is not always a biome Iris can emit.
Holder<Biome> derivative = byKey.get(derivativeKey);
if (derivative == null) {
derivative = resolveHolder(registry, derivativeKey);
}
if (derivative == null) {
IrisLogging.warn("Iris importedFeatures: vanilla derivative " + derivativeKey + " of biome "
+ irisBiome.getLoadKey()
+ " is not registered; its custom biomes generate no imported features");
continue;
}
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
derivatives.put(
engine.getData().customBiomeResourceKey(engine.getDimension(), customBiome),
derivative
);
}
}
return derivatives;
}
private static Holder<Biome> resolveHolder(Registry<Biome> registry, String key) {
if (registry == null || key == null || key.isBlank()) {
return null;
}
Identifier identifier = Identifier.tryParse(key);
if (identifier == null) {
return null;
}
return registry.get(identifier).<Holder<Biome>>map((Holder.Reference<Biome> reference) -> reference)
.orElse(null);
}
private static BiomeGenerationSettings settingsFor(Holder<Biome> biome,
Map<String, Holder<Biome>> derivatives) {
Holder<Biome> mapped = derivatives.get(holderKey(biome));
return mapped == null
? biome.value().getGenerationSettings()
: mapped.value().getGenerationSettings();
}
/**
* Runs the vanilla placed-feature pass for one chunk on the calling worldgen thread. A no-op while the
* control is disabled or the table degraded.
*/
void run(WorldGenLevel level, ChunkAccess chunk, ChunkGenerator owner) {
RuntimeFeatureState state = currentState();
FeatureTable table = state == null ? null : state.featureTable();
if (table == null) {
return;
}
ChunkPos centerPos = chunk.getPos();
SectionPos sectionPos = SectionPos.of(centerPos, level.getMinSectionY());
BlockPos origin = sectionPos.origin();
Registry<PlacedFeature> featureRegistry = level.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE);
List<FeatureSorter.StepFeatureData> steps = table.steps();
WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
long decorationSeed = random.setDecorationSeed(level.getSeed(), origin.getX(), origin.getZ());
IrisStaticObjectLayer staticObjects = engine.getDimension().getStaticObjectLayer(engine.getData());
int staticMinY = engine.getMinHeight();
DimensionStackContext stackContext = engine.getDimensionStackContext();
Set<Holder<Biome>> chunkBiomes = chunkBiomes(level, sectionPos, table, stackContext);
IntBinaryOperator surfaceFirstFreeY = stackContext == null
? (x, z) -> level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z)
: (x, z) -> Engine.hostHeight(engine, x, z, false) + staticMinY + 1;
IntBinaryOperator floorFirstFreeY = stackContext == null
? (x, z) -> level.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z)
: (x, z) -> Engine.hostHeight(engine, x, z, true) + staticMinY + 1;
WorldGenLevel placementLevel = staticObjects.isEmpty() && stackContext == null
? level
: NativeStructureWorldgenAccess.create(
level, centerPos,
surfaceFirstFreeY,
floorFirstFreeY,
stackContext != null,
NativeGenerationWriteGuard.protectedPositions(staticObjects, stackContext, staticMinY));
try {
for (int stepIndex = 0; stepIndex < steps.size(); stepIndex++) {
if (!table.control().shouldGenerateStep(IrisDecorationStep.byOrdinal(stepIndex))) {
continue;
}
placeStep(placementLevel, table, steps.get(stepIndex), featureRegistry, chunkBiomes, owner,
random, decorationSeed, origin, stepIndex);
}
} catch (Throwable error) {
throw new IllegalStateException("Iris imported feature placement failed for chunk "
+ centerPos.x() + "," + centerPos.z(), error);
} finally {
level.setCurrentlyGenerating(null);
}
}
private void placeStep(WorldGenLevel level, FeatureTable table, FeatureSorter.StepFeatureData stepData,
Registry<PlacedFeature> featureRegistry, Set<Holder<Biome>> chunkBiomes,
ChunkGenerator owner, WorldgenRandom random, long decorationSeed,
BlockPos origin, int stepIndex) {
IntSet stepFeatures = new IntArraySet();
for (Holder<Biome> biome : chunkBiomes) {
List<HolderSet<PlacedFeature>> biomeFeatures = settingsFor(biome, table.derivatives()).features();
if (stepIndex >= biomeFeatures.size()) {
continue;
}
for (Holder<PlacedFeature> feature : biomeFeatures.get(stepIndex)) {
stepFeatures.add(stepData.indexMapping().applyAsInt(feature.value()));
}
}
if (stepFeatures.isEmpty()) {
return;
}
// Sorted global indices: identical ordering to vanilla, and each feature's seed comes from its own
// global index, so denying one feature never shifts another.
int[] featureIndices = stepFeatures.toIntArray();
Arrays.sort(featureIndices);
for (int globalIndex : featureIndices) {
PlacedFeature feature = stepData.features().get(globalIndex);
if (table.filtered()) {
Identifier featureId = featureRegistry.getKey(feature);
if (featureId != null && !table.control().shouldGenerate(featureId.toString())) {
continue;
}
}
random.setFeatureSeed(decorationSeed, globalIndex, stepIndex);
level.setCurrentlyGenerating(() -> describeFeature(featureRegistry, feature));
feature.placeWithBiomeCheck(level, owner, random, origin);
}
}
private static String describeFeature(Registry<PlacedFeature> registry, PlacedFeature feature) {
Identifier id = registry.getKey(feature);
return id == null ? feature.toString() : id.toString();
}
private Set<Holder<Biome>> chunkBiomes(
WorldGenLevel level,
SectionPos sectionPos,
FeatureTable table,
DimensionStackContext stackContext
) {
List<Holder<Biome>> collected = new ArrayList<>();
ChunkPos.rangeClosed(sectionPos.chunk(), 1).forEach((ChunkPos chunkPos) -> {
ChunkAccess neighbour = level.getChunk(chunkPos.x(), chunkPos.z());
LevelChunkSection[] sections = neighbour.getSections();
for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {
LevelChunkSection section = sections[sectionIndex];
if (stackContext == null) {
section.getBiomes().getAll(collected::add);
continue;
}
int sectionMinimumY = neighbour.getSectionYFromSectionIndex(sectionIndex) << 4;
for (int quartY = 0; quartY < 4; quartY++) {
int internalY = sectionMinimumY + (quartY << 2) - level.getMinY();
for (int quartX = 0; quartX < 4; quartX++) {
int blockX = chunkPos.getMinBlockX() + (quartX << 2);
for (int quartZ = 0; quartZ < 4; quartZ++) {
int blockZ = chunkPos.getMinBlockZ() + (quartZ << 2);
DimensionStackLayout.Layer layer = stackContext.getLayerAt(
blockX, internalY, blockZ);
if (layer.terrainContext().isSelfReferencing()) {
collected.add(section.getBiomes().get(quartX, quartY, quartZ));
}
}
}
}
}
});
Set<Holder<Biome>> present = new LinkedHashSet<>();
for (Holder<Biome> biome : collected) {
if (table.biomeSet().contains(biome)) {
present.add(biome);
continue;
}
String key = holderKey(biome);
Holder<Biome> canonical = key == null ? null : table.byKey().get(key);
if (canonical != null) {
present.add(canonical);
}
}
return present;
}
private static void addKey(Set<String> keys, String key) {
String normalized = normalizeKey(key);
if (normalized != null) {
keys.add(normalized);
}
}
private String dimensionKey() {
IrisDimension dimension = engine.getDimension();
return dimension == null ? "<unbound>" : dimension.getLoadKey();
}
private static String holderKey(Holder<Biome> holder) {
return holder.unwrapKey()
.map(key -> key.identifier().toString().toLowerCase(Locale.ROOT))
.orElse(null);
}
private static String normalizeKey(String key) {
Identifier identifier = key == null ? null : Identifier.tryParse(key);
return identifier == null ? null : identifier.toString().toLowerCase(Locale.ROOT);
}
private record FeatureTable(IrisDimension dimension, IrisImportedFeatureControl control,
List<Holder<Biome>> biomes, Set<Holder<Biome>> biomeSet,
Map<String, Holder<Biome>> byKey,
List<FeatureSorter.StepFeatureData> steps,
Map<String, Holder<Biome>> derivatives, boolean filtered) {
}
private record RuntimeFeatureState(IrisDimension dimension, FeatureTable featureTable) {
}
}
@@ -1,142 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import ca.spottedleaf.moonrise.common.util.TickThread;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.SectionPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.world.entity.ai.village.poi.PoiRecord;
import net.minecraft.world.entity.ai.village.poi.PoiSection;
import net.minecraft.world.entity.ai.village.poi.PoiType;
import net.minecraft.world.entity.ai.village.poi.PoiTypes;
import net.minecraft.world.level.WorldGenLevel;
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.ImposterProtoChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.ProtoChunk;
import org.bukkit.NamespacedKey;
import org.bukkit.persistence.PersistentDataType;
import java.util.Arrays;
import java.util.Optional;
final class NativeStructurePoiUpdates {
private static final NamespacedKey DIRTY_SECTIONS = new NamespacedKey("iris", "native_poi_sections");
private final int skipPoiFlag;
NativeStructurePoiUpdates(int skipPoiFlag) {
this.skipPoiFlag = skipPoiFlag;
}
static NativeStructurePoiUpdates create() {
try {
return new NativeStructurePoiUpdates(Block.class.getField("UPDATE_SKIP_POI").getInt(null));
} catch (NoSuchFieldException e) {
return null;
} catch (IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
boolean setBlock(WorldGenLevel world, BlockPos position, BlockState state, int flags, int limit) {
if ((flags & skipPoiFlag) != 0) {
return world.setBlock(position, state, flags, limit);
}
ChunkAccess chunk = world.getChunk(position);
if (!(chunk instanceof ProtoChunk)) {
return world.setBlock(position, state, flags, limit);
}
if (!(chunk instanceof ImposterProtoChunk)
&& !PoiTypes.forState(chunk.getBlockState(position)).equals(PoiTypes.forState(state))) {
markSection(chunk, chunk.getSectionIndex(position.getY()));
}
return world.setBlock(position, state, flags | skipPoiFlag, limit);
}
static void reconcile(LevelChunk chunk) {
long[] sections = chunk.persistentDataContainer.get(DIRTY_SECTIONS, PersistentDataType.LONG_ARRAY);
if (sections == null) {
return;
}
validateSections(chunk, sections);
ServerLevel world = (ServerLevel) chunk.getLevel();
TickThread.ensureTickThread(world, chunk.getPos(), "Native structure POIs require the chunk owning thread");
PoiManager manager = world.getPoiManager();
for (int word = 0; word < sections.length; word++) {
long remaining = sections[word];
while (remaining != 0) {
int index = (word << 6) + Long.numberOfTrailingZeros(remaining);
reconcileSection(chunk, manager, index);
remaining &= remaining - 1;
}
}
chunk.persistentDataContainer.remove(DIRTY_SECTIONS);
chunk.markUnsaved();
}
private static void markSection(ChunkAccess chunk, int index) {
long[] sections = chunk.persistentDataContainer.get(DIRTY_SECTIONS, PersistentDataType.LONG_ARRAY);
int word = index >>> 6;
long bit = 1L << (index & 63);
if (sections == null) {
sections = new long[word + 1];
} else {
validateSections(chunk, sections);
if (word < sections.length && (sections[word] & bit) != 0) {
return;
}
if (word >= sections.length) {
sections = Arrays.copyOf(sections, word + 1);
}
}
sections[word] |= bit;
chunk.persistentDataContainer.set(DIRTY_SECTIONS, PersistentDataType.LONG_ARRAY, sections);
}
private static void validateSections(ChunkAccess chunk, long[] sections) {
int count = chunk.getSectionsCount();
int words = (count + 63) >>> 6;
if (sections.length > words
|| sections.length == words && (count & 63) != 0
&& (sections[words - 1] >>> (count & 63)) != 0) {
throw new IllegalArgumentException("Native POI repair sections exceed chunk height at " + chunk.getPos());
}
}
private static void reconcileSection(LevelChunk chunk, PoiManager manager, int index) {
SectionPos sectionPosition = SectionPos.of(chunk.getPos(), chunk.getSectionYFromSectionIndex(index));
Optional<PoiSection> existing = manager.getOrLoad(sectionPosition.asLong());
if (existing.isPresent()) {
for (PoiRecord record : existing.get().getRecords(type -> true, PoiManager.Occupancy.ANY).toList()) {
if (!PoiTypes.forState(chunk.getBlockState(record.getPos())).equals(Optional.of(record.getPoiType()))) {
manager.remove(record.getPos());
}
}
}
LevelChunkSection section = chunk.getSection(index);
if (!section.maybeHas(PoiTypes::hasPoi)) {
return;
}
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
Optional<Holder<PoiType>> type = PoiTypes.forState(section.getBlockState(x, y, z));
if (type.isEmpty()) {
continue;
}
position.set(sectionPosition.minBlockX() + x, sectionPosition.minBlockY() + y,
sectionPosition.minBlockZ() + z);
if (!manager.getType(position).equals(type)) {
manager.add(position.immutable(), type.get());
}
}
}
}
}
}
@@ -1,606 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.nativegen.WorldgenTerrainHeightmaps;
import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.QuartPos;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.attribute.EnvironmentAttributeReader;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.EmptyLevelChunk;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.ticks.LevelTickAccess;
import net.minecraft.world.ticks.ScheduledTick;
import org.bukkit.event.entity.CreatureSpawnEvent;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.IntBinaryOperator;
import java.util.function.Predicate;
import java.util.function.Supplier;
final class NativeStructureWorldgenAccess implements WorldGenLevel {
private static final int WRITE_RADIUS = 1;
private static final NativeStructurePoiUpdates POI_UPDATES = NativeStructurePoiUpdates.create();
private final WorldGenLevel delegate;
private final ChunkPos generationCenter;
private final IntBinaryOperator surfaceFirstFreeY;
private final IntBinaryOperator floorFirstFreeY;
private final boolean virtualizeHeights;
private final Predicate<BlockPos> protectedPosition;
private final Holder<Biome> fallbackBiome;
private final BiomeManager biomeManager;
private final AABB generationBounds;
private final LevelTickAccess<Block> blockTicks;
private final LevelTickAccess<Fluid> fluidTicks;
private final Long2LongOpenHashMap terrainHeights;
private final Long2ObjectOpenHashMap<ChunkAccess> outsideChunks;
private NativeStructureWorldgenAccess(WorldGenLevel delegate, Boundary boundary) {
this.delegate = Objects.requireNonNull(delegate, "Native structure world access requires a delegate");
this.generationCenter = Objects.requireNonNull(
boundary.generationCenter(), "Native structure world access requires a generation center");
this.surfaceFirstFreeY = Objects.requireNonNull(
boundary.surfaceFirstFreeY(), "Native structure world access requires a surface resolver");
this.floorFirstFreeY = Objects.requireNonNull(
boundary.floorFirstFreeY(), "Native structure world access requires an ocean-floor resolver");
this.virtualizeHeights = boundary.virtualizeHeights();
this.protectedPosition = Objects.requireNonNull(
boundary.protectedPosition(), "World access requires a protected-position predicate");
BlockPos biomeSample = generationCenter.getMiddleBlockPosition(delegate.getSeaLevel());
this.fallbackBiome = delegate.getBiome(biomeSample);
this.biomeManager = delegate.getBiomeManager().withDifferentSource(this);
this.generationBounds = new AABB(
generationCenter.getMinBlockX() - 16,
delegate.getMinY(),
generationCenter.getMinBlockZ() - 16,
generationCenter.getMaxBlockX() + 17,
delegate.getMinY() + delegate.getHeight(),
generationCenter.getMaxBlockZ() + 17);
this.blockTicks = new BoundedTickAccess<>(delegate.getBlockTicks(), this::isWritable);
this.fluidTicks = new BoundedTickAccess<>(delegate.getFluidTicks(), this::isWritable);
this.terrainHeights = new Long2LongOpenHashMap();
this.terrainHeights.defaultReturnValue(Long.MIN_VALUE);
this.outsideChunks = new Long2ObjectOpenHashMap<>();
}
static NativeStructureWorldgenAccess create(WorldGenLevel delegate, ChunkPos generationCenter,
IntBinaryOperator surfaceFirstFreeY,
IntBinaryOperator floorFirstFreeY,
boolean virtualizeHeights,
Predicate<BlockPos> protectedPosition) {
return new NativeStructureWorldgenAccess(delegate, new Boundary(
generationCenter, surfaceFirstFreeY, floorFirstFreeY, virtualizeHeights, protectedPosition));
}
@Override
public long getSeed() {
return delegate.getSeed();
}
@Override
public boolean ensureCanWrite(BlockPos position) {
return isWritable(position) && delegate.ensureCanWrite(position);
}
@Override
public void setCurrentlyGenerating(Supplier<String> description) {
delegate.setCurrentlyGenerating(description);
}
@Override
public ServerLevel getLevel() {
return delegate.getLevel();
}
@Override
public DifficultyInstance getCurrentDifficultyAt(BlockPos position) {
if (isReadable(position)) {
return delegate.getCurrentDifficultyAt(position);
}
return delegate.getCurrentDifficultyAt(generationCenter.getMiddleBlockPosition(
Mth.clamp(position.getY(), getMinY(), getMaxY() - 1)));
}
@Override
public long nextSubTickCount() {
return delegate.nextSubTickCount();
}
@Override
public LevelTickAccess<Block> getBlockTicks() {
return blockTicks;
}
@Override
public LevelTickAccess<Fluid> getFluidTicks() {
return fluidTicks;
}
@Override
public LevelData getLevelData() {
return delegate.getLevelData();
}
@Override
public MinecraftServer getServer() {
return delegate.getServer();
}
@Override
public ChunkSource getChunkSource() {
return delegate.getChunkSource();
}
@Override
public RandomSource getRandom() {
return delegate.getRandom();
}
@Override
public void updateNeighborsAt(BlockPos position, Block block) {
if (isWritableNeighbourhood(position)) {
delegate.updateNeighborsAt(position, block);
}
}
@Override
public void neighborShapeChanged(Direction direction, BlockPos position,
BlockPos neighbourPosition, BlockState neighbourState,
int updateFlags, int updateLimit) {
if (isWritable(position) && isWritable(neighbourPosition)) {
delegate.neighborShapeChanged(
direction, position, neighbourPosition, neighbourState, updateFlags, updateLimit);
}
}
@Override
public void playSound(Entity source, BlockPos position, SoundEvent sound,
SoundSource soundSource, float volume, float pitch) {
if (isWritable(position)) {
delegate.playSound(source, position, sound, soundSource, volume, pitch);
}
}
@Override
public void addParticle(ParticleOptions particle, double x, double y, double z,
double velocityX, double velocityY, double velocityZ) {
if (isWritable(BlockPos.containing(x, y, z))) {
delegate.addParticle(particle, x, y, z, velocityX, velocityY, velocityZ);
}
}
@Override
public void levelEvent(Entity source, int eventId, BlockPos position, int data) {
if (isWritable(position)) {
delegate.levelEvent(source, eventId, position, data);
}
}
@Override
public void gameEvent(Holder<GameEvent> event, Vec3 position, GameEvent.Context context) {
if (isWritable(BlockPos.containing(position))) {
delegate.gameEvent(event, position, context);
}
}
@Override
public ChunkAccess getChunk(int chunkX, int chunkZ) {
if (isInsideGenerationRegion(chunkX, chunkZ)) {
return delegate.getChunk(chunkX, chunkZ);
}
return outsideChunk(chunkX, chunkZ);
}
@Override
public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus status, boolean create) {
if (isInsideGenerationRegion(chunkX, chunkZ)) {
return delegate.getChunk(chunkX, chunkZ, status, create);
}
return create ? outsideChunk(chunkX, chunkZ) : null;
}
@Override
public ChunkAccess getChunkIfLoadedImmediately(int chunkX, int chunkZ) {
if (!isInsideGenerationRegion(chunkX, chunkZ)) {
return null;
}
return delegate.getChunkIfLoadedImmediately(chunkX, chunkZ);
}
@Override
public boolean hasChunk(int chunkX, int chunkZ) {
return isInsideGenerationRegion(chunkX, chunkZ) && delegate.hasChunk(chunkX, chunkZ);
}
@Override
public int getHeight(Heightmap.Types type, int x, int z) {
if (!virtualizeHeights && isInsideGenerationRegion(x >> 4, z >> 4)) {
return delegate.getHeight(type, x, z);
}
return height(type, x, z);
}
@Override
public int getSkyDarken() {
return delegate.getSkyDarken();
}
@Override
public BiomeManager getBiomeManager() {
return biomeManager;
}
@Override
public Holder<Biome> getNoiseBiome(int quartX, int quartY, int quartZ) {
return getUncachedNoiseBiome(quartX, quartY, quartZ);
}
@Override
public Holder<Biome> getUncachedNoiseBiome(int quartX, int quartY, int quartZ) {
if (isInsideGenerationRegion(QuartPos.toSection(quartX), QuartPos.toSection(quartZ))) {
return delegate.getUncachedNoiseBiome(quartX, quartY, quartZ);
}
return fallbackBiome;
}
@Override
public boolean isClientSide() {
return delegate.isClientSide();
}
@Override
public int getSeaLevel() {
return delegate.getSeaLevel();
}
@Override
public DimensionType dimensionType() {
return delegate.dimensionType();
}
@Override
public int getMinY() {
return delegate.getMinY();
}
@Override
public int getHeight() {
return delegate.getHeight();
}
@Override
public RegistryAccess registryAccess() {
return delegate.registryAccess();
}
@Override
public FeatureFlagSet enabledFeatures() {
return delegate.enabledFeatures();
}
@Override
public EnvironmentAttributeReader environmentAttributes() {
return delegate.environmentAttributes();
}
@Override
public LevelLightEngine getLightEngine() {
return delegate.getLightEngine();
}
@Override
public int getBrightness(LightLayer layer, BlockPos position) {
if (isReadable(position)) {
return delegate.getBrightness(layer, position);
}
return layer == LightLayer.SKY && canSeeSky(position) ? 15 : 0;
}
@Override
public int getRawBrightness(BlockPos position, int ambientDarkening) {
if (isReadable(position)) {
return delegate.getRawBrightness(position, ambientDarkening);
}
return Math.max(0, getBrightness(LightLayer.SKY, position) - ambientDarkening);
}
@Override
public boolean canSeeSky(BlockPos position) {
if (isReadable(position)) {
return delegate.canSeeSky(position);
}
return position.getY() >= height(Heightmap.Types.WORLD_SURFACE_WG, position.getX(), position.getZ());
}
@Override
public WorldBorder getWorldBorder() {
return delegate.getWorldBorder();
}
@Override
public BlockGetter getChunkForCollisions(int chunkX, int chunkZ) {
if (isInsideGenerationRegion(chunkX, chunkZ)) {
return delegate.getChunkForCollisions(chunkX, chunkZ);
}
return outsideChunk(chunkX, chunkZ);
}
@Override
public BlockEntity getBlockEntity(BlockPos position) {
return isWritable(position) ? delegate.getBlockEntity(position) : null;
}
@Override
public <T extends BlockEntity> Optional<T> getBlockEntity(
BlockPos position, BlockEntityType<T> type) {
return isWritable(position) ? delegate.getBlockEntity(position, type) : Optional.empty();
}
@Override
public BlockState getBlockState(BlockPos position) {
return isReadable(position) ? delegate.getBlockState(position) : terrainState(position);
}
@Override
public BlockState getBlockStateIfLoaded(BlockPos position) {
return isReadable(position) ? delegate.getBlockStateIfLoaded(position) : null;
}
@Override
public FluidState getFluidState(BlockPos position) {
return isReadable(position) ? delegate.getFluidState(position) : terrainState(position).getFluidState();
}
@Override
public FluidState getFluidIfLoaded(BlockPos position) {
return isReadable(position) ? delegate.getFluidIfLoaded(position) : null;
}
@Override
public List<Entity> getEntities(Entity source, AABB area, Predicate<? super Entity> predicate) {
AABB boundedArea = boundedArea(area);
return boundedArea == null ? List.of() : delegate.getEntities(source, boundedArea, predicate);
}
@Override
public <T extends Entity> List<T> getEntities(
EntityTypeTest<Entity, T> type,
AABB area, Predicate<? super T> predicate) {
AABB boundedArea = boundedArea(area);
return boundedArea == null ? List.of() : delegate.getEntities(type, boundedArea, predicate);
}
@Override
public List<? extends Player> players() {
return delegate.players();
}
@Override
public boolean isStateAtPosition(BlockPos position, Predicate<BlockState> predicate) {
return predicate.test(getBlockState(position));
}
@Override
public boolean isFluidAtPosition(BlockPos position, Predicate<FluidState> predicate) {
return predicate.test(getFluidState(position));
}
@Override
public BlockPos getHeightmapPos(Heightmap.Types type, BlockPos position) {
return position.atY(getHeight(type, position.getX(), position.getZ()));
}
@Override
public boolean setBlock(BlockPos position, BlockState state, int updateFlags, int updateLimit) {
return isWritable(position)
&& (POI_UPDATES != null
? POI_UPDATES.setBlock(delegate, position, state, updateFlags, updateLimit)
: delegate.setBlock(position, state, updateFlags, updateLimit));
}
@Override
public boolean removeBlock(BlockPos position, boolean move) {
if (!isWritable(position)) {
return false;
}
return POI_UPDATES != null && delegate instanceof WorldGenRegion
? setBlock(position, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL, Block.UPDATE_LIMIT)
: delegate.removeBlock(position, move);
}
@Override
public boolean destroyBlock(BlockPos position, boolean drop, Entity source, int updateLimit) {
if (!isWritable(position)) {
return false;
}
return POI_UPDATES != null && delegate instanceof WorldGenRegion
? !delegate.getBlockState(position).isAir()
&& setBlock(position, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL, updateLimit)
: delegate.destroyBlock(position, drop, source, updateLimit);
}
@Override
public boolean addFreshEntity(Entity entity) {
return isWritable(entity.blockPosition()) && delegate.addFreshEntity(entity);
}
@Override
public boolean addFreshEntity(Entity entity, CreatureSpawnEvent.SpawnReason reason) {
return isWritable(entity.blockPosition()) && delegate.addFreshEntity(entity, reason);
}
private boolean isReadable(BlockPos position) {
return isInsideGenerationRegion(position.getX() >> 4, position.getZ() >> 4)
&& !isOutsideBuildHeight(position);
}
private boolean isWritable(BlockPos position) {
return isReadable(position) && !protectedPosition.test(position);
}
private boolean isWritableNeighbourhood(BlockPos position) {
return isWritable(position)
&& isWritable(position.north())
&& isWritable(position.south())
&& isWritable(position.east())
&& isWritable(position.west())
&& isWritable(position.above())
&& isWritable(position.below());
}
private AABB boundedArea(AABB area) {
double minX = Math.max(area.minX, generationBounds.minX);
double minY = Math.max(area.minY, generationBounds.minY);
double minZ = Math.max(area.minZ, generationBounds.minZ);
double maxX = Math.min(area.maxX, generationBounds.maxX);
double maxY = Math.min(area.maxY, generationBounds.maxY);
double maxZ = Math.min(area.maxZ, generationBounds.maxZ);
if (minX >= maxX || minY >= maxY || minZ >= maxZ) {
return null;
}
if (minX == area.minX && minY == area.minY && minZ == area.minZ
&& maxX == area.maxX && maxY == area.maxY && maxZ == area.maxZ) {
return area;
}
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
boolean isInsideGenerationRegion(int chunkX, int chunkZ) {
return Math.abs(chunkX - generationCenter.x()) <= WRITE_RADIUS
&& Math.abs(chunkZ - generationCenter.z()) <= WRITE_RADIUS;
}
@Override
public boolean isOutsideBuildHeight(BlockPos position) {
return position.getY() < getMinY() || position.getY() >= getMaxY();
}
private BlockState terrainState(BlockPos position) {
if (isOutsideBuildHeight(position)) {
return Blocks.VOID_AIR.defaultBlockState();
}
long heights = heights(position.getX(), position.getZ());
int surface = (int) (heights >> 32);
int floor = (int) heights;
if (position.getY() < floor) {
return Blocks.STONE.defaultBlockState();
}
if (position.getY() < surface) {
return Blocks.WATER.defaultBlockState();
}
return Blocks.AIR.defaultBlockState();
}
private int height(Heightmap.Types type, int x, int z) {
long heights = heights(x, z);
if (type == Heightmap.Types.OCEAN_FLOOR || type == Heightmap.Types.OCEAN_FLOOR_WG) {
return (int) heights;
}
return (int) (heights >> 32);
}
private long heights(int x, int z) {
long key = ((long) x << 32) ^ (z & 0xffffffffL);
long cached = terrainHeights.get(key);
if (cached != Long.MIN_VALUE) {
return cached;
}
int floor = Mth.clamp(floorFirstFreeY.applyAsInt(x, z), getMinY(), getMaxY());
int surface = Mth.clamp(surfaceFirstFreeY.applyAsInt(x, z), floor, getMaxY());
long heights = ((long) surface << 32) | (floor & 0xffffffffL);
terrainHeights.put(key, heights);
return heights;
}
private ChunkAccess outsideChunk(int chunkX, int chunkZ) {
long key = ChunkPos.pack(chunkX, chunkZ);
ChunkAccess cached = outsideChunks.get(key);
if (cached != null) {
return cached;
}
EmptyLevelChunk chunk = new EmptyLevelChunk(getLevel(), new ChunkPos(chunkX, chunkZ), fallbackBiome);
WorldgenTerrainHeightmaps.primeTerrain(chunk, surfaceFirstFreeY, floorFirstFreeY);
outsideChunks.put(key, chunk);
return chunk;
}
private record Boundary(ChunkPos generationCenter,
IntBinaryOperator surfaceFirstFreeY,
IntBinaryOperator floorFirstFreeY,
boolean virtualizeHeights,
Predicate<BlockPos> protectedPosition) {
}
private static final class BoundedTickAccess<T> implements LevelTickAccess<T> {
private final LevelTickAccess<T> delegate;
private final Predicate<BlockPos> writable;
private BoundedTickAccess(LevelTickAccess<T> delegate, Predicate<BlockPos> writable) {
this.delegate = delegate;
this.writable = writable;
}
@Override
public void schedule(ScheduledTick<T> tick) {
if (writable.test(tick.pos())) {
delegate.schedule(tick);
}
}
@Override
public boolean hasScheduledTick(BlockPos position, T type) {
return writable.test(position) && delegate.hasScheduledTick(position, type);
}
@Override
public int count() {
return delegate.count();
}
@Override
public boolean willTickThisTick(BlockPos position, T type) {
return writable.test(position) && delegate.willTickThisTick(position, type);
}
}
}
@@ -1,61 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.platform.agent.Agent;
import it.unimi.dsi.fastutil.shorts.ShortList;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.ChunkPos;
import java.util.function.LongPredicate;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import java.util.List;
final class NmsGenerationHooks {
private NmsGenerationHooks() {
}
static void install() {
ByteBuddy buddy = new ByteBuddy();
for (Class<?> clazz : List.of(ChunkAccess.class, ProtoChunk.class)) {
buddy.redefine(clazz)
.visit(Advice.to(ChunkAccessAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(ShortList.class, int.class))))
.make()
.load(clazz.getClassLoader(), Agent.installed());
}
buddy.redefine(WorldGenRegion.class)
.visit(Advice.to(WorldGenerationWriteAdvice.class).on(ElementMatchers.named("ensureCanWrite")
.and(ElementMatchers.takesArguments(BlockPos.class))))
.make()
.load(WorldGenRegion.class.getClassLoader(), Agent.installed());
}
private static class WorldGenerationWriteAdvice {
@Advice.OnMethodExit
static void exit(@Advice.This WorldGenRegion region, @Advice.Argument(0) BlockPos position,
@Advice.Return(readOnly = false) boolean allowed) {
if (!allowed) {
return;
}
ChunkGenerator generator = region.getLevel().getChunkSource().getGenerator();
if (generator instanceof LongPredicate predicate
&& generator.getClass().getName().equals("art.arcane.iris.platform.bukkit.nms.v26_2_R1.IrisChunkGenerator")
&& region.getChunk(position.getX() >> 4, position.getZ() >> 4)
.getPersistedStatus().isOrAfter(ChunkStatus.FULL)) {
allowed = predicate.test(ChunkPos.pack(position.getX() >> 4, position.getZ() >> 4));
}
}
}
private static class ChunkAccessAdvice {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean enter(@Advice.This ChunkAccess access, @Advice.Argument(1) int index) {
return index >= access.getPostProcessing().length;
}
}
}
@@ -1,182 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.spi.PlatformGenerationRegistry;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.dimension.DimensionType;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Supplier;
final class NmsGenerationRegistry implements PlatformGenerationRegistry {
private static final String BIOME_REGISTRY = "minecraft:worldgen/biome";
private static final String STRUCTURE_REGISTRY = "minecraft:worldgen/structure";
private static final String DIMENSION_TYPE_REGISTRY = "minecraft:dimension_type";
private static final String BLOCK_REGISTRY = "minecraft:block";
private static final String ENTITY_TYPE_REGISTRY = "minecraft:entity_type";
private final Supplier<RegistryAccess> registryAccess;
private final String runtimeIdentity;
private final String rendererIdentity;
NmsGenerationRegistry(
Supplier<RegistryAccess> registryAccess,
String runtimeIdentity,
String rendererIdentity
) {
this.registryAccess = Objects.requireNonNull(registryAccess, "registryAccess");
this.runtimeIdentity = requireText(runtimeIdentity, "runtimeIdentity");
this.rendererIdentity = requireText(rendererIdentity, "rendererIdentity");
}
@Override
public String runtimeIdentity() {
return runtimeIdentity;
}
@Override
public String generatedDefinitionRendererIdentity() {
return rendererIdentity;
}
@Override
public String customBiomeResourceKey(String identitySha256) {
return PlatformGenerationRegistry.contentAddressedCustomBiomeResourceKey(identitySha256);
}
@Override
public List<String> legacyCustomBiomeResourceKeys(
String packName,
String dimensionKey,
String customBiomeId
) {
return List.of(requireText(dimensionKey, "dimensionKey").toLowerCase(Locale.ROOT)
+ ":" + requireText(customBiomeId, "customBiomeId").toLowerCase(Locale.ROOT));
}
@Override
public String dimensionTypeResourceKey(String packName, String dimensionKey, String dimensionTypeKey) {
return "iris:" + requireText(dimensionTypeKey, "dimensionTypeKey").toLowerCase(Locale.ROOT);
}
@Override
public Definition definition(String registryKey, String resourceKey) {
RegistryAccess access = Objects.requireNonNull(registryAccess.get(), "Minecraft registry access");
return switch (registryKey) {
case BIOME_REGISTRY -> presence(access, Registries.BIOME, registryKey, resourceKey);
case STRUCTURE_REGISTRY -> presence(access, Registries.STRUCTURE, registryKey, resourceKey);
case DIMENSION_TYPE_REGISTRY -> presence(
access,
Registries.DIMENSION_TYPE,
registryKey,
resourceKey
);
case BLOCK_REGISTRY -> presence(access, Registries.BLOCK, registryKey, resourceKey);
case ENTITY_TYPE_REGISTRY -> presence(access, Registries.ENTITY_TYPE, registryKey, resourceKey);
default -> throw new IllegalArgumentException("Unsupported generation registry: " + registryKey);
};
}
@Override
public Definition canonicalDefinition(String registryKey, String resourceKey, String sourceJson) {
RegistryAccess access = Objects.requireNonNull(registryAccess.get(), "Minecraft registry access");
return switch (registryKey) {
case BIOME_REGISTRY -> canonicalize(access, Biome.DIRECT_CODEC, sourceJson);
case DIMENSION_TYPE_REGISTRY -> canonicalize(access, DimensionType.DIRECT_CODEC, sourceJson);
default -> throw new IllegalArgumentException(
"Registry does not accept generated JSON definitions: " + registryKey
);
};
}
@Override
public Definition generatedDefinition(String registryKey, String resourceKey) {
RegistryAccess access = Objects.requireNonNull(registryAccess.get(), "Minecraft registry access");
return switch (registryKey) {
case BIOME_REGISTRY -> exactRegistered(
access,
Registries.BIOME,
Biome.DIRECT_CODEC,
resourceKey
);
case DIMENSION_TYPE_REGISTRY -> exactRegistered(
access,
Registries.DIMENSION_TYPE,
DimensionType.DIRECT_CODEC,
resourceKey
);
default -> throw new IllegalArgumentException(
"Registry does not contain Iris-generated definitions: " + registryKey
);
};
}
private static <T> Definition presence(
RegistryAccess access,
ResourceKey<? extends Registry<T>> registryKey,
String registryResourceKey,
String resourceKey
) {
Registry<T> registry = access.lookupOrThrow(registryKey);
Identifier identifier = requireIdentifier(resourceKey);
T value = registry.getValue(identifier);
if (value == null) {
throw new IllegalStateException("Missing registry definition " + registryKey.identifier()
+ " / " + resourceKey + ".");
}
return Definition.resourceIdentity(registryResourceKey, resourceKey);
}
private static <T> Definition canonicalize(RegistryAccess access, Codec<T> codec, String sourceJson) {
RegistryOps<JsonElement> ops = RegistryOps.create(JsonOps.INSTANCE, access);
JsonElement source = JsonParser.parseString(Objects.requireNonNull(sourceJson, "sourceJson"));
T decoded = codec.parse(ops, source).getOrThrow(IllegalArgumentException::new);
JsonElement encoded = codec.encodeStart(ops, decoded).getOrThrow(IllegalStateException::new);
return Definition.exactJson(encoded.toString());
}
private static <T> Definition exactRegistered(
RegistryAccess access,
ResourceKey<? extends Registry<T>> registryKey,
Codec<T> codec,
String resourceKey
) {
Registry<T> registry = access.lookupOrThrow(registryKey);
Identifier identifier = requireIdentifier(resourceKey);
T value = registry.getValue(identifier);
if (value == null) {
throw new IllegalStateException("Missing generated registry definition "
+ registryKey.identifier() + " / " + resourceKey + ".");
}
RegistryOps<JsonElement> ops = RegistryOps.create(JsonOps.INSTANCE, access);
JsonElement encoded = codec.encodeStart(ops, value).getOrThrow(IllegalStateException::new);
return Definition.exactJson(encoded.toString());
}
private static Identifier requireIdentifier(String resourceKey) {
Identifier identifier = Identifier.tryParse(resourceKey);
if (identifier == null) {
throw new IllegalArgumentException("Invalid registry resource key: " + resourceKey);
}
return identifier;
}
private static String requireText(String value, String label) {
String required = Objects.requireNonNull(value, label).trim();
if (required.isEmpty()) {
throw new IllegalArgumentException(label + " must not be blank.");
}
return required;
}
}
@@ -1,266 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.world.history.NativeTerrainReceipt;
import art.arcane.iris.world.history.SavedTerrainChunk;
import art.arcane.iris.world.task.J;
import ca.spottedleaf.concurrentutil.completable.Completable;
import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
import ca.spottedleaf.concurrentutil.util.Priority;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import org.bukkit.World;
import org.bukkit.craftbukkit.CraftWorld;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.LockSupport;
final class NmsSavedTerrainCapture {
private static final long WRITE_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(30L);
private static final int SNAPSHOT_BATCH_SIZE = 64;
private NmsSavedTerrainCapture() {
}
static CompletableFuture<SavedTerrainChunk> capture(World world, int chunkX, int chunkZ, int minimumY, int height) {
ServerLevel level = ((CraftWorld) world).getHandle();
CompletableFuture<CapturedChunk> snapshot = new CompletableFuture<>();
J.runRegionFuture(world, chunkX, chunkZ, () -> {
try {
NewChunkHolder holder = level.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkX, chunkZ);
ChunkAccess chunk = holder == null ? null : holder.getCurrentChunk();
if (chunk == null) {
snapshot.complete(new CapturedChunk(new ChunkPos(chunkX, chunkZ), CompletableFuture.completedFuture(null)));
return;
}
String status = BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getPersistedStatus()).toString();
if (!SavedTerrainChunk.hasTerrain(status)) {
throw new IOException("Native chunk " + chunkX + "," + chunkZ + " has no completed terrain at " + status);
}
snapshot.complete(scheduleSnapshot(level, chunk));
} catch (Throwable failure) {
snapshot.completeExceptionally(failure);
}
}).whenComplete((ignored, failure) -> {
if (failure != null) {
snapshot.completeExceptionally(failure);
}
});
return snapshot.thenApplyAsync(captured -> {
try {
long deadline = System.nanoTime() + WRITE_TIMEOUT_NANOS;
CompoundTag data = awaitSnapshot(level, captured, deadline);
Checkpoint checkpoint = checkpoint(chunkX, chunkZ, data);
awaitWrite(level, checkpoint.chunk(), deadline);
if (data == null) {
return SavedTerrainChunk.read(world.getWorldFolder().toPath(), chunkX, chunkZ, minimumY, height);
}
verifyCheckpoint(world.getWorldFolder().toPath(), checkpoint);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (DataOutputStream output = new DataOutputStream(bytes)) {
NbtIo.write(data, output);
}
return SavedTerrainChunk.readNbt(bytes.toByteArray(), chunkX, chunkZ, minimumY, height);
} catch (IOException failure) {
throw new CompletionException(failure);
}
});
}
static CompletableFuture<Void> flush(World world) {
ServerLevel level = ((CraftWorld) world).getHandle();
List<NewChunkHolder> holders = level.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolders();
return CompletableFuture.runAsync(() -> {
try {
flushSnapshots(world, level, holders);
} catch (IOException failure) {
throw new CompletionException(failure);
}
});
}
static void flushSnapshots(World world, ServerLevel level, List<NewChunkHolder> holders) throws IOException {
long deadline = System.nanoTime() + WRITE_TIMEOUT_NANOS;
List<Checkpoint> checkpoints = new ArrayList<>(holders.size());
for (int offset = 0; offset < holders.size(); offset += SNAPSHOT_BATCH_SIZE) {
int end = Math.min(holders.size(), offset + SNAPSHOT_BATCH_SIZE);
List<CompletableFuture<CapturedChunk>> snapshots = requestSnapshots(world, level, holders.subList(offset, end));
awaitSnapshots(snapshots, deadline);
for (CompletableFuture<CapturedChunk> future : snapshots) {
CapturedChunk captured = future.join();
CompoundTag data = awaitSnapshot(level, captured, deadline);
checkpoints.add(checkpoint(captured.chunk().x(), captured.chunk().z(), data));
}
for (int index = offset; index < end; index++) {
awaitWrite(level, checkpoints.get(index).chunk(), deadline);
}
}
finishCheckpoint(level, world.getWorldFolder().toPath(), checkpoints);
}
private static List<CompletableFuture<CapturedChunk>> requestSnapshots(
World world, ServerLevel level, List<NewChunkHolder> holders) {
List<CompletableFuture<CapturedChunk>> snapshots = new ArrayList<>(holders.size());
for (NewChunkHolder original : holders) {
ChunkPos position = new ChunkPos(original.chunkX, original.chunkZ);
CompletableFuture<CapturedChunk> snapshot = new CompletableFuture<>();
snapshots.add(snapshot);
J.runRegionFuture(world, position.x(), position.z(), () -> {
try {
NewChunkHolder holder = level.moonrise$getChunkTaskScheduler().chunkHolderManager
.getChunkHolder(position.x(), position.z());
ChunkAccess chunk = holder == null ? null : holder.getCurrentChunk();
snapshot.complete(chunk != null && chunk.isUnsaved() ? scheduleSnapshot(level, chunk)
: new CapturedChunk(position, CompletableFuture.completedFuture(null)));
} catch (Throwable failure) {
snapshot.completeExceptionally(failure);
}
}).whenComplete((ignored, failure) -> {
if (failure != null) {
snapshot.completeExceptionally(failure);
}
});
}
return snapshots;
}
private static void awaitSnapshots(List<CompletableFuture<CapturedChunk>> snapshots, long deadlineNanos)
throws IOException {
try {
CompletableFuture.allOf(snapshots.toArray(CompletableFuture[]::new))
.get(Math.max(0L, deadlineNanos - System.nanoTime()), TimeUnit.NANOSECONDS);
} catch (InterruptedException failure) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted while copying terrain checkpoint chunks.", failure);
} catch (ExecutionException | TimeoutException failure) {
throw new IOException("Unable to copy terrain checkpoint chunks.", failure);
}
}
static void finishCheckpoint(ServerLevel level, Path worldFolder, List<Checkpoint> checkpoints) throws IOException {
long deadline = System.nanoTime() + WRITE_TIMEOUT_NANOS;
for (Checkpoint checkpoint : checkpoints) {
awaitWrite(level, checkpoint.chunk(), deadline);
}
MoonriseRegionFileIO.flushRegionStorages(level, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA);
for (Checkpoint checkpoint : checkpoints) {
verifyCheckpoint(worldFolder, checkpoint);
}
}
static Checkpoint checkpoint(int chunkX, int chunkZ, CompoundTag data) {
if (data == null) {
return new Checkpoint(new ChunkPos(chunkX, chunkZ), null);
}
byte[] receipt = data.getCompound("ChunkBukkitValues")
.flatMap(values -> values.getByteArray(NativeTerrainReceipt.NBT_KEY)).orElse(null);
long structureActivation = data.getCompound("ChunkBukkitValues").map(values -> values.getLongOr(
NativeTerrainReceipt.STRUCTURE_ACTIVATION_KEY, 0)).orElse(0L);
return new Checkpoint(new ChunkPos(chunkX, chunkZ), new Verification(
data.getStringOr("Status", ""), receipt, structureActivation));
}
private static void verifyCheckpoint(Path worldFolder, Checkpoint checkpoint) throws IOException {
Verification expected = checkpoint.verification();
if (expected == null) {
return;
}
SavedTerrainChunk.verifyCheckpoint(worldFolder, checkpoint.chunk().x(), checkpoint.chunk().z(),
expected.status(), expected.receipt(), expected.structureActivation());
}
static void awaitWrite(ServerLevel level, ChunkPos chunk, long deadlineNanos) throws IOException {
ChunkTaskScheduler scheduler = level.moonrise$getChunkTaskScheduler();
while (true) {
checkWriteWait(chunk, deadlineNanos);
if (MoonriseRegionFileIO.getPriority(level, chunk.x(), chunk.z(),
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA) == Priority.COMPLETING) {
return;
}
helpSaveTasks(scheduler);
}
}
static CapturedChunk scheduleSnapshot(ServerLevel level, ChunkAccess chunk) {
SerializableChunkData snapshot = SerializableChunkData.copyOf(level, chunk);
PlatformHooks.get().chunkSyncSave(level, chunk, snapshot);
Completable<CompoundTag> nativeData = new Completable<>();
CompletableFuture<CompoundTag> data = new CompletableFuture<>();
PrioritisedExecutor.PrioritisedTask task = level.moonrise$getChunkTaskScheduler().saveExecutor
.createTask(() -> serializeSnapshot(snapshot, nativeData, data));
task.queue();
ChunkPos position = chunk.getPos();
MoonriseRegionFileIO.scheduleSave(level, position.x(), position.z(), nativeData, task,
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, Priority.NORMAL);
return new CapturedChunk(position, data);
}
static CompoundTag awaitSnapshot(ServerLevel level, CapturedChunk captured, long deadlineNanos) throws IOException {
ChunkTaskScheduler scheduler = level.moonrise$getChunkTaskScheduler();
while (!captured.data().isDone()) {
checkWriteWait(captured.chunk(), deadlineNanos);
helpSaveTasks(scheduler);
}
try {
return captured.data().join();
} catch (CompletionException failure) {
throw new IOException("Unable to serialize terrain checkpoint chunk " + captured.chunk(), failure.getCause());
}
}
private static void serializeSnapshot(SerializableChunkData snapshot, Completable<CompoundTag> nativeData,
CompletableFuture<CompoundTag> data) {
try {
CompoundTag serialized = snapshot.write();
nativeData.complete(serialized);
data.complete(serialized);
} catch (Throwable failure) {
nativeData.completeExceptionally(failure);
data.completeExceptionally(failure);
}
}
private static void checkWriteWait(ChunkPos chunk, long deadlineNanos) throws IOException {
if (Thread.currentThread().isInterrupted()) {
throw new IOException("Interrupted while saving terrain checkpoint chunk " + chunk);
}
if (System.nanoTime() - deadlineNanos >= 0L) {
throw new IOException("Timed out saving terrain checkpoint chunk " + chunk);
}
}
private static void helpSaveTasks(ChunkTaskScheduler scheduler) {
boolean helped = scheduler.saveExecutor.executeTask();
helped |= scheduler.compressionExecutor.executeTask();
if (!helped) {
LockSupport.parkNanos(1_000_000L);
}
}
record CapturedChunk(ChunkPos chunk, CompletableFuture<CompoundTag> data) {
}
record Checkpoint(ChunkPos chunk, Verification verification) {
}
record Verification(String status, byte[] receipt, long structureActivation) {
}
}
@@ -1,403 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.platform.bukkit.nms.ServerShutdownBoundary;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.localization.C;
import art.arcane.iris.platform.agent.Agent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.ResettableClassFileTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicBoolean;
final class NmsWorldLifecycle {
private final AtomicBoolean injected = new AtomicBoolean();
private volatile ResettableClassFileTransformer levelStorageAccessTransformer;
private volatile ResettableClassFileTransformer serverLevelTransformer;
private volatile ResettableClassFileTransformer pluginClassLoaderTransformer;
private boolean pluginClassLoaderCloseDeferred;
public boolean injectBukkit() {
synchronized (injected) {
if (injected.get()) {
return true;
}
try {
IrisLogging.info("Injecting Bukkit");
Agent.requireClassLoaderCloseDeferral();
Class<?> loaderCloseType = getClass().getClassLoader().getClass()
.getMethod("close").getDeclaringClass();
PluginClassLoaderInjectionListener loaderListener = new PluginClassLoaderInjectionListener();
pluginClassLoaderTransformer = new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(AgentBuilder.RedefinitionStrategy.Listener.ErrorEscalating.FAIL_FAST)
.with(loaderListener)
.type(ElementMatchers.is(loaderCloseType))
.transform((builder, typeDescription, classLoader, module, protectionDomain) ->
builder.visit(Advice.to(PluginClassLoaderCloseAdvice.class)
.on(ElementMatchers.named("close")
.and(ElementMatchers.takesArguments(0))
.and(ElementMatchers.returns(void.class)))))
.installOn(Agent.getInstrumentation());
loaderListener.requireInstalled();
levelStorageAccessTransformer = new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.type(ElementMatchers.is(LevelStorageSource.LevelStorageAccess.class))
.transform((builder, typeDescription, classLoader, module, protectionDomain) ->
builder.visit(Advice.to(LevelStorageAccessAdvice.class).on(ElementMatchers.isConstructor()
.and(ElementMatchers.takesArguments(4))
.and(ElementMatchers.takesArgument(0, LevelStorageSource.class))
.and(ElementMatchers.takesArgument(1, String.class))
.and(ElementMatchers.takesArgument(2, Path.class))
.and(ElementMatchers.takesArgument(3, ResourceKey.class)))))
.installOn(Agent.getInstrumentation());
serverLevelTransformer = new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.type(ElementMatchers.is(ServerLevel.class))
.transform((builder, typeDescription, classLoader, module, protectionDomain) ->
builder.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor()
.and(ElementMatchers.takesArgument(0, MinecraftServer.class))
.and(ElementMatchers.takesArgument(5, LevelStem.class)))))
.installOn(Agent.getInstrumentation());
NmsGenerationHooks.install();
injected.set(true);
return true;
} catch (Throwable e) {
IrisLogging.reportError(C.RED + "Failed to inject Bukkit", e);
ResettableClassFileTransformer partialServerLevel = serverLevelTransformer;
ResettableClassFileTransformer partialStorageAccess = levelStorageAccessTransformer;
ResettableClassFileTransformer partialPluginClassLoader = pluginClassLoaderTransformer;
serverLevelTransformer = null;
levelStorageAccessTransformer = null;
pluginClassLoaderTransformer = null;
for (ResettableClassFileTransformer partial : new ResettableClassFileTransformer[]{
partialServerLevel,
partialStorageAccess,
partialPluginClassLoader
}) {
if (partial == null) {
continue;
}
try {
partial.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);
} catch (Throwable cleanupFailure) {
IrisLogging.reportError("Failed to remove partial Bukkit lifecycle injection", cleanupFailure);
}
}
return false;
}
}
}
public void ensureServerLevelInjection() {
if (!injected.get()) {
throw new IllegalStateException("Iris world lifecycle injection is unavailable. Fix the Java Agent or Code Injection startup failure and restart before creating worlds.");
}
try {
Agent.getInstrumentation().retransformClasses(
LevelStorageSource.LevelStorageAccess.class,
ServerLevel.class
);
} catch (Throwable e) {
throw new IllegalStateException("Failed to re-apply Bukkit world lifecycle injection. World creation was stopped before loading the world.", e);
}
}
public void uninjectBukkit() {
synchronized (injected) {
ResettableClassFileTransformer activeServerLevel = serverLevelTransformer;
ResettableClassFileTransformer activeStorageAccess = levelStorageAccessTransformer;
serverLevelTransformer = null;
levelStorageAccessTransformer = null;
injected.set(false);
for (ResettableClassFileTransformer transformer : new ResettableClassFileTransformer[]{
activeServerLevel,
activeStorageAccess
}) {
if (transformer == null) {
continue;
}
try {
transformer.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);
} catch (Throwable e) {
IrisLogging.reportError(C.RED + "Failed to remove Bukkit world lifecycle injection", e);
}
}
}
}
public boolean isServerStopping() {
return ((CraftServer) Bukkit.getServer()).getServer().hasStopped();
}
public ServerShutdownBoundary createServerShutdownBoundary() {
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
return new ServerShutdownBoundary(
() -> server.hasFullyShutdown,
server.getRunningThread()
);
}
public void deferPluginClassLoaderClose() {
synchronized (injected) {
if (pluginClassLoaderTransformer == null || pluginClassLoaderCloseDeferred) {
return;
}
Agent.retainClassLoader(getClass().getClassLoader());
pluginClassLoaderCloseDeferred = true;
}
}
public void releasePluginClassLoaderClose() {
ResettableClassFileTransformer transformer;
boolean releaseLoader;
synchronized (injected) {
transformer = pluginClassLoaderTransformer;
pluginClassLoaderTransformer = null;
releaseLoader = pluginClassLoaderCloseDeferred;
pluginClassLoaderCloseDeferred = false;
}
try {
if (transformer != null
&& !transformer.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)) {
throw new IllegalStateException("Failed to remove Iris plugin class loader lifecycle injection");
}
} finally {
if (releaseLoader) {
Agent.releaseClassLoader(getClass().getClassLoader());
}
}
}
private static final class PluginClassLoaderInjectionListener extends AgentBuilder.Listener.Adapter {
private volatile boolean transformed;
private volatile Throwable failure;
@Override
public void onTransformation(TypeDescription type, ClassLoader loader, JavaModule module,
boolean loaded, DynamicType dynamicType) {
transformed = true;
}
@Override
public void onError(String typeName, ClassLoader loader, JavaModule module,
boolean loaded, Throwable throwable) {
failure = throwable;
}
private void requireInstalled() {
if (!transformed || failure != null) {
throw new IllegalStateException("Iris plugin class loader lifecycle injection failed", failure);
}
}
}
private static class PluginClassLoaderCloseAdvice {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean enter(@Advice.This ClassLoader loader) throws ReflectiveOperationException {
Class<?> installer = Class.forName(
"art.arcane.iris.platform.agent.Installer", true, ClassLoader.getSystemClassLoader());
return Boolean.TRUE.equals(installer.getMethod("deferClassLoaderClose", ClassLoader.class)
.invoke(null, loader));
}
}
private static class LevelStorageAccessAdvice {
@Advice.OnMethodEnter
static void enter(
@Advice.Argument(1) String levelId,
@Advice.Argument(value = 3, readOnly = false) ResourceKey<LevelStem> dimensionType
) {
if (levelId == null || levelId.isBlank()) {
return;
}
ClassLoader pluginClassLoader;
Class<?> generatorType;
Class<?> stagingType;
try {
Plugin irisPlugin = Bukkit.getPluginManager().getPlugin("Iris");
if (irisPlugin == null) {
return;
}
pluginClassLoader = irisPlugin.getClass().getClassLoader();
generatorType = Class.forName("art.arcane.iris.platform.generation.PlatformChunkGenerator", true, pluginClassLoader);
stagingType = Class.forName("art.arcane.iris.world.lifecycle.WorldLifecycleStaging", true, pluginClassLoader);
} catch (Throwable ignored) {
return;
}
Object generator;
try {
generator = stagingType
.getDeclaredMethod("peekStemGenerator", String.class)
.invoke(null, levelId);
} catch (Throwable e) {
throw new RuntimeException("Iris failed to inspect the staged world generator",
e instanceof InvocationTargetException ex ? ex.getCause() : e);
}
if (generator == null || !generatorType.isInstance(generator)) {
return;
}
try {
Object target = generatorType.getMethod("getTarget").invoke(generator);
if (target == null) {
throw new IllegalStateException("Iris generator has no engine target.");
}
Object world = target.getClass().getMethod("getWorld").invoke(target);
if (world == null) {
throw new IllegalStateException("Iris generator target has no world identity.");
}
Object rawIdentity = world.getClass().getMethod("identity").invoke(world);
String worldIdentity = rawIdentity == null ? "" : rawIdentity.toString().trim();
Identifier worldIdentifier = Identifier.parse(worldIdentity);
if (!"iris".equals(worldIdentifier.getNamespace())
&& !"minecraft".equals(worldIdentifier.getNamespace())) {
throw new IllegalStateException(
"Iris generator has an unmanaged world identity: " + worldIdentity);
}
dimensionType = ResourceKey.create(Registries.LEVEL_STEM, worldIdentifier);
} catch (Throwable e) {
throw new RuntimeException("Iris failed to bind the staged world storage identity",
e instanceof InvocationTargetException ex ? ex.getCause() : e);
}
}
}
private static class ServerLevelAdvice {
@Advice.OnMethodEnter
static void enter(
@Advice.Argument(0) MinecraftServer server,
@Advice.Argument(value = 4, readOnly = false) ResourceKey<Level> dimensionKey,
@Advice.Argument(value = 5, readOnly = false) LevelStem levelStem,
@Advice.AllArguments Object[] constructorArguments
) {
if (dimensionKey == null)
return;
// This advice is inlined into every ServerLevel construction on the server. Until a
// world is proven Iris-owned, every failure must fail OPEN (keep the vanilla stem):
// Iris being unloaded or half-loaded must never break other plugins' world creation.
String levelId;
ClassLoader pluginClassLoader;
Class<?> generatorType;
Class<?> stagingType;
try {
levelId = dimensionKey.identifier().getPath();
if (levelId == null || levelId.isBlank()) {
return;
}
Plugin irisPlugin = Bukkit.getPluginManager().getPlugin("Iris");
if (irisPlugin == null) {
return;
}
pluginClassLoader = irisPlugin.getClass().getClassLoader();
generatorType = Class.forName("art.arcane.iris.platform.generation.PlatformChunkGenerator", true, pluginClassLoader);
stagingType = Class.forName("art.arcane.iris.world.lifecycle.WorldLifecycleStaging", true, pluginClassLoader);
} catch (Throwable ignored) {
// Iris absent or half-loaded: fail OPEN for a world we cannot prove ours.
return;
}
// From here Iris is present and its classes resolve; a failure resolving the
// staged generator must fail LOUD — silently handing a possibly-staged Iris world
// the vanilla stem corrupts generation.
ChunkGenerator gen = null;
try {
ChunkGenerator constructorGenerator = null;
for (Object argument : constructorArguments) {
if (argument instanceof ChunkGenerator candidate) {
constructorGenerator = candidate;
break;
}
}
Object generator = generatorType.isInstance(constructorGenerator) ? constructorGenerator : null;
if (generator == null) {
generator = stagingType
.getDeclaredMethod("consumeStemGenerator", String.class)
.invoke(null, levelId);
}
if (generator instanceof ChunkGenerator owned && owned.getClass().getPackageName().startsWith("art.arcane.iris")) {
gen = owned;
}
} catch (Throwable e) {
throw new RuntimeException("Iris failed to resolve the staged world generator",
e instanceof InvocationTargetException ex ? ex.getCause() : e);
}
if (gen == null) {
return;
}
// Past the ownership gate the world is Iris-owned; silently handing back the
// vanilla stem would corrupt generation, so failures from here rethrow.
try {
Object bindings = Class.forName("art.arcane.iris.platform.bukkit.nms.INMS", true, pluginClassLoader)
.getDeclaredMethod("get")
.invoke(null);
if (bindings == null) {
throw new IllegalStateException("Iris failed to resolve an INMSBinding instance.");
}
Method stemMethod = null;
for (Method candidate : bindings.getClass().getMethods()) {
if (candidate.getName().equals("createRuntimeLevelStem") && candidate.getParameterCount() == 2) {
stemMethod = candidate;
break;
}
}
if (stemMethod == null) {
throw new IllegalStateException("Iris binding is missing createRuntimeLevelStem.");
}
Object target = generatorType.getMethod("getTarget").invoke(gen);
if (target == null) {
throw new IllegalStateException("Iris generator has no engine target.");
}
Object world = target.getClass().getMethod("getWorld").invoke(target);
if (world == null) {
throw new IllegalStateException("Iris generator target has no world identity.");
}
Object rawIdentity = world.getClass().getMethod("identity").invoke(world);
String worldIdentity = rawIdentity == null ? "" : rawIdentity.toString().trim();
Identifier worldIdentifier = Identifier.parse(worldIdentity);
if (!"iris".equals(worldIdentifier.getNamespace())
&& !"minecraft".equals(worldIdentifier.getNamespace())) {
throw new IllegalStateException(
"Iris generator has an unmanaged world identity: " + worldIdentity);
}
Object resolvedStem = stemMethod.invoke(bindings, server.registryAccess(), gen);
if (!(resolvedStem instanceof LevelStem runtimeStem)) {
throw new IllegalStateException("Iris runtime LevelStem binding returned " + (resolvedStem == null ? "null" : resolvedStem.getClass().getName()) + ".");
}
dimensionKey = ResourceKey.create(Registries.DIMENSION, worldIdentifier);
levelStem = runtimeStem;
} catch (Throwable e) {
throw new RuntimeException("Iris failed to replace the levelStem", e instanceof InvocationTargetException ex ? ex.getCause() : e);
}
}
}
}
@@ -1,80 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.nativegen.NativeStructureGenerationKeys;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.levelgen.structure.Structure;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
final class VanillaStructureBiomes {
private VanillaStructureBiomes() {
}
static Set<String> possibleBiomeKeys(BiomeSource source) {
if (source == null) {
throw new IllegalStateException("Minecraft chunk generator has no biome source");
}
Set<String> keys = new LinkedHashSet<>();
Set<Holder<Biome>> possibleBiomes = source instanceof CustomBiomeSource customBiomeSource
? customBiomeSource.possibleStructureBiomes()
: source.possibleBiomes();
for (Holder<Biome> holder : possibleBiomes) {
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
if (key.isPresent()) {
keys.add(key.get().identifier().toString());
}
}
if (keys.isEmpty()) {
throw new IllegalStateException("Minecraft biome source exposes no registered possible biomes");
}
return keys;
}
static Set<String> structureBiomeKeys(RegistryAccess access, String structureKey) {
if (access == null) {
throw new IllegalStateException("Minecraft registry access is unavailable");
}
if (structureKey == null || structureKey.isBlank()) {
throw new IllegalArgumentException("Registered structure key must not be blank");
}
Set<String> keys = new LinkedHashSet<>();
Registry<Structure> registry = access.lookupOrThrow(Registries.STRUCTURE);
Structure structure = registry.getValue(Identifier.parse(structureKey));
if (structure == null) {
throw new IllegalArgumentException("Registered structure does not exist: " + structureKey);
}
boolean hasFilterEntries = false;
for (Holder<Biome> holder : structure.biomes()) {
hasFilterEntries = true;
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
if (key.isPresent()) {
keys.add(key.get().identifier().toString());
}
}
// An empty biome filter is legal datapack content (opt-in structures whose tags only
// reference absent modded biomes); the structure is unreachable, not an error state.
if (keys.isEmpty() && hasFilterEntries) {
throw new IllegalStateException("Registered structure '" + structureKey
+ "' has biome filter entries but none resolve to registered biome keys");
}
return keys;
}
static Set<String> reachableStructureKeys(ServerLevel level, BiomeSource source) {
if (level == null) {
throw new IllegalStateException("Minecraft server level is unavailable");
}
Set<String> possible = possibleBiomeKeys(source);
return NativeStructureGenerationKeys.reachable(level, possible);
}
}
@@ -1,6 +1,9 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.platform.bukkit.nms.v26_2_R1.IrisChunkGenerator;
import art.arcane.volmlib.nativelib.v26_2_R1.terrain.NativeChunkGenerator;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeTerrainColumns;
import art.arcane.iris.platform.bukkit.nms.BukkitTerrainColumnPolicy;
import art.arcane.iris.generation.runtime.IrisComplex;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.GenerationSessionLease;
@@ -100,14 +103,13 @@ public final class IrisChunkGeneratorHeightCacheTest {
when(engine.getCacheID()).thenReturn(1);
when(engine.acquireGenerationLease(anyString())).thenReturn(GenerationSessionLease.noop());
when(complex.resolvedTerrainColumn(anyInt(), anyInt())).thenReturn(Optional.of(signature()));
IrisChunkGenerator generator = mock(IrisChunkGenerator.class, CALLS_REAL_METHODS);
set(generator, "engine", engine);
set(generator, "terrainHeights", new NativeTerrainHeightCache());
NativeChunkGenerator<?, ?, ?, ?, ?> generator = mock(NativeChunkGenerator.class, CALLS_REAL_METHODS);
set(generator, "terrainColumns", new NativeTerrainColumns(new BukkitTerrainColumnPolicy(engine, null)));
return new Fixture(generator, engine, complex);
}
private static void set(IrisChunkGenerator generator, String name, Object value) throws Exception {
Field field = IrisChunkGenerator.class.getDeclaredField(name);
private static void set(NativeChunkGenerator<?, ?, ?, ?, ?> generator, String name, Object value) throws Exception {
Field field = NativeChunkGenerator.class.getDeclaredField(name);
field.setAccessible(true);
field.set(generator, value);
}
@@ -125,6 +127,6 @@ public final class IrisChunkGeneratorHeightCacheTest {
new TerrainBoundarySignature.BiomeEncoding(List.of("minecraft:plains"), new short[]{0})), geometry);
}
private record Fixture(IrisChunkGenerator generator, Engine engine, IrisComplex complex) {
private record Fixture(NativeChunkGenerator<?, ?, ?, ?, ?> generator, Engine engine, IrisComplex complex) {
}
}
@@ -1,5 +1,9 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.structure.nativegen.GenerationWritePolicy;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeGenerationWriteGuard;
import art.arcane.iris.generation.runtime.DimensionStackContext;
import art.arcane.iris.generation.runtime.DimensionStackLayout;
import art.arcane.iris.generation.runtime.IrisComplex;
@@ -45,11 +49,11 @@ public final class NativeGenerationWriteGuardTest {
DimensionStackContext stack = mock(DimensionStackContext.class);
when(objects.contains(-3, 74, -7)).thenReturn(true);
Predicate<BlockPos> protectedPositions = NativeGenerationWriteGuard.protectedPositions(objects, stack, -64);
Predicate<BlockPos> protectedPositions = NativeGenerationWriteGuard.protectedPositions(GenerationWritePolicy.protectedPositions(objects, stack, -64));
assertTrue(protectedPositions.test(new BlockPos(-3, 10, -7)));
verifyNoInteractions(stack);
assertFalse(NativeGenerationWriteGuard.protectedPositions(objects, null, -64)
assertFalse(NativeGenerationWriteGuard.protectedPositions(GenerationWritePolicy.protectedPositions(objects, null, -64))
.test(new BlockPos(-3, 11, -7)));
}
@@ -65,7 +69,7 @@ public final class NativeGenerationWriteGuardTest {
when(negative.isHostFeatureProtectedY(64)).thenReturn(true);
when(positive.isHostFeatureProtectedY(65)).thenReturn(true);
Predicate<BlockPos> protectedPositions = NativeGenerationWriteGuard.protectedPositions(objects, stack, -64);
Predicate<BlockPos> protectedPositions = NativeGenerationWriteGuard.protectedPositions(GenerationWritePolicy.protectedPositions(objects, stack, -64));
assertTrue(protectedPositions.test(new BlockPos(-1, 0, -1)));
assertFalse(protectedPositions.test(new BlockPos(-1, 1, -1)));
@@ -85,10 +89,10 @@ public final class NativeGenerationWriteGuardTest {
when(router.history()).thenReturn(history);
when(history.activeActivation()).thenReturn(active);
when(active.activationId()).thenReturn(4L);
assertTrue(NativeGenerationWriteGuard.isHistoricalStructure(engine, 3));
assertFalse(NativeGenerationWriteGuard.isHistoricalStructure(engine, 0));
assertFalse(NativeGenerationWriteGuard.isHistoricalStructure(engine, 4));
assertFalse(NativeGenerationWriteGuard.isHistoricalStructure(engine, 5));
assertTrue(GenerationWritePolicy.isHistoricalStructure(engine, 3));
assertFalse(GenerationWritePolicy.isHistoricalStructure(engine, 0));
assertFalse(GenerationWritePolicy.isHistoricalStructure(engine, 4));
assertFalse(GenerationWritePolicy.isHistoricalStructure(engine, 5));
}
@Test
@@ -100,11 +104,11 @@ public final class NativeGenerationWriteGuardTest {
ChunkAccess partial = mock(ChunkAccess.class);
when(partial.getPersistedStatus()).thenReturn(ChunkStatus.NOISE);
when(region.getChunk(anyInt(), anyInt())).thenReturn(partial);
assertTrue(NativeGenerationWriteGuard.allowsDecoration(engine, region, new ChunkPos(0, 0)));
assertTrue(NativeGenerationWriteGuard.allowsDecoration(engine.getComplex()::allowsMantleChunkWrite, region, new ChunkPos(0, 0)));
ChunkAccess complete = mock(ChunkAccess.class);
when(complete.getPersistedStatus()).thenReturn(ChunkStatus.FULL);
when(region.getChunk(1, -1)).thenReturn(complete);
assertFalse(NativeGenerationWriteGuard.allowsDecoration(engine, region, new ChunkPos(0, 0)));
assertFalse(NativeGenerationWriteGuard.allowsDecoration(engine.getComplex()::allowsMantleChunkWrite, region, new ChunkPos(0, 0)));
}
@Test
@@ -115,11 +119,11 @@ public final class NativeGenerationWriteGuardTest {
ChunkAccess chunk = mock(ChunkAccess.class);
when(chunk.getPos()).thenReturn(new ChunkPos(-2, 3));
when(chunk.getPersistedStatus()).thenReturn(ChunkStatus.NOISE);
assertTrue(NativeGenerationWriteGuard.allowsPendingStage(engine, chunk, ChunkStatus.FEATURES));
assertFalse(NativeGenerationWriteGuard.allowsPendingStage(engine, chunk, ChunkStatus.NOISE));
assertTrue(NativeGenerationWriteGuard.allowsPendingStage(engine.getComplex()::allowsMantleChunkWrite, chunk, ChunkStatus.FEATURES));
assertFalse(NativeGenerationWriteGuard.allowsPendingStage(engine.getComplex()::allowsMantleChunkWrite, chunk, ChunkStatus.NOISE));
when(chunk.getPersistedStatus()).thenReturn(ChunkStatus.FEATURES);
assertFalse(NativeGenerationWriteGuard.allowsPendingStage(engine, chunk, ChunkStatus.FEATURES));
assertFalse(NativeGenerationWriteGuard.allowsPendingStage(engine.getComplex()::allowsMantleChunkWrite, chunk, ChunkStatus.FEATURES));
when(chunk.getPersistedStatus()).thenReturn(ChunkStatus.FULL);
assertFalse(NativeGenerationWriteGuard.allowsPendingStage(engine, chunk, ChunkStatus.SPAWN));
assertFalse(NativeGenerationWriteGuard.allowsPendingStage(engine.getComplex()::allowsMantleChunkWrite, chunk, ChunkStatus.SPAWN));
}
}
@@ -1,5 +1,19 @@
package art.arcane.iris.nativegen;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureStartInjector;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureFactory;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureReferenceEnvelope;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeJigsawMetadata;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.ForcedStructureChunkGenerator;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureTemplatePoolBounds;
import art.arcane.iris.structure.jigsaw.IrisJigsawConfiguration;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import com.mojang.datafixers.util.Either;
@@ -108,10 +122,10 @@ public class NativeStructureFactoryTest {
distance.putInt("vertical", 64);
axisSpecific.put("max_distance_from_center", distance);
assertEquals(80, NativeStructureFactory.sourceDistance(scalar, "horizontal"));
assertEquals(80, NativeStructureFactory.sourceDistance(scalar, "vertical"));
assertEquals(96, NativeStructureFactory.sourceDistance(axisSpecific, "horizontal"));
assertEquals(64, NativeStructureFactory.sourceDistance(axisSpecific, "vertical"));
assertEquals(80, NativeJigsawMetadata.sourceDistance(scalar, "horizontal"));
assertEquals(80, NativeJigsawMetadata.sourceDistance(scalar, "vertical"));
assertEquals(96, NativeJigsawMetadata.sourceDistance(axisSpecific, "horizontal"));
assertEquals(64, NativeJigsawMetadata.sourceDistance(axisSpecific, "vertical"));
}
@Test
@@ -121,8 +135,8 @@ public class NativeStructureFactoryTest {
.terrainAdapation(TerrainAdjustment.BEARD_BOX)
.build());
assertEquals(0, NativeStructureFactory.horizontalReferenceExpansion(none));
assertEquals(12, NativeStructureFactory.horizontalReferenceExpansion(beardBox));
assertEquals(0, NativeJigsawMetadata.horizontalReferenceExpansion(none));
assertEquals(12, NativeJigsawMetadata.horizontalReferenceExpansion(beardBox));
}
@Test
@@ -1,5 +1,7 @@
package art.arcane.iris.nativegen;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureGenerationKeys;
import net.minecraft.SharedConstants;
import net.minecraft.core.Vec3i;
import net.minecraft.server.Bootstrap;
@@ -1,5 +1,7 @@
package art.arcane.iris.nativegen;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureLocatePersistence;
import net.minecraft.SharedConstants;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.Bootstrap;
@@ -1,11 +1,19 @@
package art.arcane.iris.nativegen;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureReferenceRepair;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureOwnershipFingerprint;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureReferenceEnvelope;
import art.arcane.iris.structure.nativegen.NativeStructureOwnershipRecord;
import art.arcane.iris.structure.nativegen.NativeStructureStartPlan;
import art.arcane.iris.structure.nativegen.IrisNativeStructure;
import art.arcane.iris.structure.placement.IrisStructurePlacement;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.volmlib.util.structure.StructureTerrainMode;
import art.arcane.volmlib.util.collection.KList;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
@@ -65,7 +73,7 @@ public class NativeStructureOwnershipFingerprintTest {
generated, 0, 80, -64, 320);
int alignedLocatorY = NativeStructureOwnershipFingerprint.locatorY(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(24);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated,
@@ -85,11 +93,9 @@ public class NativeStructureOwnershipFingerprintTest {
StructureStart reloaded = new StructureStart(structure, origin, 0, regeneratedPieces);
String afterReload = NativeStructureOwnershipFingerprint.fingerprint(
"minecraft:monument", reloaded);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
wrapped,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(wrapped).minY()),
NativeStructureReferenceEnvelope.referenceBounds(wrapped, structure, terrain));
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
"minecraft:monument", wrapped, NativeStructureReferenceEnvelope.referenceBounds(wrapped, structure, terrain)), plan(origin, NativeStructureReferenceEnvelope.contentBounds(wrapped).minY()));
assertEquals(beforeReload, afterReload);
assertTrue(NativeStructureOwnershipFingerprint.matches(ownership, reloaded));
@@ -110,11 +116,9 @@ public class NativeStructureOwnershipFingerprintTest {
for (StructurePiece piece : moved.getPieces()) {
piece.move(1, 0, 0);
}
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
expected,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(expected).minY()),
expected.getBoundingBox());
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
"minecraft:monument", expected, expected.getBoundingBox()), plan(origin, NativeStructureReferenceEnvelope.contentBounds(expected).minY()));
assertFalse(NativeStructureOwnershipFingerprint.matches(ownership, moved));
}
@@ -129,18 +133,16 @@ public class NativeStructureOwnershipFingerprintTest {
NativeStructureVerticalPlacer.alignOceanMonumentToSeaLevel(
generated, 0, 80, -64, 320);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(24);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated,
structure,
0,
terrain);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
wrapped,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(wrapped).minY()),
NativeStructureReferenceEnvelope.referenceBounds(wrapped, structure, terrain));
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
"minecraft:monument", wrapped, NativeStructureReferenceEnvelope.referenceBounds(wrapped, structure, terrain)), plan(origin, NativeStructureReferenceEnvelope.contentBounds(wrapped).minY()));
PiecesContainer regeneratedPieces = OceanMonumentStructure.regeneratePiecesAfterLoad(
origin, seed, new PiecesContainer(wrapped.getPieces()));
StructureStart reloaded = new StructureStart(structure, origin, 0, regeneratedPieces);
@@ -263,13 +265,10 @@ public class NativeStructureOwnershipFingerprintTest {
private static NativeStructureOwnershipRecord ownership(
ChunkPos origin, StructureStart start, OceanMonumentStructure structure) {
return NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
start,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(start).minY()),
NativeStructureReferenceEnvelope.referenceBounds(
start, structure, new IrisStructureTerrain())
);
return NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
"minecraft:monument", start, NativeStructureReferenceEnvelope.referenceBounds(
start, structure, new IrisStructureTerrain())), plan(origin, NativeStructureReferenceEnvelope.contentBounds(start).minY()));
}
private static StructureStart poolStart(
@@ -1,12 +1,22 @@
package art.arcane.iris.nativegen;
import art.arcane.volmlib.nativelib.terrain.structure.StructureOwnershipRecordView;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureOwnershipRecovery;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureOwnershipFingerprint;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureReferenceEnvelope;
import art.arcane.iris.structure.nativegen.NativeStructureOwnershipRecord;
import art.arcane.iris.structure.nativegen.NativeStructureStartPlan;
import art.arcane.iris.structure.placement.StructurePlacementGrid;
import art.arcane.iris.structure.nativegen.IrisNativeStructure;
import art.arcane.iris.structure.placement.IrisStructurePlacement;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.volmlib.util.structure.StructureTerrainMode;
import art.arcane.volmlib.util.collection.KList;
import net.minecraft.SharedConstants;
import net.minecraft.core.Direction;
@@ -63,7 +73,7 @@ public class NativeStructureOwnershipRecoveryTest {
NativeStructureOwnershipRecord recovered =
NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, reloaded,
plan, expected, false);
plan, expected, false, NativeStructureOwnershipRecord::capture);
assertNotNull(recovered);
assertEquals(StructurePlacementGrid.placementIdentity(plan.placement()),
@@ -71,7 +81,7 @@ public class NativeStructureOwnershipRecoveryTest {
assertEquals(expectedLocatorY, recovered.locatorY());
assertNotEquals(NativeStructureOwnershipFingerprint.locatorY(reloaded),
recovered.locatorY());
assertEquals(IrisStructureTerrainMode.FORCE_CARVE,
assertEquals(StructureTerrainMode.FORCE_CARVE,
recovered.restoredDecision().terrain().resolvedMode());
assertEquals(24, recovered.restoredDecision().terrain().getHorizontalPadding());
assertTrue(recovered.referenceMinChunkX()
@@ -89,7 +99,7 @@ public class NativeStructureOwnershipRecoveryTest {
NativeStructureOwnershipRecord recovered =
NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, natural,
plan, natural, true);
plan, natural, true, NativeStructureOwnershipRecord::capture);
assertNull(recovered);
}
@@ -114,10 +124,10 @@ public class NativeStructureOwnershipRecoveryTest {
StructurePlacementGrid.placementIdentity(changedIdentity.placement()));
assertNull(NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, expected,
changedIdentity, expected, false));
changedIdentity, expected, false, NativeStructureOwnershipRecord::capture));
assertNull(NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, changedGeometry,
plan(origin, "geometry-check", 0), expected, false));
plan(origin, "geometry-check", 0), expected, false, NativeStructureOwnershipRecord::capture));
}
@Test
@@ -129,7 +139,7 @@ public class NativeStructureOwnershipRecoveryTest {
NativeStructureOwnershipRecord recovered =
NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, expected,
plan(origin, "fingerprint-check", 16), expected, false);
plan(origin, "fingerprint-check", 16), expected, false, NativeStructureOwnershipRecord::capture);
assertNotNull(recovered);
StructureStart moved = monumentStart(structure, origin, seed);
@@ -148,10 +158,11 @@ public class NativeStructureOwnershipRecoveryTest {
OceanMonumentStructure structure = structure();
StructureStart start = monumentStart(structure, origin, seed);
NativeStructureStartPlan plan = plan(
origin, "vacuum-envelope-refresh", IrisStructureTerrainMode.VACUUM, 0);
origin, "vacuum-envelope-refresh", StructureTerrainMode.VACUUM, 0);
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(start);
NativeStructureOwnershipRecord stale = NativeStructureOwnershipFingerprint.capture(
structureKey, start, plan, content);
NativeStructureOwnershipRecord stale = NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
structureKey, start, content), plan);
NativeStructureOwnershipRecord refreshed =
NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
@@ -166,7 +177,7 @@ public class NativeStructureOwnershipRecoveryTest {
assertEquals(stale.locatorY(), refreshed.locatorY());
assertEquals(stale.contentFingerprint(), refreshed.contentFingerprint());
assertEquals(stale.decision(), refreshed.decision());
assertEquals(IrisStructureTerrainMode.VACUUM,
assertEquals(StructureTerrainMode.VACUUM,
refreshed.restoredDecision().terrain().resolvedMode());
BoundingBox expected = NativeStructureReferenceEnvelope.referenceBounds(
start, structure, plan.placement().resolvedTerrain(), structureKey);
@@ -187,10 +198,10 @@ public class NativeStructureOwnershipRecoveryTest {
OceanMonumentStructure structure = structure();
StructureStart expected = monumentStart(structure, origin, seed);
NativeStructureStartPlan plan = plan(
origin, "vacuum-content-check", IrisStructureTerrainMode.VACUUM, 0);
NativeStructureOwnershipRecord stale = NativeStructureOwnershipFingerprint.capture(
structureKey, expected, plan,
NativeStructureReferenceEnvelope.contentBounds(expected));
origin, "vacuum-content-check", StructureTerrainMode.VACUUM, 0);
NativeStructureOwnershipRecord stale = NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
structureKey, expected, NativeStructureReferenceEnvelope.contentBounds(expected)), plan);
StructureStart moved = monumentStart(structure, origin, seed);
for (StructurePiece piece : moved.getPieces()) {
piece.move(1, 0, 0);
@@ -210,8 +221,9 @@ public class NativeStructureOwnershipRecoveryTest {
NativeStructureStartPlan plan = plan(origin, "current-force-carve", 24);
BoundingBox envelope = NativeStructureReferenceEnvelope.referenceBounds(
start, structure, plan.placement().resolvedTerrain(), structureKey);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
structureKey, start, plan, envelope);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
structureKey, start, envelope), plan);
assertSame(ownership, NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
structureKey, structure, start, ownership));
@@ -226,19 +238,20 @@ public class NativeStructureOwnershipRecoveryTest {
StructureStart start = monumentStart(structure, origin, seed);
BoundingBox initial = NativeStructureReferenceEnvelope.contentBounds(start);
int maximumReferenceBlockX = ((origin.x()
+ NativeStructureOwnershipRecord.MAX_REFERENCE_DISTANCE_CHUNKS) << 4) + 15;
+ StructureOwnershipRecordView.MAX_REFERENCE_DISTANCE_CHUNKS) << 4) + 15;
int shiftX = maximumReferenceBlockX - initial.maxX();
for (StructurePiece piece : start.getPieces()) {
piece.move(shiftX, 0, 0);
}
NativeStructureStartPlan plan = plan(
origin, "clipped-vacuum", IrisStructureTerrainMode.VACUUM, 0);
origin, "clipped-vacuum", StructureTerrainMode.VACUUM, 0);
BoundingBox envelope = NativeStructureReferenceEnvelope.referenceBounds(
start, structure, plan.placement().resolvedTerrain(), structureKey);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
structureKey, start, plan, envelope);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
structureKey, start, envelope), plan);
assertEquals(origin.x() + NativeStructureOwnershipRecord.MAX_REFERENCE_DISTANCE_CHUNKS,
assertEquals(origin.x() + StructureOwnershipRecordView.MAX_REFERENCE_DISTANCE_CHUNKS,
ownership.referenceMaxChunkX());
assertSame(ownership, NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
structureKey, structure, start, ownership));
@@ -270,12 +283,12 @@ public class NativeStructureOwnershipRecoveryTest {
String placementId,
int horizontalPadding) {
return plan(origin, placementId,
IrisStructureTerrainMode.FORCE_CARVE, horizontalPadding);
StructureTerrainMode.FORCE_CARVE, horizontalPadding);
}
private static NativeStructureStartPlan plan(ChunkPos origin,
String placementId,
IrisStructureTerrainMode terrainMode,
StructureTerrainMode terrainMode,
int horizontalPadding) {
IrisNativeStructure source = new IrisNativeStructure()
.setStructure("minecraft:monument")
@@ -1,8 +1,18 @@
package art.arcane.iris.nativegen;
import art.arcane.volmlib.nativelib.terrain.structure.StructurePalette;
import art.arcane.volmlib.nativelib.terrain.structure.StructureTerrainSettings;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureReferenceEnvelope;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureTerrainIntegrator;
import art.arcane.iris.generation.terrain.IrisMaterialPalette;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.volmlib.util.structure.StructureTerrainMode;
import art.arcane.iris.structure.placement.IrisStructureYBand;
import art.arcane.volmlib.util.math.RNG;
import net.minecraft.SharedConstants;
@@ -76,7 +86,7 @@ public class NativeStructurePostProcessorEncaseTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "minecraft:stronghold", start,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setMode(StructureTerrainMode.ENCASE)
.setHorizontalPadding(1)
.setCeilingPadding(1)
.setFloorPadding(1),
@@ -100,7 +110,7 @@ public class NativeStructurePostProcessorEncaseTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), bounds, "minecraft:stronghold", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.ENCASE),
new IrisStructureTerrain().setMode(StructureTerrainMode.ENCASE),
null);
assertEquals(-1, bounds.minY());
@@ -181,7 +191,7 @@ public class NativeStructurePostProcessorEncaseTest {
world(new HashMap<>()), area,
List.of(new NativeStructureTerrainIntegrator.TerrainTarget(
"test:bounded-snapshot", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))));
new IrisStructureTerrain().setMode(StructureTerrainMode.SOURCE))));
assertNotNull(sourceTerrain);
assertEquals(23 * 16 * 16, sourceTerrain.sampledCells());
@@ -210,7 +220,7 @@ public class NativeStructurePostProcessorEncaseTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), bounds, "minecraft:stronghold", start,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setMode(StructureTerrainMode.ENCASE)
.setEncasePalette(palette),
NativeStructurePostProcessorEncaseTest::tuffBlock);
@@ -221,10 +231,10 @@ public class NativeStructurePostProcessorEncaseTest {
@Test
public void omittedTerrainConfigurationRetainsSourceSemantics() {
for (TerrainAdjustment adjustment : TerrainAdjustment.values()) {
IrisStructureTerrain resolved = NativeStructureTerrainIntegrator.resolveNativeTerrain(
StructureTerrainSettings resolved = NativeStructureTerrainIntegrator.resolveNativeTerrain(
start(adjustment, 64), null);
assertEquals(IrisStructureTerrainMode.SOURCE, resolved.resolvedMode());
assertEquals(StructureTerrainMode.SOURCE, resolved.resolvedMode());
}
}
@@ -240,7 +250,7 @@ public class NativeStructurePostProcessorEncaseTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "test:bury", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE), null);
new IrisStructureTerrain().setMode(StructureTerrainMode.SOURCE), null);
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX() - 5, groundY, bounds.minZ()));
@@ -267,7 +277,7 @@ public class NativeStructurePostProcessorEncaseTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "test:encapsulate", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE), null);
new IrisStructureTerrain().setMode(StructureTerrainMode.SOURCE), null);
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX() - 11, bounds.minY(), bounds.minZ()));
@@ -304,7 +314,7 @@ public class NativeStructurePostProcessorEncaseTest {
@Test
public void explicitTerrainConfigurationWinsOverAutoEncase() {
IrisStructureTerrain configured = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.PRESERVE);
.setMode(StructureTerrainMode.PRESERVE);
assertSame(configured, NativeStructureTerrainIntegrator.resolveNativeTerrain(
start(TerrainAdjustment.BURY, 64), configured));
@@ -313,11 +323,11 @@ public class NativeStructurePostProcessorEncaseTest {
@Test
public void legacyTemplateAirIsClearedAfterEveryTerrainFillPath() {
IrisStructureTerrain source = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.SOURCE);
.setMode(StructureTerrainMode.SOURCE);
IrisStructureTerrain preserve = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.PRESERVE);
.setMode(StructureTerrainMode.PRESERVE);
IrisStructureTerrain encase = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE);
.setMode(StructureTerrainMode.ENCASE);
assertTrue(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
start(TerrainAdjustment.BURY, 64), source));
@@ -337,7 +347,7 @@ public class NativeStructurePostProcessorEncaseTest {
BoundingBox sourceBounds = generated.getBoundingBox();
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setMode(StructureTerrainMode.ENCASE)
.setHorizontalPadding(4);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
@@ -373,7 +383,7 @@ public class NativeStructurePostProcessorEncaseTest {
StructureStart generated = start(TerrainAdjustment.BURY, 64);
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setMode(StructureTerrainMode.ENCASE)
.setHorizontalPadding(112);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
@@ -446,7 +456,7 @@ public class NativeStructurePostProcessorEncaseTest {
assertEquals(minY, start.getBoundingBox().minY());
}
private static BlockState tuffBlock(IrisMaterialPalette palette, RNG rng, int x, int y, int z) {
private static BlockState tuffBlock(StructurePalette palette, RNG rng, int x, int y, int z) {
assertNotNull(palette);
assertNotNull(rng);
return Blocks.TUFF.defaultBlockState();
@@ -1,7 +1,11 @@
package art.arcane.iris.nativegen;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureReferenceEnvelope;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.volmlib.util.structure.StructureTerrainMode;
import net.minecraft.SharedConstants;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderSet;
@@ -95,7 +99,7 @@ public class NativeStructurePostProcessorMonumentTest {
start.getStructure(),
0,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(4));
int offset = NativeStructureVerticalPlacer.applyVerticalPlacement(
@@ -1,5 +1,11 @@
package art.arcane.iris.nativegen;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureSurfaceFitter;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureReflection;
import net.minecraft.SharedConstants;
import net.minecraft.core.HolderSet;
import net.minecraft.server.Bootstrap;
@@ -1,9 +1,29 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.generation.mantle.StructureCarvingFootprint;
import art.arcane.iris.structure.placement.IrisStructureCarveShape;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureReferenceEnvelope;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureTerrainIntegrator;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureFoundationBuilder;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureSurfaceSupportBuilder;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureSurfaceFitter;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureVegetationClearer;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureTemplateOccupancy;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureReflection;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.WorldgenTerrainHeightmaps;
import art.arcane.volmlib.util.structure.StructureCarvingFootprint;
import art.arcane.volmlib.util.structure.StructureCarveShape;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.volmlib.util.structure.StructureTerrainMode;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import net.minecraft.SharedConstants;
@@ -214,10 +234,10 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
put(blocks, x, sourceY, 0, Blocks.GRASS_BLOCK.defaultBlockState());
}
assertTrue(NativeStructureSurfaceFitter.requiresFlattenTerrain(
surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)));
surfaceTarget(start, StructureTerrainMode.FLATTEN)));
NativeStructureSurfaceFitter.prepareSurfaceStructures(world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)),
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)),
(x, z) -> x < 2 ? groundY - 32 : groundY + 32);
for (int x = 0; x <= 3; x++) {
@@ -240,7 +260,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
put(blocks, 0, groundY - 19, 0, Blocks.DIRT.defaultBlockState());
NativeStructureSurfaceFitter.prepareSurfaceStructures(world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)), (x, z) -> groundY);
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)), (x, z) -> groundY);
for (int y = groundY - 19; y <= groundY; y++) {
assertEquals(Blocks.DIRT.defaultBlockState(), state(blocks, 0, y, 0));
@@ -260,9 +280,9 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
Map<BlockPos, BlockState> originalSubmerged = new HashMap<>(submerged);
NativeStructureSurfaceFitter.prepareSurfaceStructures(world(buried), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)), (x, z) -> 100);
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)), (x, z) -> 100);
NativeStructureSurfaceFitter.prepareSurfaceStructures(world(submerged), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)), (x, z) -> groundY - 8);
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)), (x, z) -> groundY - 8);
assertEquals(originalBuried, buried);
assertEquals(originalSubmerged, submerged);
@@ -280,12 +300,12 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
StructureStart start = new StructureStart(structure, new ChunkPos(0, 0), 0,
new PiecesContainer(desertStart().getPieces()));
assertFalse(NativeStructureSurfaceFitter.requiresFlattenTerrain(
surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)));
surfaceTarget(start, StructureTerrainMode.FLATTEN)));
}
assertFalse(NativeStructureSurfaceFitter.requiresFlattenTerrain(
surfaceTarget(desertStart(TerrainAdjustment.BURY), IrisStructureTerrainMode.FLATTEN)));
surfaceTarget(desertStart(TerrainAdjustment.BURY), StructureTerrainMode.FLATTEN)));
assertFalse(NativeStructureSurfaceFitter.requiresFlattenTerrain(
surfaceTarget(desertStart(TerrainAdjustment.ENCAPSULATE), IrisStructureTerrainMode.FLATTEN)));
surfaceTarget(desertStart(TerrainAdjustment.ENCAPSULATE), StructureTerrainMode.FLATTEN)));
}
@Test
@@ -294,8 +314,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(0, 128, 0, 0, 136, 0), 0, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece));
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, IrisStructureTerrainMode.FLATTEN);
target.terrain().setFlattenRange(32);
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, StructureTerrainMode.FLATTEN);
((IrisStructureTerrain) target.terrain()).setFlattenRange(32);
BoundingBox area = new BoundingBox(0, 0, 0, 0, 255, 0);
Map<BlockPos, BlockState> blocks = new HashMap<>();
put(blocks, 0, 94, 0, Blocks.GOLD_BLOCK.defaultBlockState());
@@ -323,7 +343,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(0, 64, 0, 0, 70, 0), 0, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece));
IrisStructureTerrain terrain = new IrisStructureTerrain().setMode(IrisStructureTerrainMode.FLATTEN)
IrisStructureTerrain terrain = new IrisStructureTerrain().setMode(StructureTerrainMode.FLATTEN)
.setHorizontalPadding(128).setFlattenRange(128);
BoundingBox references = NativeStructureReferenceEnvelope.referenceBounds(start, start.getStructure(), terrain);
@@ -351,7 +371,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
Map<BlockPos, BlockState> blocks = supportTerrain(0, 0);
Set<Long> written = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)),
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertEquals(Set.of(BlockPos.asLong(0, 63, 0)), written);
@@ -372,7 +392,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
put(blocks, 0, 80, 0, Blocks.STONE.defaultBlockState());
Set<Long> written = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)),
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertTrue(written.isEmpty());
@@ -399,7 +419,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
NativeStructureSurfaceFitter.prepareSurfaceStructures(world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)),
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)),
(x, z) -> x == footprint.minX() ? 80 : 112);
assertEquals(81, piece.getBoundingBox().minY());
@@ -421,8 +441,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
List.of(), StructureTemplatePool.Projection.TERRAIN_MATCHING),
new BoundingBox(4, 65, 0, 4, 75, 0), 0, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(left, path, right));
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, IrisStructureTerrainMode.FLATTEN);
target.terrain().setHorizontalPadding(24);
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, StructureTerrainMode.FLATTEN);
((IrisStructureTerrain) target.terrain()).setHorizontalPadding(24);
BoundingBox area = new BoundingBox(0, 0, 0, 8, 128, 0);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 96);
for (int x : List.of(0, 8)) {
@@ -472,8 +492,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
List.of(), StructureTemplatePool.Projection.TERRAIN_MATCHING),
new BoundingBox(0, 65, 0, 8, 75, 0), 0, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(left, path, right));
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, IrisStructureTerrainMode.FLATTEN);
target.terrain().setHorizontalPadding(24);
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, StructureTerrainMode.FLATTEN);
((IrisStructureTerrain) target.terrain()).setHorizontalPadding(24);
BoundingBox area = new BoundingBox(0, 0, 0, 8, 127, 0);
WorldGenLevel world = world(chunk);
@@ -532,8 +552,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
List.of(), StructureTemplatePool.Projection.TERRAIN_MATCHING),
new BoundingBox(4, 65, 0, 6, 75, 0), 0, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(left, path, right));
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, IrisStructureTerrainMode.FLATTEN);
target.terrain().setHorizontalPadding(24).setFlattenRange(32);
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, StructureTerrainMode.FLATTEN);
((IrisStructureTerrain) target.terrain()).setHorizontalPadding(24).setFlattenRange(32);
BoundingBox area = new BoundingBox(0, 0, 0, 8, 127, 0);
WorldGenLevel world = world(chunk);
@@ -579,8 +599,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
}
StructureStart start = rigidSurfaceStart(pieces);
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, IrisStructureTerrainMode.FLATTEN);
target.terrain().setHorizontalPadding(24).setFlattenRange(64);
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, StructureTerrainMode.FLATTEN);
((IrisStructureTerrain) target.terrain()).setHorizontalPadding(24).setFlattenRange(64);
BoundingBox area = new BoundingBox(0, 0, 0, 8, 127, 8);
Map<BlockPos, BlockState> blocks = new HashMap<>();
for (int x = 0; x <= 8; x++) {
@@ -667,11 +687,11 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
assertTrue(NativeStructureSurfaceFitter.requiresSurfaceTerrain(
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:beard_box", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))));
new IrisStructureTerrain().setMode(StructureTerrainMode.SOURCE))));
assertFalse(NativeStructureSurfaceFitter.requiresSurfaceTerrain(
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:beard_box", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.PRESERVE))));
new IrisStructureTerrain().setMode(StructureTerrainMode.PRESERVE))));
}
@Test
@@ -681,15 +701,15 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureTerrainIntegrator.TerrainTarget sourceNone =
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:none", none,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE));
new IrisStructureTerrain().setMode(StructureTerrainMode.SOURCE));
NativeStructureTerrainIntegrator.TerrainTarget vacuumNone =
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:none", none,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.VACUUM));
new IrisStructureTerrain().setMode(StructureTerrainMode.VACUUM));
NativeStructureTerrainIntegrator.TerrainTarget vacuumBox =
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:box", box,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.VACUUM));
new IrisStructureTerrain().setMode(StructureTerrainMode.VACUUM));
assertFalse(NativeStructureSurfaceFitter.requiresSurfaceTerrain(sourceNone));
assertTrue(NativeStructureSurfaceFitter.requiresSurfaceTerrain(vacuumNone));
@@ -778,7 +798,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
List<NativeStructureTerrainIntegrator.TerrainTarget> targets = List.of(
new NativeStructureTerrainIntegrator.TerrainTarget(
"nova_structures:tavern_oak", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE)));
new IrisStructureTerrain().setMode(StructureTerrainMode.SOURCE)));
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area, targets,
(x, z) -> 65);
@@ -886,7 +906,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 0, 64, 0));
@@ -911,7 +931,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 65, 0));
@@ -932,7 +952,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 80);
for (int y = 61; y <= 74; y++) {
@@ -957,7 +977,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 79);
for (int y = 61; y <= 74; y++) {
@@ -989,7 +1009,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.SurfaceTerrainPlan plan =
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
for (int y = 61; y <= 64; y++) {
put(blocks, 0, y, 0, Blocks.CAVE_AIR.defaultBlockState());
@@ -1029,7 +1049,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.SurfaceTerrainPlan plan =
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
put(blocks, 0, 65, 0, Blocks.COBBLESTONE.defaultBlockState());
@@ -1051,7 +1071,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
new BoundingBox(15, 65, 0, 16, 70, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
NativeStructureTerrainIntegrator.TerrainTarget target =
surfaceTarget(start, IrisStructureTerrainMode.VACUUM);
surfaceTarget(start, StructureTerrainMode.VACUUM);
BoundingBox wideArea = new BoundingBox(0, 54, 0, 31, 72, 15);
BoundingBox westArea = new BoundingBox(0, 54, 0, 15, 72, 15);
BoundingBox eastArea = new BoundingBox(16, 54, 0, 31, 72, 15);
@@ -1100,7 +1120,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(originalBlocks, blocks);
@@ -1121,7 +1141,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(originalBlocks, blocks);
@@ -1145,7 +1165,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 1, 66, 0));
@@ -1170,7 +1190,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(originalBlocks, blocks);
@@ -1190,7 +1210,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 65, 0));
@@ -1215,7 +1235,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(originalBlocks, blocks);
@@ -1236,7 +1256,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 4, 61, 4));
@@ -1254,7 +1274,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 50);
assertEquals(originalBlocks, blocks);
@@ -1273,7 +1293,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60, 1);
assertEquals(originalBlocks, blocks);
@@ -1297,7 +1317,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
for (int y = 61; y <= 65; y++) {
@@ -1317,7 +1337,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
new BoundingBox(15, 66, 0, 16, 72, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
NativeStructureTerrainIntegrator.TerrainTarget target =
surfaceTarget(start, IrisStructureTerrainMode.VACUUM);
surfaceTarget(start, StructureTerrainMode.VACUUM);
BoundingBox wideArea = new BoundingBox(0, 54, 0, 31, 74, 15);
BoundingBox westArea = new BoundingBox(0, 54, 0, 15, 74, 15);
BoundingBox eastArea = new BoundingBox(16, 54, 0, 31, 74, 15);
@@ -1573,13 +1593,13 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
Set<Long> vacuumWritten = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(vacuumBlocks), area,
List.of(surfaceTarget(vacuumStart, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(vacuumStart, StructureTerrainMode.VACUUM)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertEquals(Set.of(BlockPos.asLong(0, 63, 0)), vacuumWritten);
assertTrue(NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(supportTerrain(0, 0)), area,
List.of(surfaceTarget(vacuumStart, IrisStructureTerrainMode.SOURCE)),
List.of(surfaceTarget(vacuumStart, StructureTerrainMode.SOURCE)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager).isEmpty());
PoolElementStructurePiece distantUpper = rigidTemplatePiece(
@@ -1589,7 +1609,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
List.of(lower, distantUpper), TerrainAdjustment.NONE);
assertTrue(NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(supportTerrain(0, 0)), area,
List.of(surfaceTarget(distantStart, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(distantStart, StructureTerrainMode.VACUUM)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager).isEmpty());
}
@@ -1676,8 +1696,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
Set<Long> written = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(blocks), area, List.of(
surfaceTarget(start, IrisStructureTerrainMode.PRESERVE),
surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
surfaceTarget(start, StructureTerrainMode.PRESERVE),
surfaceTarget(start, StructureTerrainMode.VACUUM)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertEquals(Set.of(BlockPos.asLong(0, 63, 0)), written);
@@ -2033,7 +2053,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), bounds, "minecraft:desert_pyramid", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.VACUUM), null);
new IrisStructureTerrain().setMode(StructureTerrainMode.VACUUM), null);
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX(), bounds.minY(), bounds.minZ()));
@@ -2055,7 +2075,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "minecraft:desert_pyramid", start,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(1)
.setCeilingPadding(1), null);
@@ -2089,7 +2109,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "minecraft:ancient_city", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.FORCE_CARVE), null);
new IrisStructureTerrain().setMode(StructureTerrainMode.FORCE_CARVE), null);
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, firstBounds.minX(), y, z));
@@ -2338,8 +2358,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "minecraft:ancient_city", start,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setShape(IrisStructureCarveShape.ERODED)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setShape(StructureCarveShape.ERODED)
.setHorizontalPadding(6)
.setCeilingPadding(8)
.setFloorPadding(0)
@@ -2378,7 +2398,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
StructureStart generated = desertStart();
BoundingBox content = generated.getBoundingBox();
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(24);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
@@ -2403,7 +2423,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
StructureStart generated = desertStart(TerrainAdjustment.NONE);
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.VACUUM)
.setMode(StructureTerrainMode.VACUUM)
.setHorizontalPadding(64);
BoundingBox references = NativeStructureReferenceEnvelope.referenceBounds(
@@ -2425,7 +2445,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
ChunkPos origin = generated.getChunkPos();
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(124);
BoundingBox references = NativeStructureReferenceEnvelope.referenceBounds(
@@ -2575,11 +2595,11 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
private static NativeStructureTerrainIntegrator.TerrainTarget surfaceTarget(
StructureStart start) {
return surfaceTarget(start, IrisStructureTerrainMode.SOURCE);
return surfaceTarget(start, StructureTerrainMode.SOURCE);
}
private static NativeStructureTerrainIntegrator.TerrainTarget surfaceTarget(
StructureStart start, IrisStructureTerrainMode mode) {
StructureStart start, StructureTerrainMode mode) {
return new NativeStructureTerrainIntegrator.TerrainTarget(
"nova_structures:tavern_oak", start,
new IrisStructureTerrain().setMode(mode));
@@ -2619,8 +2639,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
private static NativeStructureTerrainIntegrator.OrganicCarve organicCarve(
StructureStart start, int horizontalPadding, double lobeStrength) {
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setShape(IrisStructureCarveShape.ERODED)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setShape(StructureCarveShape.ERODED)
.setHorizontalPadding(horizontalPadding)
.setCeilingPadding(8)
.setFloorPadding(0)
@@ -2630,7 +2650,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
return NativeStructureTerrainIntegrator.organicCarve(
NativeStructureTerrainIntegrator.carveFootprint(start, horizontalPadding,
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager),
terrain, IrisStructureCarveShape.ERODED, TEST_SEED);
terrain, StructureCarveShape.ERODED, TEST_SEED);
}
/**
@@ -2648,8 +2668,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
return true;
}, SLAB_PADDING, 1_000_000);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setShape(IrisStructureCarveShape.ERODED)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setShape(StructureCarveShape.ERODED)
.setHorizontalPadding(SLAB_PADDING)
.setCeilingPadding(8)
.setFloorPadding(0)
@@ -2664,7 +2684,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureTerrainIntegrator.carveOrganicColumns(world(blocks), area,
NativeStructureTerrainIntegrator.organicCarve(
footprint, terrain, IrisStructureCarveShape.ERODED, TEST_SEED));
footprint, terrain, StructureCarveShape.ERODED, TEST_SEED));
int[] depths = new int[SLAB_DEPTH];
for (int z = 0; z < SLAB_DEPTH; z++) {
@@ -1,5 +1,11 @@
package art.arcane.iris.nativegen;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureSurfaceFitter;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureVegetationClearer;
import com.mojang.serialization.Codec;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
@@ -1,5 +1,9 @@
package art.arcane.iris.nativegen;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureTemplateOccupancy;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureReflection;
import com.mojang.datafixers.util.Either;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
@@ -1,5 +1,7 @@
package art.arcane.iris.nativegen;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeTerrainHeightCache;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.levelgen.Heightmap;
@@ -1,10 +1,14 @@
package art.arcane.iris.nativegen;
import art.arcane.iris.world.history.TerrainNativeBlockKeys;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeTransitionColumn;
import art.arcane.iris.world.history.BoundaryColumnGeometry;
import art.arcane.iris.world.history.TerrainBoundarySignature;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import art.arcane.iris.spi.PlatformRegistries;
import art.arcane.iris.testsupport.PlatformLeakGuard;
import net.minecraft.SharedConstants;
@@ -44,8 +48,8 @@ public final class NativeTransitionColumnTest {
@Test
public void customKeysUseCarrierForNativeColumnsAndHeightmaps() {
String key = "itemsadder:rocks/ruby_ore";
PlatformBlockState custom = mock(PlatformBlockState.class);
PlatformBlockState carrier = mock(PlatformBlockState.class);
NativeBlockState custom = mock(NativeBlockState.class);
NativeBlockState carrier = mock(NativeBlockState.class);
when(custom.placementBaseState()).thenReturn(carrier);
when(carrier.key()).thenReturn("minecraft:oak_log[axis=z]");
IrisPlatform platform = mock(IrisPlatform.class);
@@ -58,17 +62,17 @@ public final class NativeTransitionColumnTest {
try (MockedStatic<IrisPlatforms> platforms = mockStatic(IrisPlatforms.class)) {
platforms.when(IrisPlatforms::isBound).thenReturn(true);
platforms.when(IrisPlatforms::get).thenReturn(platform);
NoiseColumn column = NativeTransitionColumn.column(signature, height);
NoiseColumn column = NativeTransitionColumn.column(signature.geometry(), height, TerrainNativeBlockKeys::placementKey);
assertEquals(Direction.Axis.Z, column.getBlock(-15).getValue(RotatedPillarBlock.AXIS));
assertEquals(-14, NativeTransitionColumn.height(signature, Heightmap.Types.OCEAN_FLOOR_WG, height));
assertEquals(-14, NativeTransitionColumn.height(signature.geometry(), Heightmap.Types.OCEAN_FLOOR_WG, height, TerrainNativeBlockKeys::placementKey));
}
}
@Test
public void preservesNativePropertiesCavesAndFluids() {
NoiseColumn column = NativeTransitionColumn.column(signature("minecraft:oak_log[axis=x]"),
LevelHeightAccessor.create(-16, 32));
NoiseColumn column = NativeTransitionColumn.column(signature("minecraft:oak_log[axis=x]").geometry(),
LevelHeightAccessor.create(-16, 32), TerrainNativeBlockKeys::placementKey);
assertEquals(Direction.Axis.X, column.getBlock(-15).getValue(RotatedPillarBlock.AXIS));
assertTrue(column.getBlock(-14).is(Blocks.CAVE_AIR));
assertEquals(Integer.valueOf(3), column.getBlock(-13).getValue(LiquidBlock.LEVEL));
@@ -79,14 +83,13 @@ public final class NativeTransitionColumnTest {
public void usesNativeHeightmapPredicatesForWaterAndOpenSpace() {
TerrainBoundarySignature signature = signature("minecraft:oak_log[axis=x]");
LevelHeightAccessor height = LevelHeightAccessor.create(-16, 32);
assertEquals(-12, NativeTransitionColumn.height(signature, Heightmap.Types.WORLD_SURFACE_WG, height));
assertEquals(-14, NativeTransitionColumn.height(signature, Heightmap.Types.OCEAN_FLOOR_WG, height));
assertEquals(-12, NativeTransitionColumn.height(signature.geometry(), Heightmap.Types.WORLD_SURFACE_WG, height, TerrainNativeBlockKeys::placementKey));
assertEquals(-14, NativeTransitionColumn.height(signature.geometry(), Heightmap.Types.OCEAN_FLOOR_WG, height, TerrainNativeBlockKeys::placementKey));
}
@Test
public void rejectsUnknownSavedStatesInsteadOfReplacingTerrain() {
assertThrows(IllegalArgumentException.class, () -> NativeTransitionColumn.column(
signature("missing:removed_block"), LevelHeightAccessor.create(-16, 32)));
assertThrows(IllegalArgumentException.class, () -> NativeTransitionColumn.column(signature("missing:removed_block").geometry(), LevelHeightAccessor.create(-16, 32), TerrainNativeBlockKeys::placementKey));
}
private static TerrainBoundarySignature signature(String solidState) {
@@ -1,5 +1,7 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.platform.bukkit.nms.BukkitBiomePolicy;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.terrain.IrisDimension;
import net.minecraft.SharedConstants;
@@ -28,10 +30,10 @@ public class CustomBiomeSourceStructureContractTest {
IrisDimension upper = new IrisDimension();
upper.setLoadKey("Layers/Upper");
assertEquals("layers:upper/aurora", CustomBiomeSource.customBiomeKey(upper, "Aurora"));
assertEquals("layers:upper/aurora", BukkitBiomePolicy.customBiomeKey(upper, "Aurora"));
assertNotEquals(
CustomBiomeSource.customBiomeKey(host, "Aurora"),
CustomBiomeSource.customBiomeKey(upper, "Aurora")
BukkitBiomePolicy.customBiomeKey(host, "Aurora"),
BukkitBiomePolicy.customBiomeKey(upper, "Aurora")
);
}
@@ -39,8 +41,8 @@ public class CustomBiomeSourceStructureContractTest {
public void temporaryNaturalBiomeAnswersAreNotMemoized() {
Engine temporary = engineAnsweringNaturalFallback(true, 12, -8);
Engine stable = engineAnsweringNaturalFallback(false, 12, -8);
assertFalse(CustomBiomeSource.isBiomeCacheable(temporary, 12, -8));
assertTrue(CustomBiomeSource.isBiomeCacheable(stable, 12, -8));
assertFalse(BukkitBiomePolicy.isBiomeCacheable(temporary, 12, -8));
assertTrue(BukkitBiomePolicy.isBiomeCacheable(stable, 12, -8));
}
private static Engine engineAnsweringNaturalFallback(
@@ -1,6 +1,6 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.nativegen.NativeStructureLocateResults;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureLocateResults;
import com.mojang.datafixers.util.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
@@ -1,5 +1,10 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.platform.bukkit.nms.BukkitGeneratorContext;
import org.bukkit.World;
import art.arcane.volmlib.nativelib.v26_2_R1.terrain.NativeChunkGenerator;
import art.arcane.volmlib.nativelib.v26_2_R1.terrain.NativeBiomeSourceImpl;
import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.generation.runtime.GenerationSessionManager;
import art.arcane.iris.generation.runtime.GenerationTransitionGate;
@@ -89,8 +94,8 @@ public class IrisChunkGeneratorSpawnAdmissionTest {
}
}
private static void setField(IrisChunkGenerator generator, String name, Object value) throws Exception {
Field field = IrisChunkGenerator.class.getDeclaredField(name);
private static void setField(NativeChunkGenerator<?, ?, ?, ?, ?> generator, String name, Object value) throws Exception {
Field field = NativeChunkGenerator.class.getDeclaredField(name);
field.setAccessible(true);
field.set(generator, value);
}
@@ -98,7 +103,7 @@ public class IrisChunkGeneratorSpawnAdmissionTest {
private static final class Fixture {
private final GenerationSessionManager sessions = new GenerationSessionManager(true);
private final IrisEngine engine = mock(IrisEngine.class);
private final IrisChunkGenerator generator = mock(IrisChunkGenerator.class, CALLS_REAL_METHODS);
private final NativeChunkGenerator<?, ?, ?, ?, ?> generator = mock(NativeChunkGenerator.class, CALLS_REAL_METHODS);
private final Holder<Biome> biome = Holder.direct(mock(Biome.class));
private final StructureManager structures = mock(StructureManager.class);
private final WeightedList<MobSpawnSettings.SpawnerData> expected = mock(WeightedList.class);
@@ -110,7 +115,7 @@ public class IrisChunkGeneratorSpawnAdmissionTest {
GenerationHistoryRuntimeRouter router = mock(GenerationHistoryRuntimeRouter.class);
SavedBiomeRuntime saved = mock(SavedBiomeRuntime.class);
ChunkGenerator delegate = mock(ChunkGenerator.class);
CustomBiomeSource source = mock(CustomBiomeSource.class);
NativeBiomeSourceImpl source = mock(NativeBiomeSourceImpl.class);
when(engine.getGenerationSessions()).thenReturn(sessions);
when(engine.getGenerationHistoryRuntimeRouter()).thenReturn(Optional.of(router));
when(router.biomes()).thenReturn(saved);
@@ -126,7 +131,7 @@ public class IrisChunkGeneratorSpawnAdmissionTest {
});
when(delegate.getMobsAt(biome, structures, MobCategory.MONSTER, new BlockPos(0, 64, 0)))
.thenReturn(expected);
setField(generator, "engine", engine);
setField(generator, "context", new BukkitGeneratorContext(new BukkitGeneratorContext.Options(1L, engine, mock(World.class))));
setField(generator, "delegate", delegate);
setField(generator, "customBiomeSource", source);
}
@@ -1,5 +1,7 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.volmlib.nativelib.v26_2_R1.terrain.NativeStructureSetFilter;
import art.arcane.iris.pack.datapack.DatapackIngestService;
import art.arcane.iris.pack.datapack.DatapackStructureScopeIndex;
import art.arcane.iris.structure.nativegen.IrisImportedStructureControl;
@@ -59,9 +61,9 @@ public class NMSBindingDatapackStructureScopeTest {
List.of(),
List.of("managed:illager_barracks"));
DatapackStructureStateFilter.Selection vanilla = DatapackStructureStateFilter.filter(
NativeStructureSetFilter.Selection vanilla = NativeStructureSetFilter.filter(
List.of(managedSet), index, Set.of());
DatapackStructureStateFilter.Selection declaring = DatapackStructureStateFilter.filter(
NativeStructureSetFilter.Selection declaring = NativeStructureSetFilter.filter(
List.of(managedSet), index, index.declaredSources(List.of(SOURCE)));
assertEquals(0, vanilla.structureSets().size());
@@ -80,9 +82,9 @@ public class NMSBindingDatapackStructureScopeTest {
List.of("managed:tavern"),
List.of());
DatapackStructureStateFilter.Selection vanilla = DatapackStructureStateFilter.filter(
NativeStructureSetFilter.Selection vanilla = NativeStructureSetFilter.filter(
List.of(vanillaSet), index, Set.of());
DatapackStructureStateFilter.Selection declaring = DatapackStructureStateFilter.filter(
NativeStructureSetFilter.Selection declaring = NativeStructureSetFilter.filter(
List.of(vanillaSet), index, index.declaredSources(List.of(SOURCE)));
assertEquals(1, vanilla.structureSets().size());
@@ -100,7 +102,7 @@ public class NMSBindingDatapackStructureScopeTest {
"minecraft:custom", structureHolder("managed:only"));
DatapackStructureScopeIndex index = index(List.of("managed:only"), List.of());
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
NativeStructureSetFilter.Selection selection = NativeStructureSetFilter.filter(
List.of(unmanagedSet), index, Set.of());
assertEquals(0, selection.structureSets().size());
@@ -124,18 +126,18 @@ public class NMSBindingDatapackStructureScopeTest {
34, 8, RandomSpreadType.LINEAR, 14357620)));
DatapackStructureScopeIndex scopeIndex = index(
List.of(), List.of("managed:illager_barracks"));
DatapackStructureStateFilter.StructureSetKeyIndex keyIndex =
DatapackStructureStateFilter.keyIndex(List.of(registered));
NativeStructureSetFilter.StructureSetKeyIndex keyIndex =
NativeStructureSetFilter.keyIndex(List.of(registered));
DatapackStructureStateFilter.Selection excluded =
DatapackStructureStateFilter.filter(
NativeStructureSetFilter.Selection excluded =
NativeStructureSetFilter.filter(
List.of(spigotDirect),
scopeIndex,
Set.of(),
new IrisImportedStructureControl(),
keyIndex);
DatapackStructureStateFilter.Selection retained =
DatapackStructureStateFilter.filter(
NativeStructureSetFilter.Selection retained =
NativeStructureSetFilter.filter(
List.of(spigotDirect),
scopeIndex,
scopeIndex.declaredSources(List.of(SOURCE)),
@@ -150,8 +152,8 @@ public class NMSBindingDatapackStructureScopeTest {
@Test
public void structureScopeDoesNotLinkPaperOnlyPlacementClasses() throws IOException {
InputStream classResource = NMSBindingDatapackStructureScopeTest.class
.getResourceAsStream("DatapackStructureStateFilter.class");
InputStream classResource = NativeStructureSetFilter.class
.getResourceAsStream("NativeStructureSetFilter.class");
assertNotNull(classResource);
try (InputStream input = classResource) {
String classFile = new String(input.readAllBytes(), StandardCharsets.ISO_8859_1);
@@ -176,7 +178,7 @@ public class NMSBindingDatapackStructureScopeTest {
IrisImportedStructureControl control = new IrisImportedStructureControl()
.setFrequencyOverrides(overrides);
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
NativeStructureSetFilter.Selection selection = NativeStructureSetFilter.filter(
List.of(complexes, fossils), index(List.of(), List.of()), Set.of(), control);
RandomSpreadStructurePlacement scaledComplexes =
@@ -206,7 +208,7 @@ public class NMSBindingDatapackStructureScopeTest {
IrisImportedStructureControl control = new IrisImportedStructureControl()
.setFrequencyOverrides(overrides);
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
NativeStructureSetFilter.Selection selection = NativeStructureSetFilter.filter(
List.of(custom), index(List.of(), List.of()), Set.of(), control);
assertSame(custom, selection.structureSets().getFirst());
@@ -238,14 +240,14 @@ public class NMSBindingDatapackStructureScopeTest {
IrisImportedStructureControl control = new IrisImportedStructureControl()
.setFrequencyOverrides(overrides);
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
NativeStructureSetFilter.Selection selection = NativeStructureSetFilter.filter(
List.of(dependent, target), index(List.of(), List.of()), Set.of(), control);
Holder<StructureSet> scaledDependent = selection.structureSets().get(0);
Holder<StructureSet> scaledTarget = selection.structureSets().get(1);
assertNotSame(dependent, scaledDependent);
assertNotSame(target, scaledTarget);
assertSame(scaledTarget, DatapackStructureStateFilter.exclusionZone(
assertSame(scaledTarget, NativeStructureSetFilter.exclusionZone(
scaledDependent.value().placement()).orElseThrow().otherSet());
assertEquals(26, ((RandomSpreadStructurePlacement)
scaledTarget.value().placement()).spacing());
@@ -284,14 +286,14 @@ public class NMSBindingDatapackStructureScopeTest {
IrisImportedStructureControl control = new IrisImportedStructureControl()
.setFrequencyOverrides(overrides);
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
NativeStructureSetFilter.Selection selection = NativeStructureSetFilter.filter(
List.of(first, second), index(List.of(), List.of()), Set.of(), control);
Holder<StructureSet> scaledFirst = selection.structureSets().get(0);
Holder<StructureSet> scaledSecond = selection.structureSets().get(1);
assertSame(scaledSecond, DatapackStructureStateFilter.exclusionZone(
assertSame(scaledSecond, NativeStructureSetFilter.exclusionZone(
scaledFirst.value().placement()).orElseThrow().otherSet());
assertSame(scaledFirst, DatapackStructureStateFilter.exclusionZone(
assertSame(scaledFirst, NativeStructureSetFilter.exclusionZone(
scaledSecond.value().placement()).orElseThrow().otherSet());
assertEquals(26, ((RandomSpreadStructurePlacement)
scaledSecond.value().placement()).spacing());
@@ -310,7 +312,7 @@ public class NMSBindingDatapackStructureScopeTest {
IrisImportedStructureControl control = new IrisImportedStructureControl()
.setFrequencyOverrides(overrides);
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
NativeStructureSetFilter.Selection selection = NativeStructureSetFilter.filter(
List.of(custom), index(List.of(), List.of()), Set.of(), control);
StructurePlacement placement = selection.structureSets().getFirst().value().placement();
@@ -340,12 +342,12 @@ public class NMSBindingDatapackStructureScopeTest {
List.of("managed:blocked"),
List.of("managed:blocked"));
DatapackStructureStateFilter.Selection selection = DatapackStructureStateFilter.filter(
NativeStructureSetFilter.Selection selection = NativeStructureSetFilter.filter(
List.of(vanillaSet, managedSet), index, Set.of());
assertEquals(1, selection.structureSets().size());
StructurePlacement scopedPlacement = selection.structureSets().getFirst().value().placement();
assertEquals(0, DatapackStructureStateFilter.exclusionZone(scopedPlacement).stream().count());
assertEquals(0, NativeStructureSetFilter.exclusionZone(scopedPlacement).stream().count());
}
private static DatapackStructureScopeIndex index(
@@ -1,5 +1,7 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.volmlib.nativelib.v26_2_R1.terrain.NativeStructurePoiUpdates;
import ca.spottedleaf.moonrise.common.util.TickThread;
import com.mojang.serialization.Codec;
import net.minecraft.SharedConstants;
@@ -1,9 +1,11 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.volmlib.nativelib.v26_2_R1.terrain.NativeStructureWorldgenAccess;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.iris.nativegen.NativeStructurePostProcessor;
import art.arcane.iris.nativegen.NativeStructureTerrainIntegrator;
import art.arcane.volmlib.util.structure.StructureTerrainMode;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructurePostProcessor;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureTerrainIntegrator;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@@ -84,7 +86,7 @@ public class NativeStructureWorldgenAccessTest {
StructureStart start = new StructureStart(structure, generationCenter(), 0, new PiecesContainer(List.of(piece)));
List<NativeStructureTerrainIntegrator.TerrainTarget> targets = List.of(
new NativeStructureTerrainIntegrator.TerrainTarget("minecraft:village_plains", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.FLATTEN)));
new IrisStructureTerrain().setMode(StructureTerrainMode.FLATTEN)));
BoundingBox area = new BoundingBox(generationCenter().getMinBlockX(), -64, generationCenter().getMinBlockZ(),
generationCenter().getMaxBlockX(), 319, generationCenter().getMaxBlockZ());
RecordingDelegate unbounded = new RecordingDelegate();
@@ -1,5 +1,6 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.volmlib.nativelib.v26_2_R1.terrain.NativeTerrainAccessImpl;
import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
@@ -83,7 +84,7 @@ public class NmsChunkIOFlushTest {
}
private static final class Fixture {
private final NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
private final NativeTerrainAccessImpl binding = mock(NativeTerrainAccessImpl.class, CALLS_REAL_METHODS);
private final CraftWorld world = mock(CraftWorld.class);
private final ServerLevel level = mock(ServerLevel.class);
private final RegionDataController chunks = mock(RegionDataController.class);
@@ -1,5 +1,6 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.volmlib.nativelib.v26_2_R1.terrain.NativeTerrainAccessImpl;
import art.arcane.volmlib.util.scheduling.FoliaScheduler;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
@@ -41,7 +42,7 @@ public class NmsChunkTaskPollingTest {
ServerLevel level = mock(ServerLevel.class);
MinecraftServer server = mock(MinecraftServer.class);
ServerChunkCache chunks = mock(ServerChunkCache.class);
NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
NativeTerrainAccessImpl binding = mock(NativeTerrainAccessImpl.class, CALLS_REAL_METHODS);
CompletableFuture<Void> completed = new CompletableFuture<>();
when(world.getHandle()).thenReturn(level);
when(level.getServer()).thenReturn(server);
@@ -66,7 +67,7 @@ public class NmsChunkTaskPollingTest {
CraftWorld world = mock(CraftWorld.class);
ServerLevel level = mock(ServerLevel.class);
MinecraftServer server = mock(MinecraftServer.class);
NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
NativeTerrainAccessImpl binding = mock(NativeTerrainAccessImpl.class, CALLS_REAL_METHODS);
when(world.getHandle()).thenReturn(level);
when(level.getServer()).thenReturn(server);
when(server.getRunningThread()).thenReturn(new Thread("Other server owner"));
@@ -83,7 +84,7 @@ public class NmsChunkTaskPollingTest {
public void foliaNeverUsesTheWorldWideTaskPump() {
Server bukkitServer = mock(Server.class);
CraftWorld world = mock(CraftWorld.class);
NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
NativeTerrainAccessImpl binding = mock(NativeTerrainAccessImpl.class, CALLS_REAL_METHODS);
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class);
MockedStatic<FoliaScheduler> folia = mockStatic(FoliaScheduler.class)) {
bukkit.when(Bukkit::getServer).thenReturn(bukkitServer);
@@ -1,5 +1,6 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.volmlib.nativelib.v26_2_R1.terrain.NativeTerrainAccessImpl;
import net.minecraft.SharedConstants;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.Identifier;
@@ -41,7 +42,7 @@ public class NmsEntitySpawnDifficultyTest {
public void peacefulRejectsEveryNativeForbiddenTypeBeforeBukkitCreatesIt() {
CraftWorld world = mock(CraftWorld.class);
when(world.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
NativeTerrainAccessImpl binding = mock(NativeTerrainAccessImpl.class, CALLS_REAL_METHODS);
Location location = new Location(world, 0.5, 80, 0.5);
int rejected = 0;
@@ -64,7 +65,7 @@ public class NmsEntitySpawnDifficultyTest {
public void peacefulPreservesPassiveAndNativeMonsterCategoryExceptions() {
CraftWorld world = mock(CraftWorld.class);
when(world.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
NativeTerrainAccessImpl binding = mock(NativeTerrainAccessImpl.class, CALLS_REAL_METHODS);
Location location = new Location(world, 0.5, 80, 0.5);
Entity created = mock(Entity.class);
doReturn(created).when(world).spawn(eq(location), any(Class.class), isNull(), eq(CreatureSpawnEvent.SpawnReason.NATURAL));
@@ -77,7 +78,7 @@ public class NmsEntitySpawnDifficultyTest {
@Test
public void nonPeacefulDifficultiesPreserveHostileSpawns() {
CraftWorld world = mock(CraftWorld.class);
NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
NativeTerrainAccessImpl binding = mock(NativeTerrainAccessImpl.class, CALLS_REAL_METHODS);
Location location = new Location(world, 0.5, 80, 0.5);
Entity created = mock(Entity.class);
doReturn(created).when(world).spawn(eq(location), any(Class.class), isNull(), eq(CreatureSpawnEvent.SpawnReason.NATURAL));
@@ -92,7 +93,7 @@ public class NmsEntitySpawnDifficultyTest {
public void unrelatedBukkitSpawnFailuresStillPropagate() {
CraftWorld world = mock(CraftWorld.class);
when(world.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
NMSBinding binding = mock(NMSBinding.class, CALLS_REAL_METHODS);
NativeTerrainAccessImpl binding = mock(NativeTerrainAccessImpl.class, CALLS_REAL_METHODS);
Location location = new Location(world, 0.5, 80, 0.5);
IllegalStateException failure = new IllegalStateException("Spawn rejected by platform");
doThrow(failure).when(world).spawn(eq(location), any(Class.class), isNull(), eq(CreatureSpawnEvent.SpawnReason.NATURAL));
@@ -1,114 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.world.history.SavedTerrainChunk;
import ca.spottedleaf.concurrentutil.util.Priority;
import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
public class NmsSavedTerrainCheckpointTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void checkpointWaitsForDirtyAndUnloadedChunkWritesBeforeFlushAndVerification() throws Exception {
ServerLevel level = mock(ServerLevel.class);
Path root = Path.of("checkpoint-world");
byte[] receipt = new byte[]{1, 2, 3};
NmsSavedTerrainCapture.Checkpoint saved = new NmsSavedTerrainCapture.Checkpoint(new ChunkPos(4, -5),
new NmsSavedTerrainCapture.Verification("minecraft:full", receipt, 7L));
NmsSavedTerrainCapture.Checkpoint unloaded = NmsSavedTerrainCapture.checkpoint(-8, 9, null);
Set<ChunkPos> completed = new HashSet<>();
AtomicBoolean flushed = new AtomicBoolean();
try (MockedStatic<MoonriseRegionFileIO> io = mockStatic(MoonriseRegionFileIO.class);
MockedStatic<SavedTerrainChunk> terrain = mockStatic(SavedTerrainChunk.class)) {
io.when(() -> MoonriseRegionFileIO.getPriority(level, 4, -5, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA))
.thenAnswer(invocation -> {
completed.add(saved.chunk());
return Priority.COMPLETING;
});
io.when(() -> MoonriseRegionFileIO.getPriority(level, -8, 9, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA))
.thenAnswer(invocation -> {
completed.add(unloaded.chunk());
return Priority.COMPLETING;
});
io.when(() -> MoonriseRegionFileIO.flushRegionStorages(level, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA))
.thenAnswer(invocation -> {
assertEquals(Set.of(saved.chunk(), unloaded.chunk()), completed);
flushed.set(true);
return null;
});
terrain.when(() -> SavedTerrainChunk.verifyCheckpoint(root, 4, -5, "minecraft:full", receipt, 7L))
.thenAnswer(invocation -> {
assertTrue(flushed.get());
return null;
});
NmsSavedTerrainCapture.finishCheckpoint(level, root, List.of(saved, unloaded));
terrain.verify(() -> SavedTerrainChunk.verifyCheckpoint(root, 4, -5, "minecraft:full", receipt, 7L));
io.verify(() -> MoonriseRegionFileIO.flush(level, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA), never());
io.verify(() -> MoonriseRegionFileIO.getPriority(level, 100, 100, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA), never());
terrain.verifyNoMoreInteractions();
}
}
@Test
public void storageFlushFailureCannotCompleteTheCheckpoint() throws Exception {
ServerLevel level = mock(ServerLevel.class);
IOException failure = new IOException("storage flush failed");
try (MockedStatic<MoonriseRegionFileIO> io = mockStatic(MoonriseRegionFileIO.class);
MockedStatic<SavedTerrainChunk> terrain = mockStatic(SavedTerrainChunk.class)) {
io.when(() -> MoonriseRegionFileIO.flushRegionStorages(level, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA))
.thenThrow(failure);
assertSame(failure, assertThrows(IOException.class,
() -> NmsSavedTerrainCapture.finishCheckpoint(level, Path.of("checkpoint-world"), List.of())));
terrain.verifyNoInteractions();
}
}
@Test
public void persistedReceiptMismatchCannotCompleteTheCheckpoint() throws Exception {
ServerLevel level = mock(ServerLevel.class);
Path root = Path.of("checkpoint-world");
byte[] receipt = new byte[]{4, 5};
IOException failure = new IOException("saved terrain receipt mismatch");
NmsSavedTerrainCapture.Checkpoint saved = new NmsSavedTerrainCapture.Checkpoint(new ChunkPos(1, 2),
new NmsSavedTerrainCapture.Verification("minecraft:noise", receipt, 3L));
try (MockedStatic<MoonriseRegionFileIO> io = mockStatic(MoonriseRegionFileIO.class);
MockedStatic<SavedTerrainChunk> terrain = mockStatic(SavedTerrainChunk.class)) {
io.when(() -> MoonriseRegionFileIO.getPriority(level, 1, 2, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA))
.thenReturn(Priority.COMPLETING);
terrain.when(() -> SavedTerrainChunk.verifyCheckpoint(root, 1, 2, "minecraft:noise", receipt, 3L))
.thenThrow(failure);
assertSame(failure, assertThrows(IOException.class,
() -> NmsSavedTerrainCapture.finishCheckpoint(level, root, List.of(saved))));
io.verify(() -> MoonriseRegionFileIO.flushRegionStorages(level, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA));
}
}
}
@@ -1,223 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.world.task.J;
import ca.spottedleaf.concurrentutil.completable.Completable;
import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
import ca.spottedleaf.concurrentutil.executor.thread.BalancedPrioritisedThreadPool;
import ca.spottedleaf.concurrentutil.util.Priority;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import net.minecraft.SharedConstants;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import org.bukkit.World;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class NmsSavedTerrainSnapshotTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void ownerReservesSaveOrderBeforeOffOwnerSerializationAndLaterAutosave() throws Exception {
try (SaveFixture fixture = new SaveFixture();
ExecutorService checkpoint = Executors.newSingleThreadExecutor();
MockedStatic<SerializableChunkData> snapshots = mockStatic(SerializableChunkData.class);
MockedStatic<PlatformHooks> hooks = mockStatic(PlatformHooks.class);
MockedStatic<MoonriseRegionFileIO> io = mockStatic(MoonriseRegionFileIO.class)) {
Thread owner = Thread.currentThread();
ChunkAccess chunk = mock(ChunkAccess.class);
SerializableChunkData snapshot = mock(SerializableChunkData.class);
PlatformHooks platform = mock(PlatformHooks.class);
CompoundTag capturedData = new CompoundTag();
CompoundTag autosave = new CompoundTag();
List<String> submissions = new ArrayList<>();
when(chunk.getPos()).thenReturn(new ChunkPos(4, -9));
snapshots.when(() -> SerializableChunkData.copyOf(fixture.level, chunk)).thenAnswer(invocation -> {
assertSame(owner, Thread.currentThread());
return snapshot;
});
hooks.when(PlatformHooks::get).thenReturn(platform);
doAnswer(invocation -> {
assertSame(owner, Thread.currentThread());
return null;
}).when(platform).chunkSyncSave(fixture.level, chunk, snapshot);
when(snapshot.write()).thenAnswer(invocation -> {
assertNotSame(owner, Thread.currentThread());
assertEquals(List.of("checkpoint", "autosave"), submissions);
return capturedData;
});
io.when(() -> MoonriseRegionFileIO.scheduleSave(eq(fixture.level), eq(4), eq(-9),
any(Completable.class), any(PrioritisedExecutor.PrioritisedTask.class),
eq(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA), eq(Priority.NORMAL)))
.thenAnswer(invocation -> {
assertSame(owner, Thread.currentThread());
submissions.add("checkpoint");
return null;
});
io.when(() -> MoonriseRegionFileIO.scheduleSave(fixture.level, 4, -9, autosave,
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA)).thenAnswer(invocation -> {
submissions.add("autosave");
return null;
});
NmsSavedTerrainCapture.CapturedChunk captured = NmsSavedTerrainCapture.scheduleSnapshot(fixture.level, chunk);
assertEquals(List.of("checkpoint"), submissions);
assertFalse(captured.data().isDone());
verify(snapshot, never()).write();
verify(platform).chunkSyncSave(fixture.level, chunk, snapshot);
MoonriseRegionFileIO.scheduleSave(fixture.level, 4, -9, autosave,
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA);
Future<CompoundTag> result = checkpoint.submit(() -> NmsSavedTerrainCapture.awaitSnapshot(
fixture.level, captured, System.nanoTime() + TimeUnit.SECONDS.toNanos(3)));
assertSame(capturedData, result.get(5, TimeUnit.SECONDS));
verify(snapshot).write();
}
}
@Test
public void failedOffOwnerSerializationCannotSucceedAsACheckpoint() throws Exception {
try (SaveFixture fixture = new SaveFixture();
ExecutorService checkpoint = Executors.newSingleThreadExecutor();
MockedStatic<SerializableChunkData> snapshots = mockStatic(SerializableChunkData.class);
MockedStatic<PlatformHooks> hooks = mockStatic(PlatformHooks.class);
MockedStatic<MoonriseRegionFileIO> io = mockStatic(MoonriseRegionFileIO.class)) {
ChunkAccess chunk = mock(ChunkAccess.class);
SerializableChunkData snapshot = mock(SerializableChunkData.class);
IllegalStateException failure = new IllegalStateException("serialization failed");
when(chunk.getPos()).thenReturn(new ChunkPos(4, -9));
snapshots.when(() -> SerializableChunkData.copyOf(fixture.level, chunk)).thenReturn(snapshot);
hooks.when(PlatformHooks::get).thenReturn(mock(PlatformHooks.class));
when(snapshot.write()).thenThrow(failure);
NmsSavedTerrainCapture.CapturedChunk captured = NmsSavedTerrainCapture.scheduleSnapshot(fixture.level, chunk);
Future<IOException> result = checkpoint.submit(() -> assertThrows(IOException.class,
() -> NmsSavedTerrainCapture.awaitSnapshot(fixture.level, captured,
System.nanoTime() + TimeUnit.SECONDS.toNanos(3))));
assertSame(failure, result.get(5, TimeUnit.SECONDS).getCause());
assertTrue(captured.data().isCompletedExceptionally());
}
}
@Test
public void checkpointBoundsOwnerRequestsUntilPriorNativeWritesFinish() throws Exception {
try (SaveFixture fixture = new SaveFixture();
ExecutorService owner = Executors.newSingleThreadExecutor();
ExecutorService checkpoint = Executors.newSingleThreadExecutor()) {
World world = mock(World.class);
when(world.getWorldFolder()).thenReturn(new File("checkpoint-world"));
List<NewChunkHolder> holders = new ArrayList<>(129);
for (int index = 0; index < 129; index++) {
NewChunkHolder holder = mock(NewChunkHolder.class);
setField(holder, "chunkX", index);
holders.add(holder);
}
AtomicInteger requested = new AtomicInteger();
AtomicBoolean writesBlocked = new AtomicBoolean(true);
CountDownLatch firstBatchWaiting = new CountDownLatch(1);
Future<?> result = checkpoint.submit(() -> {
try (MockedStatic<J> scheduling = mockStatic(J.class);
MockedStatic<MoonriseRegionFileIO> io = mockStatic(MoonriseRegionFileIO.class)) {
scheduling.when(() -> J.runRegionFuture(eq(world), anyInt(), anyInt(), any(Runnable.class)))
.thenAnswer(invocation -> {
requested.incrementAndGet();
Runnable action = invocation.getArgument(3);
return CompletableFuture.runAsync(action, owner);
});
io.when(() -> MoonriseRegionFileIO.getPriority(eq(fixture.level), anyInt(), anyInt(),
eq(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA))).thenAnswer(invocation -> {
firstBatchWaiting.countDown();
return writesBlocked.get() ? Priority.NORMAL : Priority.COMPLETING;
});
NmsSavedTerrainCapture.flushSnapshots(world, fixture.level, holders);
io.verify(() -> MoonriseRegionFileIO.getPriority(fixture.level, 128, 0,
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA), times(2));
return null;
}
});
try {
assertTrue(firstBatchWaiting.await(5, TimeUnit.SECONDS));
assertEquals(64, requested.get());
assertTrue(owner.submit(() -> true).get(5, TimeUnit.SECONDS));
assertFalse(result.isDone());
writesBlocked.set(false);
result.get(5, TimeUnit.SECONDS);
assertEquals(129, requested.get());
} finally {
writesBlocked.set(false);
}
}
}
private static void setField(Object instance, String name, Object value) throws Exception {
Field field = instance.getClass().getField(name);
field.setAccessible(true);
field.set(instance, value);
}
private static final class SaveFixture implements AutoCloseable {
private final BalancedPrioritisedThreadPool pool = new BalancedPrioritisedThreadPool(
TimeUnit.MILLISECONDS.toNanos(1), Executors.defaultThreadFactory());
private final ServerLevel level = mock(ServerLevel.class);
private SaveFixture() throws Exception {
BalancedPrioritisedThreadPool.OrderedStreamGroup group = pool.createOrderedStreamGroup();
ChunkTaskScheduler scheduler = mock(ChunkTaskScheduler.class);
setField(scheduler, "saveExecutor", group.createExecutor());
setField(scheduler, "compressionExecutor", group.createExecutor());
setField(scheduler, "chunkHolderManager", mock(ChunkHolderManager.class));
when(level.moonrise$getChunkTaskScheduler()).thenReturn(scheduler);
}
@Override
public void close() throws Exception {
pool.halt(true);
assertTrue(pool.joinInterruptable(5_000));
}
}
}
@@ -1,135 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import ca.spottedleaf.concurrentutil.executor.thread.BalancedPrioritisedThreadPool;
import ca.spottedleaf.concurrentutil.util.Priority;
import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
public class NmsSavedTerrainWriteBarrierTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void checkpointHelpsSaveAndCompressionWhileNativeGenerationOccupiesTheWorker() throws Exception {
BalancedPrioritisedThreadPool pool = new BalancedPrioritisedThreadPool(
TimeUnit.MILLISECONDS.toNanos(1), Executors.defaultThreadFactory());
BalancedPrioritisedThreadPool.OrderedStreamGroup group = pool.createOrderedStreamGroup();
BalancedPrioritisedThreadPool.OrderedStreamGroup.Queue generation = group.createExecutor();
BalancedPrioritisedThreadPool.OrderedStreamGroup.Queue saves = group.createExecutor();
BalancedPrioritisedThreadPool.OrderedStreamGroup.Queue compression = group.createExecutor();
ServerLevel level = level(saves, compression);
ChunkPos chunk = new ChunkPos(4, -9);
CountDownLatch generationEntered = new CountDownLatch(1);
CountDownLatch releaseGeneration = new CountDownLatch(1);
AtomicBoolean pendingWrite = new AtomicBoolean(true);
AtomicInteger serialized = new AtomicInteger();
AtomicInteger compressed = new AtomicInteger();
AtomicReference<Thread> saveThread = new AtomicReference<>();
AtomicReference<Thread> compressionThread = new AtomicReference<>();
try (MockedStatic<MoonriseRegionFileIO> io = mockStatic(MoonriseRegionFileIO.class)) {
io.when(() -> MoonriseRegionFileIO.getPriority(level, chunk.x(), chunk.z(),
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA))
.thenAnswer(invocation -> pendingWrite.get() ? Priority.NORMAL : Priority.COMPLETING);
generation.queueTask(() -> {
generationEntered.countDown();
try {
assertTrue(releaseGeneration.await(5, TimeUnit.SECONDS));
} catch (InterruptedException interruption) {
Thread.currentThread().interrupt();
throw new AssertionError(interruption);
}
});
pool.adjustThreadCount(1);
assertTrue(generationEntered.await(5, TimeUnit.SECONDS));
saves.queueTask(() -> {
saveThread.set(Thread.currentThread());
serialized.incrementAndGet();
compression.queueTask(() -> {
compressionThread.set(Thread.currentThread());
compressed.incrementAndGet();
pendingWrite.set(false);
});
});
NmsSavedTerrainCapture.awaitWrite(level, chunk, System.nanoTime() + TimeUnit.SECONDS.toNanos(3));
assertEquals(1L, releaseGeneration.getCount());
assertEquals(1, serialized.get());
assertEquals(1, compressed.get());
assertFalse(pendingWrite.get());
assertSame(Thread.currentThread(), saveThread.get());
assertSame(Thread.currentThread(), compressionThread.get());
} finally {
releaseGeneration.countDown();
pool.halt(true);
assertTrue(pool.joinInterruptable(5_000));
}
}
@Test
public void waitingForNativeWriteHonorsTheSharedDeadline() throws Exception {
ServerLevel level = mock(ServerLevel.class);
ChunkPos chunk = new ChunkPos(2, 3);
IOException failure = assertThrows(IOException.class,
() -> NmsSavedTerrainCapture.awaitWrite(level, chunk, System.nanoTime() - 1L));
assertTrue(failure.getMessage().contains("Timed out"));
}
@Test
public void interruptedNativeWriteWaitPreservesInterruption() throws Exception {
ServerLevel level = mock(ServerLevel.class);
ChunkPos chunk = new ChunkPos(2, 3);
Thread.currentThread().interrupt();
try {
IOException failure = assertThrows(IOException.class,
() -> NmsSavedTerrainCapture.awaitWrite(level, chunk,
System.nanoTime() + TimeUnit.SECONDS.toNanos(3)));
assertTrue(failure.getMessage().contains("Interrupted"));
assertTrue(Thread.currentThread().isInterrupted());
} finally {
Thread.interrupted();
}
}
private static ServerLevel level(BalancedPrioritisedThreadPool.OrderedStreamGroup.Queue saves,
BalancedPrioritisedThreadPool.OrderedStreamGroup.Queue compression) throws Exception {
ServerLevel level = mock(ServerLevel.class);
ChunkTaskScheduler scheduler = mock(ChunkTaskScheduler.class);
Field savesField = ChunkTaskScheduler.class.getField("saveExecutor");
Field compressionField = ChunkTaskScheduler.class.getField("compressionExecutor");
savesField.setAccessible(true);
compressionField.setAccessible(true);
savesField.set(scheduler, saves);
compressionField.set(scheduler, compression);
when(level.moonrise$getChunkTaskScheduler()).thenReturn(scheduler);
return level;
}
}
@@ -1,6 +1,8 @@
package art.arcane.iris.platform.bukkit.nms.v26_2_R1;
import art.arcane.iris.nativegen.WorldgenTerrainHeightmaps;
import art.arcane.volmlib.nativelib.v26_2_R1.terrain.NativeTerrainPipeline;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.WorldgenTerrainHeightmaps;
import com.mojang.serialization.Codec;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
@@ -68,7 +70,7 @@ public class WorldgenTerrainHeightmapsTest {
public void authoringHeightmapsReadPlacedObjectsAndFluidsWithoutTerrainResolvers() {
ProtoChunk chunk = terrainChunk(new ChunkPos(0, 0));
IrisChunkGenerator.primeAuthoringHeightmaps(chunk);
NativeTerrainPipeline.primeAuthoringHeightmaps(chunk);
assertEquals(LAND_CANOPY_TOP, chunk.getHeight(Heightmap.Types.WORLD_SURFACE_WG, LAND_X, LAND_Z));
assertEquals(LAND_CANOPY_TOP, chunk.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, LAND_X, LAND_Z));
@@ -18,6 +18,7 @@
package art.arcane.iris.structure;
import art.arcane.volmlib.nativelib.common.structure.StructureReaderFixture;
import art.arcane.iris.structure.authoring.StructureCapability;
import art.arcane.iris.structure.authoring.StructureLoss;
import net.minecraft.SharedConstants;
@@ -33,7 +34,6 @@ import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class VillageImporterListPoolElementRuntimeContractTest {
@@ -54,7 +54,7 @@ public class VillageImporterListPoolElementRuntimeContractTest {
StructureTemplatePool.Projection.RIGID
);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(towers);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(StructureReaderFixture.element(towers));
Map<String, Object> entry = VillageImporter.piecePoolEntry(
"qa/pillager_outpost/piece/minecraft/pillager_outpost/watchtower",
1
@@ -85,7 +85,7 @@ public class VillageImporterListPoolElementRuntimeContractTest {
0,
List.of(Map.of("name", "minecraft:bottom")));
assertSame(tower, resolution.physicalElement());
assertEquals(tower.getClass().getSimpleName(), resolution.physicalElement().typeName());
assertEquals("minecraft:pillager_outpost/watchtower", resolution.templateLocation());
assertEquals(1, resolution.omittedElements());
assertEquals("qa/pillager_outpost/piece/minecraft/pillager_outpost/watchtower", entry.get("piece"));
@@ -103,7 +103,7 @@ public class VillageImporterListPoolElementRuntimeContractTest {
StructurePoolElement waystone = StructurePoolElement
.legacy("dungeons_and_taverns:waystones/waystone_scaffold")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(waystone);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(StructureReaderFixture.element(waystone));
VillageImporter.PoolMemberNormalization normalization = VillageImporter.normalizePoolMember(
"dungeons_and_taverns:waystones",
"qa/pool/dungeons_and_taverns/waystones",
@@ -116,7 +116,7 @@ public class VillageImporterListPoolElementRuntimeContractTest {
0,
List.of());
assertSame(waystone, resolution.physicalElement());
assertEquals(waystone.getClass().getSimpleName(), resolution.physicalElement().typeName());
assertEquals("dungeons_and_taverns:waystones/waystone_scaffold", resolution.templateLocation());
assertEquals(VillageImporter.PoolMemberDisposition.EMPTY, normalization.disposition());
assertEquals(true, normalization.poolEntry().get("empty"));
@@ -130,7 +130,7 @@ public class VillageImporterListPoolElementRuntimeContractTest {
StructurePoolElement orphan = StructurePoolElement
.legacy("test:orphan_house")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(orphan);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(StructureReaderFixture.element(orphan));
VillageImporter.PoolMemberNormalization normalization = VillageImporter.normalizePoolMember(
"test:orphan_pool",
"qa/pool/test/orphan_pool",
@@ -143,7 +143,7 @@ public class VillageImporterListPoolElementRuntimeContractTest {
37,
List.of());
assertSame(orphan, resolution.physicalElement());
assertEquals(orphan.getClass().getSimpleName(), resolution.physicalElement().typeName());
assertEquals("test:orphan_house", resolution.templateLocation());
assertEquals(VillageImporter.PoolMemberDisposition.OMITTED, normalization.disposition());
assertTrue(normalization.poolEntry().isEmpty());
@@ -160,7 +160,7 @@ public class VillageImporterListPoolElementRuntimeContractTest {
StructurePoolElement start = StructurePoolElement
.legacy("test:air_start")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(start);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(StructureReaderFixture.element(start));
VillageImporter.PoolMemberNormalization normalization = VillageImporter.normalizePoolMember(
"test:start",
"qa/pool/test/start",
@@ -173,7 +173,7 @@ public class VillageImporterListPoolElementRuntimeContractTest {
0,
List.of());
assertSame(start, resolution.physicalElement());
assertEquals(start.getClass().getSimpleName(), resolution.physicalElement().typeName());
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, normalization.disposition());
assertEquals("qa/piece/test/air_start", normalization.poolEntry().get("piece"));
assertTrue(normalization.losses().isEmpty());
@@ -184,7 +184,7 @@ public class VillageImporterListPoolElementRuntimeContractTest {
StructurePoolElement inert = StructurePoolElement
.legacy("test:mixed_air_inert")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(inert);
VillageImporter.PoolElementResolution resolution = VillageImporter.resolvePoolElement(StructureReaderFixture.element(inert));
VillageImporter.PoolMemberNormalization normalization = VillageImporter.normalizePoolMember(
"test:mixed",
"qa/pool/test/mixed",
@@ -197,7 +197,7 @@ public class VillageImporterListPoolElementRuntimeContractTest {
0,
List.of());
assertSame(inert, resolution.physicalElement());
assertEquals(inert.getClass().getSimpleName(), resolution.physicalElement().typeName());
assertEquals(VillageImporter.PoolMemberDisposition.OMITTED, normalization.disposition());
assertTrue(normalization.poolEntry().isEmpty());
assertEquals("connectorless_all_air_mixed_member_omitted",
@@ -214,8 +214,8 @@ public class VillageImporterListPoolElementRuntimeContractTest {
StructurePoolElement nonAirElement = StructurePoolElement
.legacy("test:mixed_non_air_primary")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution allAirResolution = VillageImporter.resolvePoolElement(allAirElement);
VillageImporter.PoolElementResolution nonAirResolution = VillageImporter.resolvePoolElement(nonAirElement);
VillageImporter.PoolElementResolution allAirResolution = VillageImporter.resolvePoolElement(StructureReaderFixture.element(allAirElement));
VillageImporter.PoolElementResolution nonAirResolution = VillageImporter.resolvePoolElement(StructureReaderFixture.element(nonAirElement));
VillageImporter.PoolMemberNormalization allAir = VillageImporter.normalizePoolMember(
"test:mixed",
"qa/pool/test/mixed",
@@ -239,8 +239,8 @@ public class VillageImporterListPoolElementRuntimeContractTest {
41,
List.of());
assertSame(allAirElement, allAirResolution.physicalElement());
assertSame(nonAirElement, nonAirResolution.physicalElement());
assertEquals(allAirElement.getClass().getSimpleName(), allAirResolution.physicalElement().typeName());
assertEquals(nonAirElement.getClass().getSimpleName(), nonAirResolution.physicalElement().typeName());
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, allAir.disposition());
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, nonAir.disposition());
assertEquals("qa/piece/test/mixed_all_air_primary", allAir.poolEntry().get("piece"));
@@ -257,8 +257,8 @@ public class VillageImporterListPoolElementRuntimeContractTest {
StructurePoolElement nonAirElement = StructurePoolElement
.legacy("test:non_air_primary")
.apply(StructureTemplatePool.Projection.RIGID);
VillageImporter.PoolElementResolution allAirResolution = VillageImporter.resolvePoolElement(allAirElement);
VillageImporter.PoolElementResolution nonAirResolution = VillageImporter.resolvePoolElement(nonAirElement);
VillageImporter.PoolElementResolution allAirResolution = VillageImporter.resolvePoolElement(StructureReaderFixture.element(allAirElement));
VillageImporter.PoolElementResolution nonAirResolution = VillageImporter.resolvePoolElement(StructureReaderFixture.element(nonAirElement));
VillageImporter.PoolMemberNormalization allAir = VillageImporter.normalizePoolMember(
"test:primary",
"qa/pool/test/primary",
@@ -282,8 +282,8 @@ public class VillageImporterListPoolElementRuntimeContractTest {
29,
List.of());
assertSame(allAirElement, allAirResolution.physicalElement());
assertSame(nonAirElement, nonAirResolution.physicalElement());
assertEquals(allAirElement.getClass().getSimpleName(), allAirResolution.physicalElement().typeName());
assertEquals(nonAirElement.getClass().getSimpleName(), nonAirResolution.physicalElement().typeName());
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, allAir.disposition());
assertEquals(VillageImporter.PoolMemberDisposition.PHYSICAL, nonAir.disposition());
assertEquals("qa/piece/test/all_air_primary", allAir.poolEntry().get("piece"));
@@ -1,8 +1,14 @@
package art.arcane.iris.nativegen;
package art.arcane.iris.structure.nativegen;
import art.arcane.iris.structure.nativegen.IrisStructureVolumePolicy;
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeStructureVolumeSource;
import art.arcane.iris.structure.nativegen.NativeStructureVolumeIndex;
import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.structure.nativegen.NativeStructureVolume;
import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
import art.arcane.volmlib.util.collection.KList;
import net.minecraft.SharedConstants;
@@ -63,7 +69,7 @@ public class NativeStructureVolumeIndexTest {
StructureStart start = swampHut(0, 0, 4, 6);
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
KList<NativeStructureVolume> volumes = NativeStructureVolumeIndex.appendPieces(null, STRUCTURE_KEY, start);
KList<NativeStructureVolume> volumes = NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, start);
assertEquals(1, volumes.size());
NativeStructureVolume volume = volumes.getFirst();
@@ -78,8 +84,8 @@ public class NativeStructureVolumeIndexTest {
@Test
public void invalidStartsContributeNoVolumes() {
assertNull(NativeStructureVolumeIndex.appendPieces(null, STRUCTURE_KEY, StructureStart.INVALID_START));
assertNull(NativeStructureVolumeIndex.appendPieces(null, STRUCTURE_KEY, null));
assertNull(NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, StructureStart.INVALID_START));
assertNull(NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, null));
}
@Test
@@ -174,9 +180,10 @@ public class NativeStructureVolumeIndexTest {
listeners.remove(invocation.getArgument(0));
return null;
}).when(engine).removeGenerationRuntimeRetirementListener(any());
NativeStructureVolumeIndex.Context context = new NativeStructureVolumeIndex.Context(
NativeStructureVolumeSource.Context context = new NativeStructureVolumeSource.Context(
null, null, null, null, () -> null, () -> null, () -> null);
NativeStructureVolumeIndex.install(engine, context);
NativeStructureVolumeIndex.install(engine,
new NativeStructureVolumeSource<>(context, IrisStructureVolumePolicy.INSTANCE)::volumesAt);
try {
assertEquals(1, listeners.size());
IntConsumer original = listeners.iterator().next();
@@ -501,7 +508,7 @@ public class NativeStructureVolumeIndexTest {
if (chunkX != originChunkX || chunkZ != originChunkZ) {
return NativeStructureVolume.NONE;
}
return NativeStructureVolumeIndex.appendPieces(null, STRUCTURE_KEY, swampHut(chunkX, chunkZ, 4, 6));
return NativeStructureVolumeSource.appendPieces(null, STRUCTURE_KEY, swampHut(chunkX, chunkZ, 4, 6));
}
}
@@ -0,0 +1,12 @@
package art.arcane.volmlib.nativelib.common.structure;
import art.arcane.volmlib.nativelib.terrain.NativeStructureReader;
public final class StructureReaderFixture {
private StructureReaderFixture() {
}
public static NativeStructureReader.Element element(Object handle) {
return new ReflectiveStructureReader.ElementView(handle, null, ReflectiveStructureReader::readDefaultConnectors);
}
}
@@ -1,83 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
import java.util.Set;
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.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 final ChunkGenerator delegate;
ForcedStructureChunkGenerator(ChunkGenerator delegate, Holder<Biome> sourceBiome) {
super(new FixedBiomeSource(sourceBiome));
this.delegate = delegate;
}
@Override
protected MapCodec<? extends ChunkGenerator> codec() {
return MapCodec.unit(this);
}
@Override
public void spawnOriginalMobs(WorldGenRegion worldGenRegion) {
delegate.spawnOriginalMobs(worldGenRegion);
}
@Override
public int getGenDepth() {
return delegate.getGenDepth();
}
@Override
public CompletableFuture<ChunkAccess> buildTerrain(ChunkAccess centerChunk, Blender blender, RandomState randomState,
StructureManager structureManager, BiomeManager biomeManager,
WorldGenRegion carverBiomeRegion, Set<Holder<Biome>> possibleBiomes) {
return delegate.buildTerrain(
centerChunk, blender, randomState, structureManager, biomeManager, carverBiomeRegion, possibleBiomes);
}
@Override
public int getSeaLevel() {
return delegate.getSeaLevel();
}
@Override
public int getMinY() {
return delegate.getMinY();
}
@Override
public int getBaseHeight(int x, int z, Heightmap.Types type,
LevelHeightAccessor heightAccessor, RandomState randomState) {
return delegate.getBaseHeight(x, z, type, heightAccessor, randomState);
}
@Override
public NoiseColumn getBaseColumn(int x, int z, LevelHeightAccessor heightAccessor,
RandomState randomState) {
return delegate.getBaseColumn(x, z, heightAccessor, randomState);
}
@Override
public void addDebugScreenInfo(List<String> result, RandomState randomState,
BlockPos feetPos, SamplerContext samplerContext) {
delegate.addDebugScreenInfo(result, randomState, feetPos, samplerContext);
}
}
@@ -1,31 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
/**
* Shared waiter for the racy build caches in this package: the loser of a putIfAbsent race
* must surface the same exception the builder threw, not a CompletionException wrapper.
*/
final class NativeBuildFutures {
private NativeBuildFutures() {
}
static <T> T awaitBuild(CompletableFuture<T> future, String what) {
try {
return future.join();
} catch (CompletionException error) {
Throwable cause = error.getCause();
if (cause instanceof RuntimeException runtime) {
throw runtime;
}
if (cause instanceof Error fatal) {
throw fatal;
}
if (cause == null) {
throw error;
}
throw new IllegalStateException(what + " failed", cause);
}
}
}
@@ -1,78 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.generation.runtime.DimensionStackContext;
import art.arcane.iris.generation.runtime.DimensionStackLayout;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
import art.arcane.iris.structure.object.IrisStaticObjectLayer;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.status.ChunkPyramid;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
public final class NativeGenerationWriteGuard {
private NativeGenerationWriteGuard() {
}
public static boolean allowsDecoration(Engine engine, WorldGenLevel region, ChunkPos center) {
int radius = ChunkPyramid.GENERATION_PYRAMID.getStepTo(ChunkStatus.FEATURES).blockStateWriteRadius();
for (int offsetX = -radius; offsetX <= radius; offsetX++) {
for (int offsetZ = -radius; offsetZ <= radius; offsetZ++) {
int chunkX = Math.addExact(center.x(), offsetX);
int chunkZ = Math.addExact(center.z(), offsetZ);
ChunkAccess target = region.getChunk(chunkX, chunkZ);
if (target.getPersistedStatus().isOrAfter(ChunkStatus.FULL)
&& !engine.getComplex().allowsMantleChunkWrite(chunkX, chunkZ)) {
return false;
}
}
}
return true;
}
public static boolean isHistoricalStructure(Engine engine, long activation) {
if (activation <= 0 || !(engine instanceof IrisEngine irisEngine)) {
return false;
}
GenerationHistoryRuntimeRouter router = irisEngine.getGenerationHistoryRuntimeRouter().orElse(null);
return router != null && activation < router.history().activeActivation().activationId();
}
public static boolean allowsPendingStage(Engine engine, ChunkAccess chunk, ChunkStatus stage) {
return stage.isOrAfter(ChunkStatus.FEATURES)
&& chunk.getPersistedStatus().isOrAfter(ChunkStatus.TERRAIN)
&& !chunk.getPersistedStatus().isOrAfter(stage)
&& !engine.getComplex().allowsMantleChunkWrite(chunk.getPos().x(), chunk.getPos().z());
}
public static Predicate<BlockPos> protectedPositions(
IrisStaticObjectLayer staticObjects,
DimensionStackContext stackContext,
int minimumY
) {
Map<Long, DimensionStackLayout> layouts = new ConcurrentHashMap<>();
return position -> {
if (!staticObjects.isEmpty() && staticObjects.contains(
position.getX(), position.getY() - minimumY, position.getZ())) {
return true;
}
if (stackContext == null) {
return false;
}
long columnKey = ((long) position.getX() << 32)
^ (position.getZ() & 0xFFFFFFFFL);
DimensionStackLayout layout = layouts.computeIfAbsent(
columnKey,
ignored -> stackContext.sample(position.getX(), position.getZ())
);
return layout.isHostFeatureProtectedY(position.getY() - minimumY);
};
}
}
@@ -1,286 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
import art.arcane.iris.structure.nativegen.NativeStructureStartPlan;
import art.arcane.iris.structure.jigsaw.IrisJigsawConfiguration;
import art.arcane.iris.structure.jigsaw.IrisJigsawHeightmap;
import art.arcane.iris.structure.jigsaw.IrisJigsawLiquidSettings;
import art.arcane.iris.spi.PlatformStructureHooks.JigsawSourceMetadata;
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.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.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);
StructureStart generated = configured.generate(
sourceHolder,
context.levelKey(),
context.registryAccess(),
forcedGenerator,
forcedGenerator.getBiomeSource(),
context.randomState().createClimateSampler(SamplerContext.EMPTY_UNCACHED),
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 = NativeStructureVerticalPlacer.relocateToMinY(
generated, source, plan.baseY(), context.heightAccessor());
} else {
NativeStructureVerticalPlacer.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.wrapForPublication(
positioned,
source,
references,
plan.placement().resolvedTerrain(),
plan.source().getStructure()
);
}
public static JigsawSourceMetadata sourceMetadata(RegistryAccess registryAccess,
StructureTemplateManager templateManager,
JigsawStructure source) {
CompoundTag sourceTag = encodeJigsaw(registryAccess, source);
return new JigsawSourceMetadata(
sourceDistance(sourceTag, "horizontal"),
horizontalReferenceExpansion(source),
NativeStructureTemplatePoolBounds.sourceHorizontalSpan(
registryAccess, templateManager, source));
}
public static int templatePoolHorizontalSpan(RegistryAccess registryAccess,
StructureTemplateManager templateManager,
String templatePoolKey) {
return NativeStructureTemplatePoolBounds.horizontalSpan(
registryAccess, templateManager, templatePoolKey);
}
public static int jigsawStartPoolHorizontalSpan(RegistryAccess registryAccess,
StructureTemplateManager templateManager,
JigsawStructure source,
String templatePoolKey) {
return NativeStructureTemplatePoolBounds.horizontalSpan(
registryAccess, templateManager, source, templatePoolKey);
}
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);
CompoundTag structureTag = encodeJigsaw(registryOps, sourceJigsaw);
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 CompoundTag encodeJigsaw(RegistryAccess registryAccess,
JigsawStructure source) {
return encodeJigsaw(RegistryOps.create(NbtOps.INSTANCE, registryAccess), source);
}
private static CompoundTag encodeJigsaw(RegistryOps<Tag> registryOps,
JigsawStructure source) {
Tag encoded = Structure.DIRECT_CODEC.encodeStart(registryOps, source).getOrThrow();
if (!(encoded instanceof CompoundTag structureTag)) {
throw new IllegalStateException("Native jigsaw codec did not produce a compound");
}
return structureTag;
}
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);
}
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);
}
static int horizontalReferenceExpansion(Structure structure) {
BoundingBox content = new BoundingBox(0, 0, 0, 0, 0, 0);
BoundingBox adjusted = structure.adjustBoundingBox(content);
int expansion = Math.max(content.minX() - adjusted.minX(), adjusted.maxX() - content.maxX());
expansion = Math.max(expansion, content.minZ() - adjusted.minZ());
return Math.max(0, Math.max(expansion, adjusted.maxZ() - content.maxZ()));
}
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
) {
}
}
@@ -1,212 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.structure.placement.IrisStructureStiltSettings;
import art.arcane.volmlib.util.math.RNG;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.SupportType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Objects;
import java.util.function.IntBinaryOperator;
public final class NativeStructureFoundationBuilder {
private static final int FOUNDATION_VERTICAL_TOLERANCE = 1;
private NativeStructureFoundationBuilder() {
}
private static List<FoundationColumn> foundationEnvelope(BoundingBox area, StructureStart start) {
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());
int maxZ = Math.min(area.maxZ(), structure.maxZ());
if (minX > maxX || minZ > maxZ) {
return List.of();
}
List<StructurePiece> pieces = start.getPieces();
List<FoundationColumn> columns = new ArrayList<>((maxX - minX + 1) * (maxZ - minZ + 1));
BitSet envelope = new BitSet(area.getYSpan());
for (int z = minZ; z <= maxZ; z++) {
for (int x = minX; x <= maxX; x++) {
envelope.clear();
markFoundationEnvelope(envelope, pieces, area, x, z);
int cellCount = envelope.cardinality();
if (cellCount == 0) {
continue;
}
int[] ys = new int[cellCount];
int cell = 0;
for (int bit = envelope.nextSetBit(0); bit >= 0; bit = envelope.nextSetBit(bit + 1)) {
ys[cell++] = area.minY() + bit;
}
columns.add(new FoundationColumn(x, z, ys));
}
}
return List.copyOf(columns);
}
private static void markFoundationEnvelope(BitSet envelope, List<StructurePiece> pieces, BoundingBox area,
int x, int z) {
for (StructurePiece piece : pieces) {
BoundingBox bounds = piece.getBoundingBox();
if (x < bounds.minX() || x > bounds.maxX() || z < bounds.minZ() || z > bounds.maxZ()) {
continue;
}
int groundY = bounds.minY();
if (piece instanceof PoolElementStructurePiece poolPiece) {
groundY += poolPiece.getGroundLevelDelta();
if (groundY < bounds.minY()) {
continue;
}
}
int minY = Math.max(area.minY(), bounds.minY());
int maxY = Math.min(area.maxY(), Math.min(bounds.maxY(), groundY + FOUNDATION_VERTICAL_TOLERANCE));
if (minY <= maxY) {
envelope.set(minY - area.minY(), maxY - area.minY() + 1);
}
}
}
static void placeStilts(WorldGenLevel world, BoundingBox area, String structureId,
StructureStart start, IrisStructureStiltSettings settings,
NativeStructurePostProcessor.PaletteBlockResolver paletteBlockResolver,
IntBinaryOperator surfaceHeight,
boolean surfaceStructure) {
Objects.requireNonNull(surfaceHeight, "Structure stilts require an Iris terrain height resolver");
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
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;
}
int terrainY = surfaceStructure
? Math.max(area.minY(), Math.min(
area.maxY(), surfaceHeight.applyAsInt(column.x(), column.z())))
: area.minY() - 1;
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 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) {
Objects.requireNonNull(expectedStilt, "Expected stilt state must not be null");
Objects.requireNonNull(surfaceHeight, "Structure stilt audit requires an Iris terrain height resolver");
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
int baseColumns = 0;
int stiltBlocks = 0;
int stiltColumns = 0;
int unsupportedColumns = 0;
for (FoundationColumn column : foundationEnvelope(area, start)) {
int foundationY = findFoundationY(world, column, position);
if (foundationY == Integer.MIN_VALUE) {
continue;
}
baseColumns++;
int terrainY = Math.max(area.minY(), Math.min(
area.maxY(), surfaceHeight.applyAsInt(column.x(), column.z())));
boolean grounded = foundationY <= terrainY + 1;
boolean stiltColumn = false;
for (int y = foundationY - 1; y >= area.minY(); y--) {
if (y <= terrainY) {
grounded = true;
break;
}
BlockState state = world.getBlockState(position.set(column.x(), y, column.z()));
if (state.is(expectedStilt.getBlock())) {
stiltBlocks++;
stiltColumn = true;
if (y == area.minY()) {
grounded = true;
}
continue;
}
boolean vegetation = state.is(BlockTags.LOGS) || state.is(BlockTags.LEAVES);
grounded = state.isSolid() && !vegetation;
break;
}
if (stiltColumn) {
stiltColumns++;
}
if (!grounded) {
unsupportedColumns++;
}
}
return new StiltSupportAudit(baseColumns, stiltBlocks, stiltColumns, unsupportedColumns);
}
private static int findFoundationY(WorldGenLevel world, FoundationColumn column,
BlockPos.MutableBlockPos position) {
for (int cell = 0; cell < column.ys().length; cell++) {
int y = column.ys()[cell];
BlockState state = world.getBlockState(position.set(column.x(), y, column.z()));
if (state.isSolid()) {
return y;
}
}
return Integer.MIN_VALUE;
}
private record FoundationColumn(int x, int z, int[] ys) {
}
public record StiltSupportAudit(int baseColumns, int stiltBlocks, int stiltColumns,
int unsupportedColumns) {
}
}
@@ -1,35 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
public final class NativeStructureGenerationException extends IllegalStateException {
private NativeStructureGenerationException(FailureContext context, Throwable cause) {
super(message(context), cause);
}
private NativeStructureGenerationException(FailureContext context) {
super(message(context));
}
public static NativeStructureGenerationException failure(String operation, String structureContext,
int chunkX, int chunkZ, Throwable cause) {
return new NativeStructureGenerationException(
new FailureContext(operation, structureContext, chunkX, chunkZ), cause);
}
public static NativeStructureGenerationException failure(String operation, String structureContext,
int chunkX, int chunkZ) {
return new NativeStructureGenerationException(
new FailureContext(operation, structureContext, chunkX, chunkZ));
}
private static String message(FailureContext context) {
String resolvedContext = context.structureContext() == null || context.structureContext().isBlank()
? "<unregistered structure>"
: context.structureContext();
return "Iris native structure " + context.operation() + " failed for " + resolvedContext
+ " in chunk " + context.chunkX() + "," + context.chunkZ()
+ "; chunk generation was aborted to prevent partial structure state";
}
private record FailureContext(String operation, String structureContext, int chunkX, int chunkZ) {
}
}
@@ -1,121 +0,0 @@
/*
* 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.nativegen.v26_3_R1;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.AbstractSpreadingStructurePlacement;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
public final class NativeStructureGenerationKeys {
private static final Method FREQUENCY_METHOD = resolveFrequencyMethod();
private NativeStructureGenerationKeys() {
}
public static Set<String> reachable(ServerLevel level, Set<String> possibleBiomeKeys) {
ServerLevel activeLevel = Objects.requireNonNull(level, "Native structure reachability requires a level");
Set<String> possibleBiomes = Objects.requireNonNull(
possibleBiomeKeys, "Native structure reachability requires possible biome keys");
if (!activeLevel.getServer().getWorldGenSettings().options().generateStructures()) {
return Collections.emptySet();
}
Set<String> keys = new LinkedHashSet<>();
List<Holder<StructureSet>> structureSets =
activeLevel.getChunkSource().getGeneratorState().possibleStructureSets();
for (Holder<StructureSet> structureSetHolder : structureSets) {
StructureSet structureSet = structureSetHolder.value();
if (!isEnabledPlacement(structureSet.placement())) {
continue;
}
for (StructureSet.StructureSelectionEntry entry : structureSet.structures()) {
if (entry.weight() <= 0 || !hasPossibleBiome(entry.structure().value(), possibleBiomes)) {
continue;
}
Optional<ResourceKey<Structure>> key = entry.structure().unwrapKey();
key.ifPresent(resourceKey -> keys.add(resourceKey.identifier().toString()));
}
}
return Collections.unmodifiableSet(keys);
}
static boolean hasPossibleBiome(Structure structure, Set<String> possibleBiomeKeys) {
for (Holder<Biome> holder : structure.biomes()) {
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
if (key.isPresent() && possibleBiomeKeys.contains(key.get().identifier().toString())) {
return true;
}
}
return false;
}
static boolean isEnabledPlacement(StructurePlacement placement) {
return placementFrequency(placement) > 0.0F;
}
private static float placementFrequency(StructurePlacement placement) {
StructurePlacement activePlacement = Objects.requireNonNull(
placement, "Native structure placement frequency requires a placement");
if (!(activePlacement instanceof AbstractSpreadingStructurePlacement)) {
return 1F;
}
try {
return ((Float) FREQUENCY_METHOD.invoke(activePlacement)).floatValue();
} catch (ReflectiveOperationException error) {
throw new IllegalStateException(
"Unable to read native structure placement frequency from "
+ activePlacement.getClass().getName(), error);
}
}
private static Method resolveFrequencyMethod() {
Method frequencyMethod = null;
for (Method method : AbstractSpreadingStructurePlacement.class.getDeclaredMethods()) {
if (Modifier.isStatic(method.getModifiers())
|| method.getParameterCount() != 0
|| method.getReturnType() != float.class) {
continue;
}
if (frequencyMethod != null) {
throw new IllegalStateException(
"Native structure placement exposes multiple zero-argument float accessors");
}
frequencyMethod = method;
}
if (frequencyMethod == null || !frequencyMethod.trySetAccessible()) {
throw new IllegalStateException(
"Native structure placement frequency accessor is unavailable");
}
return frequencyMethod;
}
}
@@ -1,351 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.structure.placement.IrisStructureLocator;
import art.arcane.iris.structure.nativegen.NativeStructureOwnershipRecord;
import art.arcane.iris.spi.IrisLogging;
import net.minecraft.SharedConstants;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.IntTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.StreamTagVisitor;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.visitors.CollectFields;
import net.minecraft.nbt.visitors.FieldSelector;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.datafix.DataFixTypes;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.storage.SimpleRegionStorage;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
public final class NativeStructureLocatePersistence {
public static final int MAX_SELECTED_CANDIDATE_RETRIES = 64;
private static final int MAX_STORAGE_PROBES = 512;
private static final Method PAPER_LEVEL_TYPE_KEY = paperLevelTypeKey();
private NativeStructureLocatePersistence() {
}
public static Probe probe(ServerLevel level, Structure structure, boolean requireUnreferenced) {
return probe(level, structure, requireUnreferenced, new ProbeBudget(MAX_STORAGE_PROBES));
}
public static ProbeBudget probeBudget() {
return new ProbeBudget(MAX_STORAGE_PROBES);
}
public static Probe probe(ServerLevel level, Structure structure, boolean requireUnreferenced,
ProbeBudget budget) {
return new Probe(level, structure, requireUnreferenced, budget);
}
public static Search search(Engine engine, String structureKey,
int blockX, int blockZ, int radius, Probe probe) {
return new Search(engine, structureKey, blockX, blockZ, radius, probe);
}
public static final class Probe {
private final ServerLevel level;
private final Structure structure;
private final String structureKey;
private final boolean requireUnreferenced;
private final ProbeBudget budget;
private final Map<Long, Boolean> storedDecisions = new HashMap<>();
Probe(ServerLevel level, Structure structure, boolean requireUnreferenced,
ProbeBudget budget) {
this.level = level;
this.structure = structure;
Registry<Structure> registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
Identifier identifier = registry.getKey(structure);
if (identifier == null) {
throw new IllegalStateException("Cannot probe an unregistered native structure");
}
this.structureKey = identifier.toString();
this.requireUnreferenced = requireUnreferenced;
this.budget = budget;
}
public boolean accepts(int chunkX, int chunkZ) {
ChunkAccess loaded = level.getChunkSource().getChunk(
chunkX, chunkZ, ChunkStatus.STRUCTURE_STARTS, false);
if (loaded != null) {
StructureStart start = loaded.getStartForStructure(structure);
return start != null && start.isValid()
&& (!requireUnreferenced || start.canBeReferenced());
}
ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
Boolean storedDecision = storedDecisions.get(chunkPos.pack());
if (storedDecision != null) {
return storedDecision;
}
boolean accepted = budget.acceptsStored(
level, chunkPos, structureKey, requireUnreferenced);
storedDecisions.put(chunkPos.pack(), accepted);
return accepted;
}
public StructureStart verifySelected(int chunkX, int chunkZ) {
ChunkAccess chunk = level.getChunk(chunkX, chunkZ, ChunkStatus.STRUCTURE_STARTS);
StructureStart start = chunk.getStartForStructure(structure);
if (start == null || !start.isValid()) {
return null;
}
if (requireUnreferenced) {
if (!start.canBeReferenced()) {
return null;
}
}
return start;
}
private void reference(StructureStart start) {
if (requireUnreferenced) {
level.structureManager().addReference(start);
}
}
}
public static final class ProbeBudget {
private final int maximum;
private final Map<Long, StoredChunkState> storedChunks = new HashMap<>();
private final ChunkStorageScanner scanner;
private final StoredChunkDatafixer datafixer;
private int used;
private boolean scanFailureReported;
private ProbeBudget(int maximum) {
this(maximum, null, ProbeBudget::datafix);
}
ProbeBudget(int maximum, ChunkStorageScanner scanner) {
this(maximum, scanner, ProbeBudget::datafix);
}
ProbeBudget(int maximum, ChunkStorageScanner scanner,
StoredChunkDatafixer datafixer) {
this.maximum = maximum;
this.scanner = scanner;
this.datafixer = Objects.requireNonNull(
datafixer, "Stored chunk datafixer must not be null");
}
private void claim() {
if (used >= maximum) {
throw new IrisStructureLocator.CandidateSearchLimitException();
}
used++;
}
private StoredChunkState storedState(ServerLevel level, ChunkPos chunkPos) {
StoredChunkState cached = storedChunks.get(chunkPos.pack());
if (cached != null) {
return cached;
}
claim();
CollectFields fields = new CollectFields(
new FieldSelector(IntTag.TYPE, "DataVersion"),
new FieldSelector("Level", "Structures", CompoundTag.TYPE, "Starts"),
new FieldSelector("structures", CompoundTag.TYPE, "starts"));
ChunkStorageScanner activeScanner = scanner == null
? level.getChunkSource().chunkMap.chunkScanner()::scanChunk : scanner;
try {
activeScanner.scan(chunkPos, fields).join();
} catch (RuntimeException error) {
if (!scanFailureReported) {
scanFailureReported = true;
IrisLogging.reportError(
"Native structure locate could not scan stored chunk state; candidates will be verified by loading their structure starts.",
error);
}
StoredChunkState unresolved = StoredChunkState.missing();
storedChunks.put(chunkPos.pack(), unresolved);
return unresolved;
}
Tag result = fields.getResult();
StoredChunkState resolved;
try {
resolved = result instanceof CompoundTag storedChunk
? StoredChunkState.parse(datafixer.datafix(level, storedChunk))
: StoredChunkState.missing();
} catch (RuntimeException error) {
if (!scanFailureReported) {
scanFailureReported = true;
IrisLogging.reportError(
"Native structure locate could not datafix stored chunk state; candidates will be verified by loading their structure starts.",
error);
}
resolved = StoredChunkState.missing();
}
storedChunks.put(chunkPos.pack(), resolved);
return resolved;
}
private static CompoundTag datafix(ServerLevel level, CompoundTag storedChunk) {
int dataVersion = NbtUtils.getDataVersion(storedChunk);
int currentDataVersion = SharedConstants.getCurrentVersion().dataVersion().version();
if (dataVersion >= currentDataVersion) {
return storedChunk;
}
if (level == null) {
throw new IllegalStateException("Stored chunk datafix requires an active level");
}
CompoundTag context = chunkDataFixContext(level);
SimpleRegionStorage.injectDatafixingContext(storedChunk, context);
return DataFixTypes.CHUNK.updateToCurrentVersion(
level.getServer().getFixerUpper(), storedChunk, dataVersion);
}
private static CompoundTag chunkDataFixContext(ServerLevel level) {
CompoundTag context = new CompoundTag();
String levelIdentifier = level.dimension().identifier().toString();
if (PAPER_LEVEL_TYPE_KEY == null) {
context.putString("dimension", levelIdentifier);
} else {
context.putString("dimension", paperLevelTypeKey(level).identifier().toString());
context.putString("level_identifier", levelIdentifier);
}
level.getChunkSource().getGenerator().getTypeNameForDataFixer()
.ifPresent(identifier -> context.putString("generator", identifier.toString()));
return context;
}
boolean acceptsStored(ServerLevel level, ChunkPos chunkPos,
String structureKey, boolean requireUnreferenced) {
return storedState(level, chunkPos).accepts(structureKey, requireUnreferenced);
}
int used() {
return used;
}
}
public static final class Search {
private final Engine engine;
private final String structureKey;
private final int blockX;
private final int blockZ;
private final int radius;
private final Probe probe;
private final Set<Long> rejectedChunks = new HashSet<>();
private Search(Engine engine, String structureKey, int blockX, int blockZ,
int radius, Probe probe) {
this.engine = engine;
this.structureKey = structureKey;
this.blockX = blockX;
this.blockZ = blockZ;
this.radius = radius;
this.probe = probe;
}
public IrisStructureLocator.LocateResult predict() {
if (rejectedChunks.size() >= MAX_SELECTED_CANDIDATE_RETRIES) {
return new IrisStructureLocator.LocateResult(
IrisStructureLocator.LocateStatus.SEARCH_LIMIT_REACHED, 0, 0, 0);
}
return IrisStructureLocator.locateInPlacementRings(
engine, structureKey, blockX, blockZ, radius,
(chunkX, chunkZ) -> !rejectedChunks.contains(ChunkPos.pack(chunkX, chunkZ))
&& probe.accepts(chunkX, chunkZ));
}
public VerifiedStart verify(IrisStructureLocator.LocateResult result) {
int chunkX = result.originX() >> 4;
int chunkZ = result.originZ() >> 4;
StructureStart start = probe.verifySelected(chunkX, chunkZ);
if (start == null) {
return null;
}
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipRecovery.resolve(
engine, probe.level, structureKey, probe.structure, start);
if (ownership == null) {
return null;
}
return new VerifiedStart(start, ownership);
}
public void reject(IrisStructureLocator.LocateResult result) {
rejectedChunks.add(ChunkPos.pack(result.originX() >> 4, result.originZ() >> 4));
}
public void reference(VerifiedStart verified) {
probe.reference(verified.start());
}
}
private static Method paperLevelTypeKey() {
try {
return ServerLevel.class.getMethod("getTypeKey");
} catch (NoSuchMethodException ignored) {
return null;
}
}
private static ResourceKey<?> paperLevelTypeKey(ServerLevel level) {
try {
Object value = PAPER_LEVEL_TYPE_KEY.invoke(level);
if (value instanceof ResourceKey<?> key) {
return key;
}
throw new IllegalStateException("Paper level type key has an unexpected value");
} catch (IllegalAccessException | InvocationTargetException error) {
throw new IllegalStateException("Cannot read Paper level type key", error);
}
}
public record VerifiedStart(StructureStart start,
NativeStructureOwnershipRecord ownership) {
}
@FunctionalInterface
interface ChunkStorageScanner {
CompletableFuture<Void> scan(ChunkPos chunkPos, StreamTagVisitor visitor);
}
@FunctionalInterface
interface StoredChunkDatafixer {
CompoundTag datafix(ServerLevel level, CompoundTag storedChunk);
}
private record StoredChunkState(boolean stored, boolean startsPresent, CompoundTag starts) {
private static StoredChunkState missing() {
return new StoredChunkState(false, false, new CompoundTag());
}
private static StoredChunkState parse(CompoundTag chunk) {
CompoundTag structures = chunk.getCompoundOrEmpty("structures");
return structures.getCompound("starts")
.map(starts -> new StoredChunkState(true, true, starts))
.orElseGet(() -> new StoredChunkState(true, false, new CompoundTag()));
}
private boolean accepts(String structureKey, boolean requireUnreferenced) {
if (!stored || !startsPresent) {
return true;
}
return starts.getCompound(structureKey).map(start -> {
String id = start.getStringOr("id", "");
return !StructureStart.INVALID_START_ID.equals(id)
&& (!requireUnreferenced || start.getIntOr("references", 0) == 0);
}).orElse(false);
}
}
}
@@ -1,58 +0,0 @@
/*
* 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.nativegen.v26_3_R1;
import com.mojang.datafixers.util.Pair;
import net.minecraft.core.BlockPos;
public final class NativeStructureLocateResults {
private NativeStructureLocateResults() {
}
public static <T> Pair<BlockPos, T> nearest(BlockPos origin, Pair<BlockPos, T> replacement,
Pair<BlockPos, T> nativeResult) {
if (replacement == null) {
return nativeResult;
}
if (nativeResult == null) {
return replacement;
}
return horizontalDistanceSquared(origin, nativeResult.getFirst())
<= horizontalDistanceSquared(origin, replacement.getFirst()) ? nativeResult : replacement;
}
public static <T> Pair<BlockPos, T> selectAndReference(
BlockPos origin,
Pair<BlockPos, T> replacement, Runnable replacementReference,
Pair<BlockPos, T> nativeResult, Runnable nativeReference) {
Pair<BlockPos, T> selected = nearest(origin, replacement, nativeResult);
if (selected == replacement && replacement != null) {
replacementReference.run();
} else if (selected == nativeResult && nativeResult != null) {
nativeReference.run();
}
return selected;
}
private static long horizontalDistanceSquared(BlockPos origin, BlockPos target) {
long dx = (long) target.getX() - origin.getX();
long dz = (long) target.getZ() - origin.getZ();
return dx * dx + dz * dz;
}
}
@@ -1,239 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.structure.nativegen.NativeStructureOwnershipRecord;
import art.arcane.iris.structure.nativegen.NativeStructurePlacementPlanner;
import art.arcane.iris.structure.nativegen.NativeStructureStartPlan;
import art.arcane.iris.structure.placement.StructurePlacementGrid;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentStructure;
import net.minecraft.world.level.levelgen.structure.pools.EmptyPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.ListPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HexFormat;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
public final class NativeStructureOwnershipFingerprint {
private static final Comparator<PieceIdentity> PIECE_ORDER = Comparator
.comparing(PieceIdentity::type)
.thenComparingInt(PieceIdentity::minX)
.thenComparingInt(PieceIdentity::minY)
.thenComparingInt(PieceIdentity::minZ)
.thenComparingInt(PieceIdentity::maxX)
.thenComparingInt(PieceIdentity::maxY)
.thenComparingInt(PieceIdentity::maxZ)
.thenComparingInt(PieceIdentity::orientation)
.thenComparingInt(PieceIdentity::generationDepth)
.thenComparing(PieceIdentity::detail);
private NativeStructureOwnershipFingerprint() {
}
public static NativeStructureOwnershipRecord capture(String structureKey,
StructureStart start,
NativeStructureStartPlan plan,
BoundingBox referenceEnvelope) {
StructureStart resolvedStart = requireValid(start);
NativeStructureStartPlan resolvedPlan = Objects.requireNonNull(
plan, "Native structure start plan must not be null");
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(resolvedStart);
BoundingBox references = Objects.requireNonNull(
referenceEnvelope, "Native structure reference envelope must not be null");
ChunkPos origin = resolvedStart.getChunkPos();
return NativeStructureOwnershipRecord.create(
structureKey,
origin.x(),
origin.z(),
StructurePlacementGrid.placementIdentity(resolvedPlan.placement()),
resolvedPlan.baseY(),
content.minX(),
content.minY(),
content.minZ(),
content.maxX(),
content.maxY(),
content.maxZ(),
locatorY(resolvedStart),
references.minX() >> 4,
references.maxX() >> 4,
references.minZ() >> 4,
references.maxZ() >> 4,
fingerprint(structureKey, resolvedStart),
NativeStructurePlacementPlanner.decisionFor(resolvedPlan)
);
}
public static boolean matches(NativeStructureOwnershipRecord record, StructureStart start) {
if (record == null || start == null || !start.isValid()) {
return false;
}
ChunkPos origin = start.getChunkPos();
if (origin.x() != record.originChunkX()
|| origin.z() != record.originChunkZ()
|| !fingerprint(record.structureKey(), start).equals(record.contentFingerprint())) {
return false;
}
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(start);
if (content.getXSpan() != (record.contentMaxX() - record.contentMinX()) + 1
|| content.getYSpan() != (record.contentMaxY() - record.contentMinY()) + 1
|| content.getZSpan() != (record.contentMaxZ() - record.contentMinZ()) + 1) {
return false;
}
return start.getStructure() instanceof OceanMonumentStructure
|| content.minX() == record.contentMinX()
&& content.minY() == record.contentMinY()
&& content.minZ() == record.contentMinZ()
&& content.maxX() == record.contentMaxX()
&& content.maxY() == record.contentMaxY()
&& content.maxZ() == record.contentMaxZ();
}
public static String fingerprint(String structureKey, StructureStart start) {
StructureStart resolvedStart = requireValid(start);
MessageDigest digest = sha256();
updateString(digest, Objects.requireNonNull(structureKey,
"Native structure key must not be null").trim().toLowerCase(Locale.ROOT));
updateInt(digest, resolvedStart.getChunkPos().x());
updateInt(digest, resolvedStart.getChunkPos().z());
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(resolvedStart);
List<PieceIdentity> pieces = contentPieces(resolvedStart, content.minY());
updateInt(digest, pieces.size());
for (PieceIdentity piece : pieces) {
updateString(digest, piece.type());
updateInt(digest, piece.minX());
updateInt(digest, piece.minY());
updateInt(digest, piece.minZ());
updateInt(digest, piece.maxX());
updateInt(digest, piece.maxY());
updateInt(digest, piece.maxZ());
updateInt(digest, piece.orientation());
updateInt(digest, piece.generationDepth());
updateString(digest, piece.detail());
}
return HexFormat.of().formatHex(digest.digest());
}
public static int locatorY(StructureStart start) {
StructureStart resolvedStart = requireValid(start);
if (!resolvedStart.getPieces().isEmpty()) {
return resolvedStart.getPieces().getFirst().getLocatorPosition().getY();
}
throw new IllegalStateException("Native structure contains no locatable content pieces");
}
private static List<PieceIdentity> contentPieces(StructureStart start, int contentMinY) {
List<PieceIdentity> pieces = new ArrayList<>();
for (StructurePiece piece : start.getPieces()) {
BoundingBox bounds = piece.getBoundingBox();
Identifier type = BuiltInRegistries.STRUCTURE_PIECE.getKey(piece.getType());
Direction orientation = piece.getOrientation();
String detail = pieceDetail(piece, contentMinY);
pieces.add(new PieceIdentity(
type == null ? "" : type.toString(),
bounds.minX(), bounds.minY() - contentMinY, bounds.minZ(),
bounds.maxX(), bounds.maxY() - contentMinY, bounds.maxZ(),
orientation == null ? -1 : orientation.get2DDataValue(),
piece.getGenDepth(),
detail
));
}
if (pieces.isEmpty()) {
throw new IllegalStateException("Native structure contains no content pieces");
}
pieces.sort(PIECE_ORDER);
return List.copyOf(pieces);
}
private static String pieceDetail(StructurePiece piece, int contentMinY) {
if (!(piece instanceof PoolElementStructurePiece poolPiece)) {
return "";
}
return poolElementIdentity(poolPiece.getElement()) + "|"
+ poolPiece.getPosition().getX() + ","
+ (poolPiece.getPosition().getY() - contentMinY) + ","
+ poolPiece.getPosition().getZ() + "|"
+ poolPiece.getGroundLevelDelta() + "|"
+ poolPiece.getRotation().getSerializedName();
}
private static String poolElementIdentity(StructurePoolElement element) {
Identifier type = BuiltInRegistries.STRUCTURE_POOL_ELEMENT.getKey(element.getType());
StringBuilder identity = new StringBuilder(element.getClass().getName())
.append('|')
.append(type == null ? "" : type)
.append('|')
.append(element.getProjection().getSerializedName())
.append('|')
.append(element.getGroundLevelDelta());
if (element == EmptyPoolElement.INSTANCE) {
return identity.append("|empty").toString();
}
if (element instanceof SinglePoolElement single) {
try {
return identity.append("|template=")
.append(single.getTemplateLocation()).toString();
} catch (RuntimeException inlineTemplate) {
return identity.append("|runtime-template").toString();
}
}
if (element instanceof ListPoolElement list) {
identity.append("|list[");
for (StructurePoolElement child : list.getElements()) {
String childIdentity = poolElementIdentity(child);
identity.append(childIdentity.length()).append(':').append(childIdentity);
}
return identity.append(']').toString();
}
return identity.append("|bounded-custom").toString();
}
private static StructureStart requireValid(StructureStart start) {
StructureStart resolved = Objects.requireNonNull(
start, "Native structure start must not be null");
if (!resolved.isValid()) {
throw new IllegalArgumentException("Native structure start must be valid");
}
return resolved;
}
private static MessageDigest sha256() {
try {
return MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException error) {
throw new IllegalStateException("SHA-256 is unavailable", error);
}
}
private static void updateString(MessageDigest digest, String value) {
byte[] encoded = value.getBytes(StandardCharsets.UTF_8);
updateInt(digest, encoded.length);
digest.update(encoded);
}
private static void updateInt(MessageDigest digest, int value) {
digest.update((byte) (value >>> 24));
digest.update((byte) (value >>> 16));
digest.update((byte) (value >>> 8));
digest.update((byte) value);
}
private record PieceIdentity(String type,
int minX, int minY, int minZ,
int maxX, int maxY, int maxZ,
int orientation, int generationDepth, String detail) {
}
}
@@ -1,195 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.structure.nativegen.NativeStructureGenerationPolicy;
import art.arcane.iris.structure.nativegen.NativeStructureOwnershipRecord;
import art.arcane.iris.structure.nativegen.NativeStructureOwnershipStore;
import art.arcane.iris.structure.nativegen.NativeStructurePlacementPlanner;
import art.arcane.iris.structure.nativegen.NativeStructureStartPlan;
import art.arcane.iris.structure.placement.StructurePlacementGrid;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.iris.structure.nativegen.NativeStructureGenerationStatus;
import art.arcane.iris.structure.nativegen.NativeStructureSuppression;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
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.placement.StructurePlacement;
import java.util.Locale;
import java.util.Objects;
public final class NativeStructureOwnershipRecovery {
private NativeStructureOwnershipRecovery() {
}
public static NativeStructureOwnershipRecord resolve(
Engine engine, ServerLevel level, String structureKey,
Structure structure, StructureStart start) {
Objects.requireNonNull(engine, "Native structure ownership recovery requires an engine");
ServerLevel activeLevel = Objects.requireNonNull(
level, "Native structure ownership recovery requires a level");
Structure activeStructure = Objects.requireNonNull(
structure, "Native structure ownership recovery requires a structure");
if (start == null || !start.isValid() || start.getStructure() != activeStructure) {
return null;
}
ChunkPos origin = start.getChunkPos();
NativeStructureOwnershipRecord persisted = NativeStructureOwnershipStore.findPersisted(
engine, structureKey, origin.x(), origin.z());
if (persisted != null) {
NativeStructureOwnershipRecord refreshed = refreshReferenceEnvelope(
structureKey, activeStructure, start, persisted);
if (refreshed != null) {
if (refreshed != persisted) {
NativeStructureOwnershipStore.record(engine, refreshed);
}
return refreshed;
}
NativeStructureOwnershipStore.discard(
engine, structureKey, origin.x(), origin.z());
}
NativeStructureStartPlan plan = NativeStructurePlacementPlanner.matchingPlan(
engine, structureKey, origin.x(), origin.z());
if (!matchesPlan(structureKey, origin, plan)) {
return null;
}
Registry<Structure> registry = activeLevel.registryAccess().lookupOrThrow(Registries.STRUCTURE);
Holder<Structure> holder = registry.wrapAsHolder(activeStructure);
ChunkGeneratorStructureState state = activeLevel.getChunkSource().getGeneratorState();
if (naturalStartIsAmbiguous(engine, structureKey, activeStructure, holder, state, plan)) {
return null;
}
StructureStart expected = generateExpected(
engine, activeLevel, holder, plan, start.getReferences());
NativeStructureOwnershipRecord recovered = proveCandidate(
structureKey, activeStructure, start, plan, expected, false);
if (recovered == null) {
return null;
}
NativeStructureOwnershipStore.record(engine, recovered);
return recovered;
}
static NativeStructureOwnershipRecord refreshReferenceEnvelope(
String structureKey, Structure structure, StructureStart start,
NativeStructureOwnershipRecord ownership) {
if (structure == null || start == null || !start.isValid()
|| start.getStructure() != structure || ownership == null
|| !ownership.structureKey().equals(normalize(structureKey))
|| !NativeStructureOwnershipFingerprint.matches(ownership, start)) {
return null;
}
IrisStructureTerrain terrain = NativeStructureTerrainIntegrator.resolveNativeTerrain(
start, ownership.restoredDecision().terrain());
if (terrain.resolvedMode() != IrisStructureTerrainMode.VACUUM) {
return ownership;
}
BoundingBox expected = NativeStructureReferenceEnvelope.referenceBounds(
start, structure, terrain, structureKey);
int referenceMinChunkX = expected.minX() >> 4;
int referenceMaxChunkX = expected.maxX() >> 4;
int referenceMinChunkZ = expected.minZ() >> 4;
int referenceMaxChunkZ = expected.maxZ() >> 4;
if (ownership.referenceMinChunkX() == referenceMinChunkX
&& ownership.referenceMaxChunkX() == referenceMaxChunkX
&& ownership.referenceMinChunkZ() == referenceMinChunkZ
&& ownership.referenceMaxChunkZ() == referenceMaxChunkZ) {
return ownership;
}
return ownership.withReferenceEnvelope(
referenceMinChunkX, referenceMaxChunkX,
referenceMinChunkZ, referenceMaxChunkZ);
}
static NativeStructureOwnershipRecord proveCandidate(
String structureKey, Structure structure, StructureStart persisted,
NativeStructureStartPlan plan, StructureStart expected,
boolean naturalStartAmbiguous) {
if (naturalStartAmbiguous || structure == null
|| persisted == null || !persisted.isValid()
|| expected == null || !expected.isValid()
|| persisted.getStructure() != structure
|| expected.getStructure() != structure
|| !matchesPlan(structureKey, persisted.getChunkPos(), plan)
|| !persisted.getChunkPos().equals(expected.getChunkPos())) {
return null;
}
BoundingBox referenceEnvelope = NativeStructureReferenceEnvelope.referenceBounds(
expected, structure, plan.placement().resolvedTerrain(), structureKey);
NativeStructureOwnershipRecord candidate = NativeStructureOwnershipFingerprint.capture(
structureKey, expected, plan, referenceEnvelope);
if (candidate.placementIdentity()
!= StructurePlacementGrid.placementIdentity(plan.placement())) {
return null;
}
return NativeStructureOwnershipFingerprint.matches(candidate, persisted)
? candidate : null;
}
private static boolean matchesPlan(String structureKey, ChunkPos origin,
NativeStructureStartPlan plan) {
if (structureKey == null || structureKey.isBlank() || plan == null) {
return false;
}
return plan.chunkX() == origin.x()
&& plan.chunkZ() == origin.z()
&& normalize(structureKey).equals(normalize(plan.source().getStructure()));
}
private static StructureStart generateExpected(
Engine engine, ServerLevel level, Holder<Structure> holder,
NativeStructureStartPlan plan, int references) {
ChunkGeneratorStructureState state = level.getChunkSource().getGeneratorState();
ChunkGenerator generator = level.getChunkSource().getGenerator();
BiomeSource biomeSource = generator.getBiomeSource();
NativeStructureFactory.GenerationContext generationContext =
new NativeStructureFactory.GenerationContext(
level.registryAccess(),
generator,
biomeSource,
state.randomState(),
level.getStructureTemplateManager(),
state.getLevelSeed(),
level.dimension(),
level,
biome -> true,
generator.getSeaLevel(),
(x, z) -> Engine.hostHeight(engine, x, z, true) + engine.getMinHeight()
);
return NativeStructureFactory.generate(generationContext, holder, plan, references);
}
private static boolean naturalStartIsAmbiguous(
Engine engine, String structureKey, Structure structure,
Holder<Structure> holder, ChunkGeneratorStructureState state,
NativeStructureStartPlan plan) {
if (plan.placement().getNativeSuppression() == NativeStructureSuppression.REPLACE_SOURCE) {
return false;
}
NativeStructureGenerationStatus sourceStatus = NativeStructureGenerationPolicy.resolve(
engine, structureKey,
NativeStructureVegetationClearer.isUndergroundStep(structure.step())).status();
if (sourceStatus == NativeStructureGenerationStatus.REPLACED_BY_IRIS) {
return false;
}
for (StructurePlacement placement : state.getPlacementsForStructure(holder)) {
if (placement.isStructureChunk(state, plan.chunkX(), plan.chunkZ())) {
return true;
}
}
return false;
}
private static String normalize(String structureKey) {
return structureKey == null ? "" : structureKey.trim().toLowerCase(Locale.ROOT);
}
}
@@ -1,66 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.generation.terrain.IrisMaterialPalette;
import art.arcane.iris.structure.nativegen.IrisNativeStructureDecision;
import art.arcane.iris.structure.placement.IrisStructureStiltSettings;
import art.arcane.volmlib.util.math.RNG;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import java.util.List;
import java.util.function.IntBinaryOperator;
public final class NativeStructurePostProcessor {
private NativeStructurePostProcessor() {
}
public static void place(WorldGenLevel world, StructureManager structureManager, ChunkGenerator generator,
WorldgenRandom random, BoundingBox area, ChunkPos chunkPos, String structureId,
StructureStart start, IrisNativeStructureDecision decision,
PaletteBlockResolver paletteBlockResolver,
IntBinaryOperator surfaceHeight) {
IrisStructureStiltSettings stilt = decision.stilt();
NativeStructureVerticalPlacer.ensureMonumentSeaLevelAlignment(start, structureId, decision.yShift(),
generator.getSeaLevel(), area.minY(), area.maxY() + 1);
start.placeInChunk(world, structureManager, generator, random, area, chunkPos);
if (stilt != null) {
NativeStructureFoundationBuilder.placeStilts(world, area, structureId, start, stilt,
paletteBlockResolver, surfaceHeight,
!NativeStructureVegetationClearer.isUndergroundStep(start.getStructure().step()));
}
}
public static void prepareTerrain(WorldGenLevel world, BoundingBox area,
List<NativeStructureTerrainIntegrator.TerrainTarget> targets,
PaletteBlockResolver paletteBlockResolver) {
if (targets == null || targets.isEmpty()) {
return;
}
NativeStructureTerrainIntegrator.SourceTerrainSnapshot sourceTerrain =
NativeStructureTerrainIntegrator.captureSourceTerrain(world, area, targets);
for (NativeStructureTerrainIntegrator.TerrainTarget target : targets) {
NativeStructureTerrainIntegrator.integrateTerrain(world, area, target.structureId(), target.start(),
target.terrain(), paletteBlockResolver, sourceTerrain);
}
for (NativeStructureTerrainIntegrator.TerrainTarget target : targets) {
if (NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
target.start(), target.terrain())) {
NativeStructureTerrainIntegrator.clearLegacyTemplateAir(
world, area, target.start(), () -> world.getLevel().getStructureTemplateManager());
}
}
NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world, area, targets, () -> world.getLevel().getStructureTemplateManager());
}
@FunctionalInterface
public interface PaletteBlockResolver {
BlockState resolve(IrisMaterialPalette palette, RNG rng, int x, int y, int z);
}
}
@@ -1,239 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.iris.spi.IrisLogging;
import net.minecraft.world.level.ChunkPos;
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.pieces.PiecesContainer;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public final class NativeStructureReferenceEnvelope {
private static final int MAX_REFERENCE_DISTANCE_CHUNKS = 8;
private static final Set<String> WARNED_CLIPPED_TERRAIN = ConcurrentHashMap.newKeySet();
private static final Set<String> WARNED_SKIPPED_CONTENT = ConcurrentHashMap.newKeySet();
private NativeStructureReferenceEnvelope() {
}
public static StructureStart wrap(StructureStart generated, Structure source, int references,
IrisStructureTerrain terrain) {
return wrap(generated, source, references, terrain, null);
}
public static StructureStart wrap(StructureStart generated, Structure source, int references,
IrisStructureTerrain terrain, String structureKey) {
referenceBounds(generated, source, terrain, structureKey);
return new StructureStart(
source,
generated.getChunkPos(),
references,
new PiecesContainer(List.copyOf(generated.getPieces()))
);
}
public static StructureStart wrapForPublication(
StructureStart generated, Structure source, int references,
IrisStructureTerrain terrain, String structureKey) {
try {
return wrap(generated, source, references, terrain, structureKey);
} catch (UnrepresentableContentException exception) {
warnSkippedContent(generated, source, structureKey);
return StructureStart.INVALID_START;
}
}
public static BoundingBox referenceBounds(StructureStart start, Structure source,
IrisStructureTerrain terrain) {
return referenceBounds(start, source, terrain, null);
}
public static BoundingBox referenceBounds(StructureStart start, Structure source,
IrisStructureTerrain terrain,
String structureKey) {
IrisStructureTerrainMode mode = terrain == null
? IrisStructureTerrainMode.PRESERVE : terrain.resolvedMode();
boolean usesEnvelope = mode == IrisStructureTerrainMode.BORE
|| mode == IrisStructureTerrainMode.FORCE_CARVE
|| mode == IrisStructureTerrainMode.VACUUM
|| mode == IrisStructureTerrainMode.FLATTEN
|| mode == IrisStructureTerrainMode.ENCASE;
int horizontalPadding = mode == IrisStructureTerrainMode.VACUUM
? NativeStructureSurfaceFitter.surfaceTerrainRadius()
: usesEnvelope ? Math.max(0, terrain.getHorizontalPadding()) : 0;
BoundingBox content = contentBounds(start);
if (!fitsReferenceRange(start.getChunkPos(), content)) {
throw new UnrepresentableContentException("Native structure content at "
+ start.getChunkPos().x() + "," + start.getChunkPos().z()
+ " exceeds Minecraft's " + MAX_REFERENCE_DISTANCE_CHUNKS
+ "-chunk structure reference range");
}
BoundingBox adjustedEnvelope;
boolean arithmeticClipped = false;
try {
BoundingBox envelope = horizontalPadding == 0
? content
: 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)
);
adjustedEnvelope = mode == IrisStructureTerrainMode.SOURCE
? source.adjustBoundingBox(content) : envelope;
adjustedEnvelope = encapsulating(adjustedEnvelope, content);
} catch (ArithmeticException exception) {
adjustedEnvelope = maximumReferenceBounds(start.getChunkPos(), content);
arithmeticClipped = true;
}
BoundingBox referencedEnvelope = clampReferenceRange(start.getChunkPos(), adjustedEnvelope);
if (arithmeticClipped || !sameHorizontalBounds(adjustedEnvelope, referencedEnvelope)) {
warnClippedTerrain(start, source, structureKey);
return referencedEnvelope;
}
return adjustedEnvelope;
}
public static boolean contentFitsReferenceRange(StructureStart start) {
try {
return fitsReferenceRange(start.getChunkPos(), contentBounds(start));
} catch (UnrepresentableContentException exception) {
return false;
}
}
public static BoundingBox contentBounds(StructureStart start) {
return contentBounds(start.getPieces());
}
private static BoundingBox contentBounds(List<StructurePiece> pieces) {
BoundingBox bounds = null;
for (StructurePiece piece : pieces) {
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 BoundingBox clampReferenceRange(ChunkPos startChunk, BoundingBox envelope) {
int minX = minimumReferenceBlock(startChunk.x());
int maxX = maximumReferenceBlock(startChunk.x());
int minZ = minimumReferenceBlock(startChunk.z());
int maxZ = maximumReferenceBlock(startChunk.z());
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 BoundingBox maximumReferenceBounds(ChunkPos startChunk, BoundingBox content) {
return new BoundingBox(
minimumReferenceBlock(startChunk.x()), content.minY(),
minimumReferenceBlock(startChunk.z()),
maximumReferenceBlock(startChunk.x()), content.maxY(),
maximumReferenceBlock(startChunk.z())
);
}
private static boolean fitsReferenceRange(ChunkPos startChunk, BoundingBox content) {
return content.minX() >= minimumReferenceBlock(startChunk.x())
&& content.maxX() <= maximumReferenceBlock(startChunk.x())
&& content.minZ() >= minimumReferenceBlock(startChunk.z())
&& content.maxZ() <= maximumReferenceBlock(startChunk.z());
}
private static int minimumReferenceBlock(int originChunk) {
return checkedReferenceBlock(
((long) originChunk - MAX_REFERENCE_DISTANCE_CHUNKS) << 4, originChunk);
}
private static int maximumReferenceBlock(int originChunk) {
return checkedReferenceBlock(
(((long) originChunk + MAX_REFERENCE_DISTANCE_CHUNKS) << 4) + 15L, originChunk);
}
private static int checkedReferenceBlock(long block, int originChunk) {
if (block < Integer.MIN_VALUE || block > Integer.MAX_VALUE) {
throw new UnrepresentableContentException("Native structure origin chunk " + originChunk
+ " exceeds Minecraft's block coordinate range");
}
return (int) block;
}
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());
}
private static BoundingBox encapsulating(BoundingBox left, BoundingBox right) {
return new BoundingBox(
Math.min(left.minX(), right.minX()),
Math.min(left.minY(), right.minY()),
Math.min(left.minZ(), right.minZ()),
Math.max(left.maxX(), right.maxX()),
Math.max(left.maxY(), right.maxY()),
Math.max(left.maxZ(), right.maxZ())
);
}
private static void warnClippedTerrain(
StructureStart start, Structure source, String structureKey) {
String key = warningKey(source, structureKey);
if (WARNED_CLIPPED_TERRAIN.add(key)) {
IrisLogging.warn("Clipping optional terrain envelope for native structure '"
+ key + "' at " + start.getChunkPos().x() + "," + start.getChunkPos().z()
+ " to Minecraft's " + MAX_REFERENCE_DISTANCE_CHUNKS
+ "-chunk reference range; all generated content remains exactly referenced");
}
}
private static void warnSkippedContent(
StructureStart start, Structure source, String structureKey) {
String key = warningKey(source, structureKey);
if (WARNED_SKIPPED_CONTENT.add(key)) {
IrisLogging.warn("Skipping native structure '" + key + "' at "
+ start.getChunkPos().x() + "," + start.getChunkPos().z()
+ " because its generated content exceeds Minecraft's "
+ MAX_REFERENCE_DISTANCE_CHUNKS
+ "-chunk reference range; no structure start, ownership, references, or blocks were published");
}
}
private static String warningKey(Structure source, String structureKey) {
if (structureKey != null && !structureKey.isBlank()) {
return structureKey.trim().toLowerCase(Locale.ROOT);
}
return source.getClass().getName();
}
public static final class UnrepresentableContentException extends IllegalStateException {
private UnrepresentableContentException(String message) {
super(message);
}
}
}
@@ -1,246 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.structure.nativegen.NativeStructureGenerationPolicy;
import art.arcane.iris.structure.nativegen.NativeStructureOwnershipRecord;
import art.arcane.iris.structure.nativegen.NativeStructureOwnershipStore;
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
import art.arcane.iris.structure.nativegen.IrisNativeStructureDecision;
import art.arcane.iris.spi.IrisLogging;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.core.Registry;
import net.minecraft.core.SectionPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.status.ChunkStatus;
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 java.util.ArrayList;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public final class NativeStructureReferenceRepair {
private static final int REFERENCE_DISTANCE_CHUNKS =
NativeStructureOwnershipRecord.MAX_REFERENCE_DISTANCE_CHUNKS;
private static final Set<String> WARNED_POLICY_INVALIDATIONS = ConcurrentHashMap.newKeySet();
private NativeStructureReferenceRepair() {
}
public static void createReferences(Engine engine, WorldGenLevel level,
StructureManager structureManager, ChunkAccess targetChunk) {
ChunkPos target = targetChunk.getPos();
Registry<Structure> registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
ServerLevel serverLevel = level.getLevel();
List<ScannedStart> starts = new ArrayList<>();
for (int originChunkX = target.x() - REFERENCE_DISTANCE_CHUNKS;
originChunkX <= target.x() + REFERENCE_DISTANCE_CHUNKS; originChunkX++) {
for (int originChunkZ = target.z() - REFERENCE_DISTANCE_CHUNKS;
originChunkZ <= target.z() + REFERENCE_DISTANCE_CHUNKS; originChunkZ++) {
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
openOriginRuntimeScope(engine, originChunkX, originChunkZ)) {
ChunkAccess originChunk = level.getChunk(
originChunkX, originChunkZ, ChunkStatus.STRUCTURE_STARTS);
for (Map.Entry<Structure, StructureStart> entry : originChunk.getAllStarts().entrySet()) {
ScannedStart start = scanStart(
engine, serverLevel, structureManager, targetChunk, registry,
originChunk, entry.getKey(), entry.getValue());
if (start != null && isTargetRelevant(engine, targetChunk, start)) {
starts.add(start);
}
}
}
}
}
for (ScannedStart start : starts) {
boolean valid;
synchronized (start.originChunk()) {
StructureStart current = start.originChunk().getStartForStructure(start.structure());
valid = current == start.start() && current.isValid();
}
if (!valid) {
continue;
}
structureManager.addReferenceForStructure(
start.structure(),
start.origin().pack(), targetChunk);
}
}
private static GenerationHistoryRuntimeRouter.CoordinateScope openOriginRuntimeScope(
Engine engine,
int chunkX,
int chunkZ
) {
if (!(engine instanceof IrisEngine irisEngine)) {
return null;
}
try {
return irisEngine.openGenerationHistoryCoordinateScope(chunkX << 4, chunkZ << 4);
} catch (IOException failure) {
throw new IllegalStateException("Unable to resolve native structure references for origin "
+ chunkX + "," + chunkZ + " through generation history.", failure);
}
}
private static boolean isTargetRelevant(
Engine engine, ChunkAccess targetChunk, ScannedStart scanned) {
StructureStart start = scanned.start();
Structure structure = scanned.structure();
ChunkPos target = targetChunk.getPos();
if (scanned.ownership() != null) {
return requiresReference(target, scanned.structureKey(), start, scanned.ownership());
}
if (scanned.registered()) {
return requiresNaturalReference(
engine, target, scanned.structureKey(), structure, start);
}
return NativeStructureReferenceEnvelope.contentBounds(start).intersects(
target.getMinBlockX(), target.getMinBlockZ(),
target.getMaxBlockX(), target.getMaxBlockZ());
}
private static ScannedStart scanStart(
Engine engine, ServerLevel level, StructureManager structureManager,
ChunkAccess targetChunk, Registry<Structure> registry,
ChunkAccess originChunk, Structure structure, StructureStart start) {
Identifier identifier = registry.getKey(structure);
String structureKey = identifier == null
? structure.getClass().getName() : identifier.toString();
if (start == null || !start.isValid()) {
return null;
}
if (!NativeStructureReferenceEnvelope.contentFitsReferenceRange(start)) {
if (identifier != null) {
NativeStructureOwnershipStore.discard(
engine, structureKey, start.getChunkPos().x(), start.getChunkPos().z());
}
invalidateStart(structureManager, originChunk, structure, start, targetChunk);
return null;
}
ChunkPos startOrigin = start.getChunkPos();
NativeStructureOwnershipRecord ownership = identifier == null ? null
: NativeStructureOwnershipRecovery.resolve(
engine, level, structureKey, structure, start);
if (ownership != null) {
return new ScannedStart(
originChunk, structure, start, structureKey, ownership, true);
}
if (identifier != null && !naturalPolicyAllows(engine, structureKey, structure)) {
invalidateStart(structureManager, originChunk, structure, start, targetChunk);
if (WARNED_POLICY_INVALIDATIONS.add(structureKey)) {
IrisLogging.warn("Invalidating persisted natural structure start '"
+ structureKey + "' because the current Iris dimension policy disables it");
}
return null;
}
return new ScannedStart(
originChunk, structure, start, structureKey, null, identifier != null);
}
private static boolean naturalPolicyAllows(
Engine engine, String structureKey, Structure structure) {
IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(
engine, structureKey,
NativeStructureVegetationClearer.isUndergroundStep(structure.step()));
return naturalDecisionAllows(decision);
}
static boolean naturalDecisionAllows(IrisNativeStructureDecision decision) {
return Objects.requireNonNull(
decision, "Native structure decision must not be null").generate();
}
private static void invalidateStart(
StructureManager structureManager, ChunkAccess originChunk,
Structure structure, StructureStart start, ChunkAccess targetChunk) {
synchronized (originChunk) {
StructureStart current = originChunk.getStartForStructure(structure);
if (current == start && current.isValid()) {
structureManager.setStartForStructure(
structure,
StructureStart.INVALID_START, originChunk);
}
}
if (targetChunk != null) {
removeTargetReference(targetChunk, structure, start.getChunkPos().pack());
}
}
private static void removeTargetReference(
ChunkAccess targetChunk, Structure structure, long origin) {
LongSet current = targetChunk.getAllReferences().get(structure);
if (current == null || !current.contains(origin)) {
return;
}
Map<Structure, LongSet> updated = new HashMap<>(targetChunk.getAllReferences());
LongSet retained = new LongOpenHashSet(current);
retained.remove(origin);
if (retained.isEmpty()) {
updated.remove(structure);
} else {
updated.put(structure, retained);
}
targetChunk.setAllReferences(updated);
}
static boolean requiresNaturalReference(Engine engine, ChunkPos target, String structureKey,
Structure structure, StructureStart start) {
if (target == null || start == null || !start.isValid()) {
return false;
}
IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(
engine, structureKey,
NativeStructureVegetationClearer.isUndergroundStep(structure.step()));
if (!decision.generate()) {
return false;
}
BoundingBox referenceBounds = NativeStructureReferenceEnvelope.referenceBounds(
start, structure,
NativeStructureTerrainIntegrator.resolveNativeTerrain(start, decision.terrain()),
structureKey);
return referenceBounds.intersects(
target.getMinBlockX(), target.getMinBlockZ(),
target.getMaxBlockX(), target.getMaxBlockZ());
}
static boolean requiresReference(ChunkPos target, String structureKey,
StructureStart start,
NativeStructureOwnershipRecord ownership) {
if (target == null || start == null || !start.isValid()
|| ownership == null || !ownership.structureKey().equals(structureKey)
|| !ownership.covers(target.x(), target.z())) {
return false;
}
return NativeStructureOwnershipFingerprint.matches(ownership, start);
}
private record ScannedStart(
ChunkAccess originChunk,
Structure structure,
StructureStart start,
String structureKey,
NativeStructureOwnershipRecord ownership,
boolean registered
) {
private ChunkPos origin() {
return ownership == null
? start.getChunkPos()
: new ChunkPos(ownership.originChunkX(), ownership.originChunkZ());
}
}
}
@@ -1,386 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import com.mojang.datafixers.util.Either;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.levelgen.structure.ScatteredFeaturePiece;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentPieces;
import net.minecraft.world.level.levelgen.structure.structures.RuinedPortalPiece;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
final class NativeStructureReflection {
private NativeStructureReflection() {
}
static Field resolveScatteredHeightPositionField() {
Field resolved = null;
for (Field field : ScatteredFeaturePiece.class.getDeclaredFields()) {
int modifiers = field.getModifiers();
if (Modifier.isStatic(modifiers) || field.getType() != int.class
|| !Modifier.isProtected(modifiers) || Modifier.isFinal(modifiers)) {
continue;
}
if (resolved != null) {
throw new IllegalStateException("ScatteredFeaturePiece has multiple mutable protected int fields");
}
resolved = field;
}
if (resolved == null) {
throw new IllegalStateException("ScatteredFeaturePiece height-position field is missing");
}
if (!resolved.trySetAccessible()) {
throw new IllegalStateException("ScatteredFeaturePiece height-position field is inaccessible");
}
return resolved;
}
static RuinedPortalPiece.VerticalPlacement ruinedPortalVerticalPlacement(RuinedPortalPiece piece) {
try {
return (RuinedPortalPiece.VerticalPlacement) RuinedPortalPlacementAccess.FIELD.get(piece);
} catch (IllegalAccessException error) {
throw new IllegalStateException("Cannot read native ruined portal vertical placement", error);
}
}
private static Field resolveRuinedPortalPlacementField() {
Field resolved = null;
for (Field field : RuinedPortalPiece.class.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())
|| field.getType() != RuinedPortalPiece.VerticalPlacement.class) {
continue;
}
if (resolved != null) {
throw new IllegalStateException("RuinedPortalPiece has multiple vertical-placement fields");
}
resolved = field;
}
if (resolved == null || !resolved.trySetAccessible()) {
throw new IllegalStateException("RuinedPortalPiece vertical-placement field is inaccessible");
}
return resolved;
}
private static Field resolveMonumentChildPiecesField() {
Field resolved = null;
for (Field field : OceanMonumentPieces.MonumentBuilding.class.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) || field.getType() != List.class) {
continue;
}
if (resolved != null) {
throw new IllegalStateException("Ocean Monument has multiple instance List fields");
}
resolved = field;
}
if (resolved == null) {
throw new IllegalStateException("Ocean Monument child-pieces List field is missing");
}
if (!Modifier.isPrivate(resolved.getModifiers()) || !Modifier.isFinal(resolved.getModifiers())) {
throw new IllegalStateException("Ocean Monument child-pieces field has an unexpected access contract");
}
if (!resolved.trySetAccessible()) {
throw new IllegalStateException("Ocean Monument child-pieces field is inaccessible");
}
return resolved;
}
static Field resolveSinglePoolTemplateField() {
Field resolved = null;
for (Field field : SinglePoolElement.class.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) || field.getType() != Either.class) {
continue;
}
if (resolved != null) {
throw new IllegalStateException("SinglePoolElement has multiple instance Either fields");
}
resolved = field;
}
if (resolved == null) {
throw new IllegalStateException("SinglePoolElement template Either field is missing");
}
if (!Modifier.isProtected(resolved.getModifiers()) || !Modifier.isFinal(resolved.getModifiers())) {
throw new IllegalStateException("SinglePoolElement template field has an unexpected access contract");
}
if (!resolved.trySetAccessible()) {
throw new IllegalStateException("SinglePoolElement template field is inaccessible");
}
return resolved;
}
static StructureTemplate resolveTemplate(SinglePoolElement element,
Supplier<StructureTemplateManager> templates) {
Object value;
try {
value = SinglePoolTemplateAccess.FIELD.get(element);
} catch (IllegalAccessException error) {
throw new IllegalStateException("Cannot read native structure pool template", error);
}
if (!(value instanceof Either<?, ?> reference)) {
throw new IllegalStateException("Native structure pool template field is not an Either");
}
return resolveTemplateReference(reference, templates);
}
@SuppressWarnings("unchecked")
static List<StructureTemplate.StructureBlockInfo> resolveTemplateBlocks(
StructureTemplate template, StructurePlaceSettings settings,
BlockPos origin) {
Object value;
try {
value = StructureTemplatePalettesAccess.FIELD.get(template);
} catch (IllegalAccessException error) {
throw new IllegalStateException("Cannot read native structure template palettes", error);
}
if (!(value instanceof List<?> paletteValues)) {
throw new IllegalStateException("Native structure template palettes field is not a List");
}
List<StructureTemplate.Palette> palettes =
(List<StructureTemplate.Palette>) paletteValues;
if (palettes.isEmpty()) {
return List.of();
}
return settings.getRandomPalette(palettes, origin).blocks();
}
static StructurePlaceSettings resolvePlacementSettings(
SinglePoolElement element, PoolElementStructurePiece piece,
BoundingBox area) {
LiquidSettings liquidSettings;
try {
liquidSettings = (LiquidSettings) PoolPieceLiquidSettingsAccess.FIELD.get(piece);
} catch (IllegalAccessException error) {
throw new IllegalStateException("Cannot read native structure piece liquid settings", error);
}
try {
return (StructurePlaceSettings) SinglePoolSettingsAccess.METHOD.invoke(
element, piece.getRotation(), area, liquidSettings, false);
} catch (IllegalAccessException | InvocationTargetException error) {
throw new IllegalStateException("Cannot create native structure placement settings", error);
}
}
@SuppressWarnings("unchecked")
static List<StructureTemplate.StructureBlockInfo> processTemplateBlocks(
ServerLevelAccessor world, BlockPos origin, BlockPos referencePos,
StructurePlaceSettings settings,
List<StructureTemplate.StructureBlockInfo> blocks,
StructureTemplate template) {
Method forgeMethod = StructureTemplateProcessingAccess.FORGE_METHOD;
if (forgeMethod == null) {
return StructureTemplate.processBlockInfos(
world, origin, referencePos, settings, blocks);
}
try {
return (List<StructureTemplate.StructureBlockInfo>) forgeMethod.invoke(
null, world, origin, referencePos, settings, blocks, template);
} catch (IllegalAccessException | InvocationTargetException error) {
throw new IllegalStateException("Cannot process native structure template blocks", error);
}
}
static StructureTemplate resolveTemplateReference(Either<?, ?> reference,
Supplier<StructureTemplateManager> templates) {
return reference.map(
location -> resolveNamedTemplate(location, templates),
NativeStructureReflection::requireRuntimeTemplate);
}
private static StructureTemplate resolveNamedTemplate(Object value,
Supplier<StructureTemplateManager> templates) {
if (!(value instanceof Identifier identifier)) {
throw new IllegalStateException("Native structure pool template identifier is "
+ (value == null ? "null" : value.getClass().getName()));
}
return Objects.requireNonNull(templates == null ? null : templates.get(),
"Native structure template manager is unavailable").getOrCreate(identifier);
}
private static StructureTemplate requireRuntimeTemplate(Object value) {
if (!(value instanceof StructureTemplate template)) {
throw new IllegalStateException("Native runtime structure pool template is "
+ (value == null ? "null" : value.getClass().getName()));
}
return template;
}
private static Field resolveStructureTemplatePalettesField() {
Field resolved = null;
for (Field field : StructureTemplate.class.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) || field.getType() != List.class
|| !isPaletteList(field.getGenericType())) {
continue;
}
if (resolved != null) {
throw new IllegalStateException("StructureTemplate has multiple palette List fields");
}
resolved = field;
}
if (resolved == null) {
throw new IllegalStateException("StructureTemplate palette List field is missing");
}
if (!Modifier.isFinal(resolved.getModifiers())) {
throw new IllegalStateException("StructureTemplate palette field has an unexpected access contract");
}
if (!resolved.trySetAccessible()) {
throw new IllegalStateException("StructureTemplate palette field is inaccessible");
}
return resolved;
}
private static boolean isPaletteList(Type type) {
if (!(type instanceof ParameterizedType parameterized)) {
return false;
}
Type[] arguments = parameterized.getActualTypeArguments();
return arguments.length == 1 && arguments[0] == StructureTemplate.Palette.class;
}
private static Method resolveSinglePoolSettingsMethod() {
Method resolved = null;
Class<?>[] parameterTypes = {
Rotation.class, BoundingBox.class, LiquidSettings.class, boolean.class
};
for (Method method : SinglePoolElement.class.getDeclaredMethods()) {
if (Modifier.isStatic(method.getModifiers())
|| method.getReturnType() != StructurePlaceSettings.class
|| !Arrays.equals(method.getParameterTypes(), parameterTypes)) {
continue;
}
if (resolved != null) {
throw new IllegalStateException("SinglePoolElement has multiple placement-settings methods");
}
resolved = method;
}
if (resolved == null) {
throw new IllegalStateException("SinglePoolElement placement-settings method is missing");
}
if (!Modifier.isProtected(resolved.getModifiers())) {
throw new IllegalStateException("SinglePoolElement placement-settings method has an unexpected access contract");
}
if (!resolved.trySetAccessible()) {
throw new IllegalStateException("SinglePoolElement placement-settings method is inaccessible");
}
return resolved;
}
private static Field resolvePoolPieceLiquidSettingsField() {
Field resolved = null;
for (Field field : PoolElementStructurePiece.class.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())
|| field.getType() != LiquidSettings.class) {
continue;
}
if (resolved != null) {
throw new IllegalStateException("PoolElementStructurePiece has multiple liquid-settings fields");
}
resolved = field;
}
if (resolved == null) {
throw new IllegalStateException("PoolElementStructurePiece liquid-settings field is missing");
}
if (!Modifier.isPrivate(resolved.getModifiers())
|| !Modifier.isFinal(resolved.getModifiers())) {
throw new IllegalStateException("PoolElementStructurePiece liquid-settings field has an unexpected access contract");
}
if (!resolved.trySetAccessible()) {
throw new IllegalStateException("PoolElementStructurePiece liquid-settings field is inaccessible");
}
return resolved;
}
private static Method resolveForgeTemplateProcessingMethod() {
Class<?>[] parameterTypes = {
ServerLevelAccessor.class, BlockPos.class, BlockPos.class,
StructurePlaceSettings.class, List.class, StructureTemplate.class
};
Method resolved = null;
for (Method method : StructureTemplate.class.getDeclaredMethods()) {
if (!Modifier.isPublic(method.getModifiers())
|| !Modifier.isStatic(method.getModifiers())
|| method.getReturnType() != List.class
|| !Arrays.equals(method.getParameterTypes(), parameterTypes)) {
continue;
}
if (resolved != null) {
throw new IllegalStateException(
"StructureTemplate has multiple Forge block-processing methods");
}
resolved = method;
}
return resolved;
}
static final class MonumentChildPiecesAccess {
static final Field FIELD = resolveMonumentChildPiecesField();
private MonumentChildPiecesAccess() {
}
}
private static final class RuinedPortalPlacementAccess {
private static final Field FIELD = resolveRuinedPortalPlacementField();
private RuinedPortalPlacementAccess() {
}
}
static final class ScatteredHeightPositionAccess {
static final Field FIELD = resolveScatteredHeightPositionField();
private ScatteredHeightPositionAccess() {
}
}
private static final class SinglePoolTemplateAccess {
private static final Field FIELD = resolveSinglePoolTemplateField();
private SinglePoolTemplateAccess() {
}
}
private static final class StructureTemplatePalettesAccess {
private static final Field FIELD = resolveStructureTemplatePalettesField();
private StructureTemplatePalettesAccess() {
}
}
private static final class SinglePoolSettingsAccess {
private static final Method METHOD = resolveSinglePoolSettingsMethod();
private SinglePoolSettingsAccess() {
}
}
private static final class PoolPieceLiquidSettingsAccess {
private static final Field FIELD = resolvePoolPieceLiquidSettingsField();
private PoolPieceLiquidSettingsAccess() {
}
}
private static final class StructureTemplateProcessingAccess {
private static final Method FORGE_METHOD = resolveForgeTemplateProcessingMethod();
private StructureTemplateProcessingAccess() {
}
}
}
@@ -1,147 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.structure.nativegen.NativeStructureGenerationPolicy;
import art.arcane.iris.structure.nativegen.NativeStructureOwnershipRecord;
import art.arcane.iris.structure.nativegen.NativeStructureOwnershipStore;
import art.arcane.iris.structure.nativegen.NativeStructurePlacementPlanner;
import art.arcane.iris.structure.nativegen.NativeStructureStartPlan;
import art.arcane.iris.structure.nativegen.NativeStructureGenerationStatus;
import art.arcane.iris.structure.nativegen.NativeStructureSuppression;
import art.arcane.iris.spi.IrisLogging;
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.BoundingBox;
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;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public final class NativeStructureStartInjector {
private static final Set<String> WARNED_DUPLICATE_STRUCTURES = ConcurrentHashMap.newKeySet();
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)) {
if (WARNED_DUPLICATE_STRUCTURES.add(identifier.toString())) {
IrisLogging.warn("Ignoring duplicate native structure placements for '"
+ identifier + "'; the first deterministic candidate owns each chunk start");
}
continue;
}
StructureStart existing = context.structureManager().getStartForStructure(
structure, chunk);
boolean replacement = plan.placement().getNativeSuppression()
== NativeStructureSuppression.REPLACE_SOURCE;
if (!replacement && existing != null && existing.isValid()) {
NativeStructureGenerationStatus sourceStatus = NativeStructureGenerationPolicy.resolve(
context.engine(), identifier.toString(),
NativeStructureVegetationClearer.isUndergroundStep(structure.step())).status();
replacement = sourceStatus == NativeStructureGenerationStatus.REPLACED_BY_IRIS;
}
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) -> Engine.hostHeight(context.engine(), x, z, true)
+ context.engine().getMinHeight()
);
StructureStart generated = NativeStructureFactory.generate(
generationContext, holder, plan, references);
if (!isUsableGeneratedStart(generated)) {
if (replacement) {
context.structureManager().setStartForStructure(
structure, StructureStart.INVALID_START, chunk);
}
NativeStructureOwnershipStore.discard(
context.engine(), identifier.toString(),
chunk.getPos().x(), chunk.getPos().z());
continue;
}
BoundingBox referenceBounds = NativeStructureReferenceEnvelope.referenceBounds(
generated, structure, plan.placement().resolvedTerrain(), identifier.toString());
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
identifier.toString(), generated, plan, referenceBounds);
NativeStructureOwnershipStore.record(context.engine(), ownership);
try {
context.structureManager().setStartForStructure(
structure, generated, chunk);
} catch (RuntimeException | Error publicationError) {
try {
NativeStructureOwnershipStore.discard(
context.engine(), identifier.toString(),
chunk.getPos().x(), chunk.getPos().z());
} catch (RuntimeException | Error cleanupError) {
publicationError.addSuppressed(cleanupError);
}
throw publicationError;
}
configuredStarts.put(structure, plan);
}
return Map.copyOf(configuredStarts);
}
static boolean isUsableGeneratedStart(StructureStart start) {
return start != null && start.isValid();
}
public record InjectionContext(
Engine engine,
RegistryAccess registryAccess,
ChunkGeneratorStructureState structureState,
StructureManager structureManager,
ChunkAccess chunk,
StructureTemplateManager templateManager,
ResourceKey<Level> levelKey,
ChunkGenerator generator,
BiomeSource biomeSource
) {
}
}
@@ -1,305 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
final class NativeStructureSurfaceSupportBuilder {
private static final int MAX_BATCH_CELLS = 524_288;
private static final int MAX_BRIDGE_SPAN = 2;
private NativeStructureSurfaceSupportBuilder() {
}
static Set<Long> bridgeRigidPieceSupport(
WorldGenLevel world, BoundingBox area,
List<NativeStructureTerrainIntegrator.TerrainTarget> targets,
Supplier<StructureTemplateManager> templates) {
if (targets == null || targets.isEmpty()) {
return Set.of();
}
Map<Long, BlockState> planned;
try {
BatchCellBudget budget = new BatchCellBudget();
List<SupportRequest> requests = new ArrayList<>();
Set<StructureStart> seenStarts = Collections.newSetFromMap(
new IdentityHashMap<>());
for (NativeStructureTerrainIntegrator.TerrainTarget target : targets) {
if (target == null
|| !NativeStructureSurfaceFitter.requiresSurfaceTerrain(target)
|| target.terrain().resolvedMode() == IrisStructureTerrainMode.FLATTEN
&& !NativeStructureSurfaceFitter.hasExposedFlattenPiece(
target.start(), (x, z) -> world.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z) - 1,
target.terrain().resolvedFlattenRange())
|| !seenStarts.add(target.start())) {
continue;
}
collectTargetRequests(
world, area, target, templates, budget, requests);
}
planned = planWrites(world, area, requests, budget);
} catch (BatchBudgetExceeded ignored) {
return Set.of();
}
if (planned.isEmpty()) {
return Set.of();
}
List<Map.Entry<Long, BlockState>> ordered = new ArrayList<>(planned.entrySet());
ordered.sort(Map.Entry.comparingByKey());
Set<Long> written = new HashSet<>();
for (Map.Entry<Long, BlockState> entry : ordered) {
BlockPos position = BlockPos.of(entry.getKey());
BlockState existing = world.getBlockState(position);
if (!existing.isSolid()
&& !NativeStructureVegetationClearer.isTreeBlock(existing)
&& existing.getFluidState().isEmpty()
&& world.setBlock(position, entry.getValue(), 2)) {
written.add(entry.getKey());
}
}
return Set.copyOf(written);
}
private static void collectTargetRequests(
WorldGenLevel world, BoundingBox area,
NativeStructureTerrainIntegrator.TerrainTarget target,
Supplier<StructureTemplateManager> templates,
BatchCellBudget budget, List<SupportRequest> requests) {
if (!NativeStructureSurfaceFitter.requiresSurfaceTerrain(target)) {
return;
}
List<RigidAnchor> anchors = rigidAnchors(target.start());
if (anchors.size() < 2) {
return;
}
BlockPos referencePos = referencePosition(target.start());
for (RigidAnchor upper : anchors) {
Map<Long, Integer> lowerMeets = candidateLowerMeets(
area, upper, anchors, budget);
if (lowerMeets.isEmpty()) {
continue;
}
NativeStructureTemplateOccupancy.OccupancyResult occupancy =
NativeStructureTemplateOccupancy.resolve(
world, upper.piece(), referencePos, area, templates,
position -> retain(position, area, lowerMeets), budget::consume);
if (!occupancy.resolved()) {
continue;
}
Map<Long, NativeStructureTemplateOccupancy.LowestCell> lowest =
NativeStructureTemplateOccupancy.lowestCells(occupancy.cells());
for (Map.Entry<Long, NativeStructureTemplateOccupancy.LowestCell> entry
: lowest.entrySet()) {
NativeStructureTemplateOccupancy.LowestCell cell = entry.getValue();
Integer lowerMeet = lowerMeets.get(entry.getKey());
if (lowerMeet == null || cell.occupancy().blocker()
|| !NativeStructureTemplateOccupancy.isSolidBase(
cell.occupancy().state())
|| cell.y() - lowerMeet < 2
|| cell.y() - lowerMeet > MAX_BRIDGE_SPAN) {
continue;
}
budget.consume(1);
requests.add(new SupportRequest(
cell.x(), cell.z(), lowerMeet, cell.y()));
}
}
}
private static List<RigidAnchor> rigidAnchors(StructureStart start) {
List<RigidAnchor> anchors = new ArrayList<>();
for (StructurePiece piece : start.getPieces()) {
if (!(piece instanceof PoolElementStructurePiece poolPiece)
|| poolPiece.getElement().getProjection()
!= StructureTemplatePool.Projection.RIGID) {
continue;
}
BoundingBox bounds = poolPiece.getBoundingBox();
int meetY = bounds.minY() + poolPiece.getGroundLevelDelta() - 1;
anchors.add(new RigidAnchor(poolPiece, bounds, meetY));
}
return List.copyOf(anchors);
}
private static Map<Long, Integer> candidateLowerMeets(
BoundingBox area, RigidAnchor upper, List<RigidAnchor> anchors,
BatchCellBudget budget) {
Map<Long, Integer> lowerMeets = new HashMap<>();
for (RigidAnchor lower : anchors) {
budget.consume(1);
int span = upper.meetY() - lower.meetY();
if (lower.piece() == upper.piece() || span < 1
|| span > MAX_BRIDGE_SPAN
|| lower.meetY() < area.minY() || lower.meetY() > area.maxY()) {
continue;
}
int minimumX = Math.max(area.minX(),
Math.max(upper.bounds().minX(), lower.bounds().minX()));
int maximumX = Math.min(area.maxX(),
Math.min(upper.bounds().maxX(), lower.bounds().maxX()));
int minimumZ = Math.max(area.minZ(),
Math.max(upper.bounds().minZ(), lower.bounds().minZ()));
int maximumZ = Math.min(area.maxZ(),
Math.min(upper.bounds().maxZ(), lower.bounds().maxZ()));
if (minimumX > maximumX || minimumZ > maximumZ) {
continue;
}
for (int x = minimumX; x <= maximumX; x++) {
for (int z = minimumZ; z <= maximumZ; z++) {
budget.consume(1);
lowerMeets.merge(columnKey(x, z), lower.meetY(), Math::max);
}
}
}
return lowerMeets;
}
private static boolean retain(
BlockPos position, BoundingBox area, Map<Long, Integer> lowerMeets) {
if (!area.isInside(position)) {
return false;
}
Integer lowerMeet = lowerMeets.get(columnKey(position.getX(), position.getZ()));
return lowerMeet != null && position.getY() <= lowerMeet + MAX_BRIDGE_SPAN;
}
private static Map<Long, BlockState> planWrites(
WorldGenLevel world, BoundingBox area,
List<SupportRequest> requests, BatchCellBudget budget) {
List<SupportRequest> eligible = new ArrayList<>();
Set<MaterialKey> materialKeys = new HashSet<>();
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
for (SupportRequest request : requests) {
BlockState lowerTerrain = world.getBlockState(position.set(
request.x(), request.lowerMeetY(), request.z()));
if (!isTerrainSupport(lowerTerrain)
|| !gapIsClear(world, area, position, request, budget)) {
continue;
}
eligible.add(request);
materialKeys.add(new MaterialKey(
request.x(), request.z(), request.lowerMeetY()));
}
Map<MaterialKey, BlockState> materials = new HashMap<>();
for (MaterialKey key : materialKeys) {
materials.put(key, resolveSupportState(
world, position, key.x(), key.z(), key.lowerMeetY(), budget));
}
Map<Long, BlockState> planned = new HashMap<>();
Set<Long> conflicts = new HashSet<>();
eligible.sort(Comparator.comparingInt(SupportRequest::x)
.thenComparingInt(SupportRequest::z)
.thenComparingInt(SupportRequest::lowerMeetY)
.thenComparingInt(SupportRequest::baseY));
for (SupportRequest request : eligible) {
MaterialKey materialKey = new MaterialKey(
request.x(), request.z(), request.lowerMeetY());
BlockState material = materials.get(materialKey);
for (int y = request.lowerMeetY() + 1; y < request.baseY(); y++) {
budget.consume(1);
long positionKey = BlockPos.asLong(request.x(), y, request.z());
if (conflicts.contains(positionKey)) {
continue;
}
BlockState existing = planned.putIfAbsent(positionKey, material);
if (existing != null && !existing.equals(material)) {
planned.remove(positionKey);
conflicts.add(positionKey);
}
}
}
return planned;
}
private static boolean gapIsClear(
WorldGenLevel world, BoundingBox area, BlockPos.MutableBlockPos position,
SupportRequest request, BatchCellBudget budget) {
for (int y = request.lowerMeetY() + 1; y < request.baseY(); y++) {
budget.consume(1);
if (!area.isInside(position.set(request.x(), y, request.z()))) {
return false;
}
BlockState state = world.getBlockState(position);
if (state.isSolid() || NativeStructureVegetationClearer.isTreeBlock(state)
|| !state.getFluidState().isEmpty()) {
return false;
}
}
return true;
}
private static BlockState resolveSupportState(
WorldGenLevel world, BlockPos.MutableBlockPos position,
int x, int z, int lowerMeetY, BatchCellBudget budget) {
budget.consume(1);
BlockState below = world.getBlockState(position.set(x, lowerMeetY - 1, z));
if (isTerrainSupport(below)) {
return below;
}
return world.getBlockState(position.set(x, lowerMeetY, z));
}
private static boolean isTerrainSupport(BlockState state) {
return state.isSolid()
&& !NativeStructureVegetationClearer.isTreeBlock(state)
&& state.getFluidState().isEmpty();
}
private static BlockPos referencePosition(StructureStart start) {
BoundingBox bounds = start.getPieces().getFirst().getBoundingBox();
BlockPos center = bounds.getCenter();
return new BlockPos(center.getX(), bounds.minY(), center.getZ());
}
private static long columnKey(int x, int z) {
return (long) x << 32 ^ z & 0xffffffffL;
}
private record RigidAnchor(
PoolElementStructurePiece piece, BoundingBox bounds, int meetY) {
}
private record SupportRequest(int x, int z, int lowerMeetY, int baseY) {
}
private record MaterialKey(int x, int z, int lowerMeetY) {
}
private static final class BatchCellBudget {
private int consumed;
private void consume(int amount) {
if (amount < 0 || consumed > MAX_BATCH_CELLS - amount) {
throw BatchBudgetExceeded.INSTANCE;
}
consumed += amount;
}
}
private static final class BatchBudgetExceeded extends RuntimeException {
private static final BatchBudgetExceeded INSTANCE = new BatchBudgetExceeded();
private BatchBudgetExceeded() {
super(null, null, false, false);
}
}
}
@@ -1,165 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import net.minecraft.core.BlockPos;
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.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.pools.EmptyPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.ListPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
final class NativeStructureTemplateOccupancy {
private static final OccupancyCell BLOCKER = new OccupancyCell(null, true);
private NativeStructureTemplateOccupancy() {
}
static OccupancyResult resolve(
WorldGenLevel world, PoolElementStructurePiece piece,
BlockPos referencePosition, BoundingBox processingArea,
Supplier<StructureTemplateManager> templates,
PositionFilter filter, CellBudget budget) {
List<SinglePoolElement> leaves = new ArrayList<>();
if (!flattenSingles(piece.getElement(), leaves)) {
return new OccupancyResult(false, Map.of());
}
List<LeafPlacement> placements = new ArrayList<>(leaves.size());
Map<Long, OccupancyCell> occupancy = new HashMap<>();
for (SinglePoolElement leaf : leaves) {
StructurePlaceSettings settings = NativeStructureReflection.resolvePlacementSettings(
leaf, piece, processingArea);
StructureTemplate template = NativeStructureReflection.resolveTemplate(leaf, templates);
List<StructureTemplate.StructureBlockInfo> rawBlocks =
NativeStructureReflection.resolveTemplateBlocks(
template, settings, piece.getPosition());
budget.consume(rawBlocks.size());
placements.add(new LeafPlacement(settings, rawBlocks, template));
for (StructureTemplate.StructureBlockInfo raw : rawBlocks) {
BlockPos position = piece.getPosition().offset(
StructureTemplate.calculateRelativePosition(settings, raw.pos()));
if (filter.retain(position)) {
occupancy.put(position.asLong(), BLOCKER);
}
}
}
for (LeafPlacement placement : placements) {
List<StructureTemplate.StructureBlockInfo> processed =
NativeStructureReflection.processTemplateBlocks(
world, piece.getPosition(), referencePosition,
placement.settings(), placement.rawBlocks(), placement.template());
budget.consume(processed.size());
for (StructureTemplate.StructureBlockInfo block : processed) {
BlockPos position = block.pos();
if (!filter.retain(position)) {
continue;
}
BlockState state = block.state()
.mirror(placement.settings().getMirror())
.rotate(placement.settings().getRotation());
occupancy.put(position.asLong(), new OccupancyCell(state, false));
}
}
return new OccupancyResult(true, occupancy);
}
static Map<Long, LowestCell> lowestCells(Map<Long, OccupancyCell> occupancy) {
Map<Long, LowestCell> lowest = new HashMap<>();
for (Map.Entry<Long, OccupancyCell> entry : occupancy.entrySet()) {
OccupancyCell cell = entry.getValue();
BlockPos position = BlockPos.of(entry.getKey());
long column = columnKey(position.getX(), position.getZ());
LowestCell current = lowest.get(column);
if (current == null || position.getY() < current.y()) {
lowest.put(column, new LowestCell(
position.getX(), position.getY(), position.getZ(), cell));
}
}
return lowest;
}
static Map<Long, LowestCell> lowestProcessedSolidCells(Map<Long, OccupancyCell> occupancy) {
Map<Long, LowestCell> lowest = new HashMap<>();
for (Map.Entry<Long, OccupancyCell> entry : occupancy.entrySet()) {
OccupancyCell cell = entry.getValue();
if (cell.blocker() || !isSolidBase(cell.state())) {
continue;
}
BlockPos position = BlockPos.of(entry.getKey());
long column = columnKey(position.getX(), position.getZ());
LowestCell current = lowest.get(column);
if (current == null || position.getY() < current.y()) {
lowest.put(column, new LowestCell(
position.getX(), position.getY(), position.getZ(), cell));
}
}
return lowest;
}
static boolean isSolidBase(BlockState state) {
return state != null && state.isSolid()
&& !state.is(Blocks.STRUCTURE_VOID)
&& !state.is(Blocks.JIGSAW)
&& state.getFluidState().isEmpty();
}
static long columnKey(int x, int z) {
return (long) x << 32 ^ z & 0xffffffffL;
}
private static boolean flattenSingles(
StructurePoolElement element, List<SinglePoolElement> leaves) {
if (element instanceof ListPoolElement listElement) {
for (StructurePoolElement child : listElement.getElements()) {
if (!flattenSingles(child, leaves)) {
return false;
}
}
return true;
}
if (element == EmptyPoolElement.INSTANCE) {
return true;
}
if (element instanceof SinglePoolElement singleElement) {
leaves.add(singleElement);
return true;
}
return false;
}
@FunctionalInterface
interface PositionFilter {
boolean retain(BlockPos position);
}
@FunctionalInterface
interface CellBudget {
void consume(int amount);
}
record OccupancyResult(boolean resolved, Map<Long, OccupancyCell> cells) {
}
record OccupancyCell(BlockState state, boolean blocker) {
}
record LowestCell(int x, int y, int z, OccupancyCell occupancy) {
}
private record LeafPlacement(
StructurePlaceSettings settings,
List<StructureTemplate.StructureBlockInfo> rawBlocks,
StructureTemplate template) {
}
}
@@ -1,228 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import com.mojang.datafixers.util.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.pools.EmptyPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.FeaturePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.ListPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.pools.alias.DirectPoolAlias;
import net.minecraft.world.level.levelgen.structure.pools.alias.PoolAliasBinding;
import net.minecraft.world.level.levelgen.structure.pools.alias.RandomGroupPoolAlias;
import net.minecraft.world.level.levelgen.structure.pools.alias.RandomPoolAlias;
import net.minecraft.world.level.levelgen.structure.structures.JigsawStructure;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
public final class NativeStructureTemplatePoolBounds {
private NativeStructureTemplatePoolBounds() {
}
public static int sourceHorizontalSpan(RegistryAccess registryAccess,
StructureTemplateManager templateManager,
JigsawStructure source) {
Registry<StructureTemplatePool> pools = registryAccess.lookupOrThrow(Registries.TEMPLATE_POOL);
return sourceHorizontalSpan(
templateManager,
source.getStartPool(),
source.getPoolAliases(),
key -> pools.getValue(key.identifier()));
}
public static int horizontalSpan(RegistryAccess registryAccess,
StructureTemplateManager templateManager,
String templatePoolKey) {
Identifier identifier = Identifier.tryParse(templatePoolKey);
if (identifier == null) {
throw new IllegalArgumentException("Invalid registered template pool key: " + templatePoolKey);
}
Registry<StructureTemplatePool> pools = registryAccess.lookupOrThrow(Registries.TEMPLATE_POOL);
StructureTemplatePool pool = pools.getValue(identifier);
if (pool == null) {
throw new IllegalArgumentException("Registered template pool does not exist: " + templatePoolKey);
}
return horizontalSpan(pool, templateManager);
}
public static int horizontalSpan(RegistryAccess registryAccess,
StructureTemplateManager templateManager,
JigsawStructure source,
String templatePoolKey) {
Identifier identifier = Identifier.tryParse(templatePoolKey);
if (identifier == null) {
throw new IllegalArgumentException("Invalid registered template pool key: " + templatePoolKey);
}
Registry<StructureTemplatePool> pools = registryAccess.lookupOrThrow(Registries.TEMPLATE_POOL);
ResourceKey<StructureTemplatePool> startPoolKey = ResourceKey.create(
Registries.TEMPLATE_POOL, identifier);
StructureTemplatePool startPool = pools.getValue(identifier);
if (startPool == null) {
throw new IllegalArgumentException("Registered template pool does not exist: " + templatePoolKey);
}
return sourceHorizontalSpan(
templateManager,
startPool,
startPoolKey,
source.getPoolAliases(),
key -> pools.getValue(key.identifier()));
}
static int sourceHorizontalSpan(StructureTemplateManager templateManager,
Holder<StructureTemplatePool> startPool,
List<PoolAliasBinding> aliases,
Function<ResourceKey<StructureTemplatePool>, StructureTemplatePool> poolLookup) {
return sourceHorizontalSpan(templateManager, startPool.value(),
startPool.unwrapKey().orElse(null), aliases, poolLookup);
}
static int sourceHorizontalSpan(StructureTemplateManager templateManager,
StructureTemplatePool startPool,
ResourceKey<StructureTemplatePool> startPoolKey,
List<PoolAliasBinding> aliases,
Function<ResourceKey<StructureTemplatePool>, StructureTemplatePool> poolLookup) {
int maximumSpan = directHorizontalSpan(startPool, templateManager);
if (startPoolKey == null) {
return maximumSpan;
}
Set<ResourceKey<StructureTemplatePool>> targets = new HashSet<>();
for (PoolAliasBinding binding : aliases) {
collectTargets(binding, startPoolKey, targets);
}
for (ResourceKey<StructureTemplatePool> target : targets) {
StructureTemplatePool targetPool = poolLookup.apply(target);
if (targetPool == null) {
throw new IllegalStateException("Jigsaw start-pool alias target does not exist: "
+ target.identifier());
}
maximumSpan = Math.max(maximumSpan, directHorizontalSpan(targetPool, templateManager));
}
return maximumSpan;
}
private static void collectTargets(PoolAliasBinding binding,
ResourceKey<StructureTemplatePool> startPoolKey,
Set<ResourceKey<StructureTemplatePool>> targets) {
if (binding instanceof DirectPoolAlias direct) {
if (direct.alias().equals(startPoolKey)) {
targets.add(direct.target());
}
return;
}
if (binding instanceof RandomPoolAlias random) {
if (random.alias().equals(startPoolKey)) {
random.targets().unwrap().forEach(entry -> targets.add(entry.value()));
}
return;
}
if (binding instanceof RandomGroupPoolAlias group) {
group.groups().unwrap().forEach(entry -> entry.value().forEach(
nested -> collectTargets(nested, startPoolKey, targets)));
return;
}
throw new IllegalStateException("Unsupported jigsaw pool alias type: "
+ binding.getClass().getName());
}
static int horizontalSpan(StructureTemplatePool pool,
StructureTemplateManager templateManager) {
Set<StructureTemplatePool> visited = Collections.newSetFromMap(new IdentityHashMap<>());
return horizontalSpan(pool, templateManager, visited);
}
private static int horizontalSpan(StructureTemplatePool pool,
StructureTemplateManager templateManager,
Set<StructureTemplatePool> visited) {
if (!visited.add(pool)) {
return 0;
}
int maximumSpan = directHorizontalSpan(pool, templateManager);
Holder<StructureTemplatePool> fallback = pool.getFallback();
if (fallback == null || !fallback.isBound()) {
throw new IllegalStateException("Template pool has an unresolved fallback");
}
return Math.max(maximumSpan, horizontalSpan(fallback.value(), templateManager, visited));
}
private static int directHorizontalSpan(StructureTemplatePool pool,
StructureTemplateManager templateManager) {
int maximumSpan = 0;
for (Pair<StructurePoolElement, Integer> entry : pool.getTemplates()) {
StructurePoolElement element = entry.getFirst();
validateElement(element, templateManager);
maximumSpan = Math.max(maximumSpan, horizontalSpan(element, templateManager));
}
return maximumSpan;
}
private static void validateElement(StructurePoolElement element,
StructureTemplateManager templateManager) {
if (element == EmptyPoolElement.INSTANCE || element instanceof FeaturePoolElement) {
return;
}
if (element instanceof SinglePoolElement single) {
Identifier template;
try {
template = single.getTemplateLocation();
} catch (RuntimeException inlineTemplate) {
return;
}
if (templateManager.get(template).isEmpty()) {
throw new IllegalStateException("Template pool element does not resolve a structure template: "
+ template);
}
return;
}
if (element instanceof ListPoolElement list) {
for (StructurePoolElement child : list.getElements()) {
validateElement(child, templateManager);
}
return;
}
}
private static int horizontalSpan(StructurePoolElement element,
StructureTemplateManager templateManager) {
if (element == EmptyPoolElement.INSTANCE) {
return 0;
}
long maximumSpan = 0L;
for (Rotation rotation : Rotation.values()) {
BoundingBox box;
try {
box = element.getBoundingBox(templateManager, BlockPos.ZERO, rotation);
} catch (RuntimeException | LinkageError error) {
throw new IllegalStateException("Template pool element "
+ element.getClass().getName()
+ " could not provide bounded geometry for rotation " + rotation, error);
}
if (box == null) {
throw new IllegalStateException("Template pool element "
+ element.getClass().getName()
+ " returned no bounded geometry for rotation " + rotation);
}
long xSpan = (long) box.maxX() - box.minX() + 1L;
long zSpan = (long) box.maxZ() - box.minZ() + 1L;
maximumSpan = Math.max(maximumSpan, Math.max(xSpan, zSpan));
}
if (maximumSpan < 0L || maximumSpan > Integer.MAX_VALUE) {
throw new IllegalStateException("Template pool element has an unbounded horizontal span");
}
return (int) maximumSpan;
}
}
@@ -1,224 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import com.mojang.datafixers.util.Pair;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.SectionPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureCheckResult;
import net.minecraft.world.level.levelgen.structure.StructureStart;
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.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
public final class NativeStructureVanillaLocator {
private NativeStructureVanillaLocator() {
}
public static Candidate predict(ServerLevel level, HolderSet<Structure> holders,
BlockPos origin, int radius, boolean findUnexplored) {
if (SharedConstants.DEBUG_DISABLE_FEATURES
|| !level.getServer().getWorldGenSettings().options().generateStructures()) {
return null;
}
ChunkGeneratorStructureState state = level.getChunkSource().getGeneratorState();
Map<StructurePlacement, Set<Holder<Structure>>> byPlacement = new LinkedHashMap<>();
for (Holder<Structure> holder : holders) {
for (StructurePlacement placement : state.getPlacementsForStructure(holder)) {
byPlacement.computeIfAbsent(placement, ignored -> new LinkedHashSet<>()).add(holder);
}
}
if (byPlacement.isEmpty()) {
return null;
}
StructureManager structureManager = level.structureManager();
Candidate best = null;
double bestDistance = Double.MAX_VALUE;
List<Map.Entry<RandomSpreadStructurePlacement, Set<Holder<Structure>>>> randomPlacements =
new ArrayList<>(byPlacement.size());
for (Map.Entry<StructurePlacement, Set<Holder<Structure>>> entry : byPlacement.entrySet()) {
StructurePlacement placement = entry.getKey();
if (placement instanceof ConcentricRingsStructurePlacement concentric) {
Candidate candidate = predictConcentric(
entry.getValue(), level, structureManager, origin, findUnexplored,
state, concentric);
if (candidate != null) {
double distance = origin.distSqr(candidate.result().getFirst());
if (distance < bestDistance) {
best = candidate;
bestDistance = distance;
}
}
} else if (placement instanceof RandomSpreadStructurePlacement randomSpread) {
randomPlacements.add(Map.entry(randomSpread, entry.getValue()));
}
}
int centerChunkX = SectionPos.blockToSectionCoord(origin.getX());
int centerChunkZ = SectionPos.blockToSectionCoord(origin.getZ());
int searchRadius = Math.max(0, radius);
for (int ring = 0; ring <= searchRadius; ring++) {
boolean foundInRing = false;
for (Map.Entry<RandomSpreadStructurePlacement, Set<Holder<Structure>>> entry
: randomPlacements) {
Candidate candidate = predictRandomSpread(
entry.getValue(), level, structureManager,
centerChunkX, centerChunkZ, ring, findUnexplored,
state.getLevelSeed(), entry.getKey());
if (candidate == null) {
continue;
}
foundInRing = true;
double distance = origin.distSqr(candidate.result().getFirst());
if (distance < bestDistance) {
best = candidate;
bestDistance = distance;
}
}
if (foundInRing) {
return best;
}
}
return best;
}
private static Candidate predictConcentric(Set<Holder<Structure>> holders,
ServerLevel level,
StructureManager structureManager,
BlockPos origin,
boolean findUnexplored,
ChunkGeneratorStructureState state,
ConcentricRingsStructurePlacement placement) {
List<ChunkPos> positions = state.getRingPositionsFor(placement);
if (positions == null) {
throw new IllegalStateException(
"Tried to locate structures for an unavailable concentric placement");
}
Candidate best = null;
double bestDistance = Double.MAX_VALUE;
for (ChunkPos position : positions) {
BlockPos locatePosition = new BlockPos(
SectionPos.sectionToBlockCoord(position.x(), 8),
32,
SectionPos.sectionToBlockCoord(position.z(), 8));
double distance = locatePosition.distSqr(origin);
if (best != null && distance >= bestDistance) {
continue;
}
Candidate candidate = predictAt(
holders, level, structureManager, findUnexplored, placement, position);
if (candidate != null) {
best = candidate;
bestDistance = distance;
}
}
return best;
}
private static Candidate predictRandomSpread(Set<Holder<Structure>> holders,
ServerLevel level,
StructureManager structureManager,
int centerChunkX, int centerChunkZ,
int ring, boolean findUnexplored,
long seed,
RandomSpreadStructurePlacement placement) {
int spacing = placement.spacing();
for (int offsetX = -ring; offsetX <= ring; offsetX++) {
boolean edgeX = offsetX == -ring || offsetX == ring;
for (int offsetZ = -ring; offsetZ <= ring; offsetZ++) {
boolean edgeZ = offsetZ == -ring || offsetZ == ring;
if (!edgeX && !edgeZ) {
continue;
}
int gridChunkX = centerChunkX + spacing * offsetX;
int gridChunkZ = centerChunkZ + spacing * offsetZ;
ChunkPos candidatePosition = placement.getPotentialStructureChunk(
seed, gridChunkX, gridChunkZ);
Candidate candidate = predictAt(
holders, level, structureManager,
findUnexplored, placement, candidatePosition);
if (candidate != null) {
return candidate;
}
}
}
return null;
}
private static Candidate predictAt(Set<Holder<Structure>> holders,
ServerLevel level,
StructureManager structureManager,
boolean findUnexplored,
StructurePlacement placement,
ChunkPos candidatePosition) {
for (Holder<Structure> holder : holders) {
Structure structure = holder.value();
StructureCheckResult result = structureManager.checkStructurePresence(
candidatePosition, structure, placement, findUnexplored);
if (result == StructureCheckResult.START_NOT_PRESENT) {
continue;
}
if (!findUnexplored && result == StructureCheckResult.START_PRESENT) {
return new Candidate(
Pair.of(placement.getLocatePos(candidatePosition), holder), null);
}
ChunkAccess chunk = level.getChunk(
candidatePosition.x(), candidatePosition.z(), ChunkStatus.STRUCTURE_STARTS);
StructureStart start = structureManager.getStartForStructure(
structure, chunk);
if (start == null || !start.isValid()
|| findUnexplored && !start.canBeReferenced()) {
continue;
}
return new Candidate(
Pair.of(placement.getLocatePos(start.getChunkPos()), holder),
findUnexplored ? start : null);
}
return null;
}
public static final class Candidate {
private final Pair<BlockPos, Holder<Structure>> result;
private final StructureStart referenceStart;
private final AtomicBoolean committed;
private Candidate(Pair<BlockPos, Holder<Structure>> result,
StructureStart referenceStart) {
this.result = result;
this.referenceStart = referenceStart;
this.committed = new AtomicBoolean();
}
public Pair<BlockPos, Holder<Structure>> result() {
return result;
}
public boolean reference(StructureManager structureManager) {
if (referenceStart == null || !committed.compareAndSet(false, true)) {
return false;
}
if (!referenceStart.canBeReferenced()) {
return false;
}
structureManager.addReference(referenceStart);
return true;
}
}
}
@@ -1,174 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.tags.BlockTags;
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.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
public final class NativeStructureVegetationClearer {
private NativeStructureVegetationClearer() {
}
public static boolean isUndergroundStep(GenerationStep.Decoration step) {
return step == GenerationStep.Decoration.UNDERGROUND_STRUCTURES
|| step == GenerationStep.Decoration.UNDERGROUND_DECORATION
|| step == GenerationStep.Decoration.STRONGHOLDS;
}
public static void clearIntersectingVegetation(WorldGenLevel world, ChunkAccess chunk, BoundingBox area,
List<StructureStart> starts) {
if (starts == null || starts.isEmpty()) {
return;
}
VegetationSnapshot snapshot = captureVegetation(chunk, area);
if (snapshot.treeBlockCount() == 0) {
return;
}
int columns = area.getXSpan() * area.getZSpan();
int[] clearMinY = new int[columns];
int[] clearMaxY = new int[columns];
Arrays.fill(clearMinY, Integer.MAX_VALUE);
Arrays.fill(clearMaxY, Integer.MIN_VALUE);
for (StructureStart start : starts) {
if (shouldProcessStart(start)) {
markVegetationColumns(area, snapshot, start, clearMinY, clearMaxY);
}
}
clearVegetationColumns(world, area, snapshot, clearMinY, clearMaxY);
}
static boolean shouldProcessStart(StructureStart start) {
return start != null && start.isValid();
}
private static VegetationSnapshot captureVegetation(ChunkAccess chunk, BoundingBox area) {
int width = area.getXSpan();
int depth = area.getZSpan();
BitSet[] columns = new BitSet[width * depth];
int treeBlockCount = 0;
LevelChunkSection[] sections = chunk.getSections();
int chunkMinX = chunk.getPos().getMinBlockX();
int chunkMinZ = chunk.getPos().getMinBlockZ();
for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {
LevelChunkSection section = sections[sectionIndex];
int sectionMinY = chunk.getSectionYFromSectionIndex(sectionIndex) << 4;
int minY = Math.max(area.minY(), sectionMinY);
int maxY = Math.min(area.maxY(), sectionMinY + 15);
if (minY > maxY || section.hasOnlyAir()
|| !section.maybeHas(NativeStructureVegetationClearer::isTreeBlock)) {
continue;
}
for (int y = minY; y <= maxY; y++) {
int localY = y - sectionMinY;
for (int z = area.minZ(); z <= area.maxZ(); z++) {
int localZ = z - chunkMinZ;
for (int x = area.minX(); x <= area.maxX(); x++) {
int localX = x - chunkMinX;
if (!isTreeBlock(section.getBlockState(localX, localY, localZ))) {
continue;
}
int column = (z - area.minZ()) * width + x - area.minX();
BitSet treeBlocks = columns[column];
if (treeBlocks == null) {
treeBlocks = new BitSet(area.getYSpan());
columns[column] = treeBlocks;
}
treeBlocks.set(y - area.minY());
treeBlockCount++;
}
}
}
}
return new VegetationSnapshot(columns, treeBlockCount);
}
private static void markVegetationColumns(BoundingBox area, VegetationSnapshot snapshot,
StructureStart start, int[] clearMinY, int[] clearMaxY) {
int width = area.getXSpan();
int[] pieceTops = new int[clearMinY.length];
int[] pieceBottoms = new int[clearMinY.length];
Arrays.fill(pieceTops, Integer.MIN_VALUE);
Arrays.fill(pieceBottoms, Integer.MAX_VALUE);
for (StructurePiece piece : start.getPieces()) {
BoundingBox bounds = piece.getBoundingBox();
int minX = Math.max(area.minX(), bounds.minX());
int maxX = Math.min(area.maxX(), bounds.maxX());
int minZ = Math.max(area.minZ(), bounds.minZ());
int maxZ = Math.min(area.maxZ(), bounds.maxZ());
if (minX > maxX || minZ > maxZ) {
continue;
}
for (int z = minZ; z <= maxZ; z++) {
for (int x = minX; x <= maxX; x++) {
int column = (z - area.minZ()) * width + x - area.minX();
pieceTops[column] = Math.max(pieceTops[column], bounds.maxY());
pieceBottoms[column] = Math.min(pieceBottoms[column], bounds.minY());
}
}
}
for (int column = 0; column < pieceTops.length; column++) {
if (snapshot.columns()[column] == null || pieceTops[column] == Integer.MIN_VALUE) {
continue;
}
clearMinY[column] = Math.min(clearMinY[column], pieceBottoms[column] - 1);
clearMaxY[column] = Math.max(clearMaxY[column], pieceTops[column]);
}
}
private static void clearVegetationColumns(WorldGenLevel world, BoundingBox area,
VegetationSnapshot snapshot, int[] clearMinY, int[] clearMaxY) {
int width = area.getXSpan();
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
BlockState air = Blocks.AIR.defaultBlockState();
for (int z = area.minZ(); z <= area.maxZ(); z++) {
for (int x = area.minX(); x <= area.maxX(); x++) {
int column = (z - area.minZ()) * width + x - area.minX();
BitSet treeBlocks = snapshot.columns()[column];
if (treeBlocks == null || clearMaxY[column] < clearMinY[column]) {
continue;
}
int firstBit = Math.max(0, clearMinY[column] - area.minY());
int lastBit = Math.min(area.getYSpan() - 1, clearMaxY[column] - area.minY());
if (firstBit > lastBit) {
continue;
}
for (int bit = treeBlocks.nextSetBit(firstBit); bit >= 0 && bit <= lastBit;
bit = treeBlocks.nextSetBit(bit + 1)) {
int y = area.minY() + bit;
position.set(x, y, z);
BlockState state = world.getBlockState(position);
if (isTreeBlock(state)) {
world.setBlock(position, air, 2);
}
}
}
}
}
static boolean isTreeBlock(BlockState state) {
if (state.is(BlockTags.LOGS) || state.is(BlockTags.LEAVES)) {
return true;
}
// Field reads instead of a registry byValue map hit — this predicate runs per block
// probe on the hottest native-structure scans.
String path = state.getBlock().builtInRegistryHolder().key().identifier().getPath();
return path.endsWith("_log") || path.endsWith("_wood")
|| path.endsWith("_stem") || path.endsWith("_hyphae")
|| path.endsWith("_leaves");
}
private record VegetationSnapshot(BitSet[] columns, int treeBlockCount) {
}
}
@@ -1,384 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.structure.placement.StructureVerticalBounds;
import art.arcane.iris.structure.placement.IrisStructureYBand;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.volmlib.util.math.RNG;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.ScatteredFeaturePiece;
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.JigsawJunction;
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.structures.JungleTemplePiece;
import net.minecraft.world.level.levelgen.structure.structures.JungleTempleStructure;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentPieces;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentStructure;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.IntBinaryOperator;
public final class NativeStructureVerticalPlacer {
private static final String DESERT_PYRAMID_ID = "minecraft:desert_pyramid";
private static final String JUNGLE_PYRAMID_ID = "minecraft:jungle_pyramid";
private static final int MAX_BURIAL_COLUMNS = 2_000_000;
private static final int MONUMENT_BASE_BELOW_SEA_LEVEL = 24;
private static final String OCEAN_MONUMENT_ID = "minecraft:monument";
private static final int UNDERGROUND_SURFACE_CLEARANCE = 1;
private NativeStructureVerticalPlacer() {
}
public static int applyVerticalPlacement(StructureStart start, String structureId, int requestedOffset,
int seaLevel, int worldMinY, int worldMaxYExclusive,
boolean underground, boolean preserveSourceY,
IrisStructureYBand yBand,
IntBinaryOperator surfaceHeight) {
if (isOceanMonument(start, structureId)) {
return alignOceanMonumentToSeaLevel(
start, requestedOffset, seaLevel, worldMinY, worldMaxYExclusive);
}
if (isAdjustedScatteredStructure(start, structureId)) {
return alignScatteredStructureToSurface(
start, structureId, requestedOffset, worldMinY, worldMaxYExclusive, 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,
int configuredOffset, int worldMinY,
int worldMaxYExclusive,
IntBinaryOperator surfaceHeight) {
Objects.requireNonNull(surfaceHeight, "Scattered native structure requires a terrain height resolver");
ScatteredFeaturePiece piece = requireAdjustedScatteredPiece(start, structureId);
BoundingBox bounds = start.getBoundingBox();
BoundingBox pieceBounds = piece.getBoundingBox();
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(
bounds.minY(), bounds.maxY(), requestedMove, worldMinY, worldMaxYExclusive);
if (offsetY != 0) {
moveStructureStart(start, bounds, offsetY);
}
setScatteredHeightPosition(piece, Math.max(0, piece.getBoundingBox().minY()));
return offsetY;
}
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 = resolveShiftOffset(start, bounds, requestedOffset, worldMinY,
worldMaxYExclusive, underground, preserveSourceY, yBand, surfaceHeight);
int offsetY = StructureVerticalBounds.clampOffset(
bounds.minY(), bounds.maxY(), resolvedOffset, worldMinY, worldMaxYExclusive);
if (offsetY == 0) {
return 0;
}
moveStructureStart(start, bounds, offsetY);
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);
BoundingBox bounds = start.getBoundingBox();
int targetMinY = Math.addExact(
Math.subtractExact(seaLevel, MONUMENT_BASE_BELOW_SEA_LEVEL), configuredOffset);
int requestedOffset = Math.subtractExact(targetMinY, building.getBoundingBox().minY());
int offsetY = StructureVerticalBounds.clampOffset(
bounds.minY(), bounds.maxY(), requestedOffset, worldMinY, worldMaxYExclusive);
if (offsetY != requestedOffset) {
throw new IllegalStateException("Ocean monument cannot align to sea level " + seaLevel
+ " with configured offset " + configuredOffset + " inside world bounds ["
+ worldMinY + "," + worldMaxYExclusive + ")");
}
if (offsetY == 0) {
return 0;
}
moveStructureStart(start, bounds, offsetY);
return offsetY;
}
static void ensureMonumentSeaLevelAlignment(StructureStart start, String structureId,
int configuredOffset, int seaLevel,
int worldMinY, int worldMaxYExclusive) {
if (!isOceanMonument(start, structureId)) {
return;
}
synchronized (start) {
alignOceanMonumentToSeaLevel(
start, configuredOffset, seaLevel, worldMinY, worldMaxYExclusive);
}
}
private static boolean isOceanMonument(StructureStart start, String structureId) {
return OCEAN_MONUMENT_ID.equals(structureId)
&& start != null
&& start.getStructure() instanceof OceanMonumentStructure;
}
private static boolean isAdjustedScatteredStructure(StructureStart start, String structureId) {
if (start == null) {
return false;
}
Structure source = start.getStructure();
return (DESERT_PYRAMID_ID.equals(structureId) && source instanceof DesertPyramidStructure)
|| (JUNGLE_PYRAMID_ID.equals(structureId) && source instanceof JungleTempleStructure);
}
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) {
throw new IllegalStateException(structureId + " must contain exactly one scattered piece, found "
+ pieces.size());
}
StructurePiece piece = pieces.get(0);
if (DESERT_PYRAMID_ID.equals(structureId) && piece instanceof DesertPyramidPiece desertPyramid) {
return desertPyramid;
}
if (JUNGLE_PYRAMID_ID.equals(structureId) && piece instanceof JungleTemplePiece jungleTemple) {
return jungleTemple;
}
throw new IllegalStateException(structureId + " contains unexpected piece "
+ piece.getClass().getName());
}
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++) {
lowestY = Math.min(lowestY, surfaceHeight.applyAsInt(x, z));
}
}
if (lowestY == Integer.MAX_VALUE) {
throw new IllegalStateException("Scattered native structure has an empty terrain footprint");
}
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) {
try {
NativeStructureReflection.ScatteredHeightPositionAccess.FIELD.setInt(piece, heightPosition);
} catch (IllegalAccessException error) {
throw new IllegalStateException("Cannot lock scattered native structure height", error);
}
}
private static OceanMonumentPieces.MonumentBuilding requireOceanMonumentBuilding(StructureStart start) {
Objects.requireNonNull(start, "Ocean monument start must not be null");
List<StructurePiece> pieces = start.getPieces();
OceanMonumentPieces.MonumentBuilding building = null;
for (StructurePiece piece : pieces) {
if (piece instanceof OceanMonumentPieces.MonumentBuilding monumentBuilding) {
building = monumentBuilding;
}
}
if (pieces.size() != 1 || building == null) {
throw new IllegalStateException("minecraft:monument must contain exactly one MonumentBuilding, found "
+ pieces.size() + " pieces");
}
return building;
}
private static void moveStructureStart(StructureStart start, BoundingBox cachedBounds, int offsetY) {
for (StructurePiece piece : start.getPieces()) {
moveStructurePiece(piece, offsetY);
}
cachedBounds.move(0, offsetY, 0);
}
private static void moveStructurePiece(StructurePiece piece, int offsetY) {
piece.move(0, offsetY, 0);
if (piece instanceof OceanMonumentPieces.MonumentBuilding building) {
for (StructurePiece child : monumentChildPieces(building)) {
child.move(0, offsetY, 0);
}
}
if (piece instanceof PoolElementStructurePiece poolPiece) {
List<JigsawJunction> junctions = poolPiece.getJunctions();
for (int i = 0; i < junctions.size(); i++) {
JigsawJunction junction = junctions.get(i);
junctions.set(i, new JigsawJunction(
junction.getSourceX(),
junction.getSourceGroundY() + offsetY,
junction.getSourceZ(),
junction.getDeltaY(),
junction.getDestProjection()));
}
}
}
static List<StructurePiece> monumentChildPieces(OceanMonumentPieces.MonumentBuilding building) {
Object value;
try {
value = NativeStructureReflection.MonumentChildPiecesAccess.FIELD.get(building);
} catch (IllegalAccessException error) {
throw new IllegalStateException("Cannot read Ocean Monument child pieces", error);
}
if (!(value instanceof List<?> children)) {
throw new IllegalStateException("Ocean Monument child-pieces field is not a list");
}
List<StructurePiece> pieces = new ArrayList<>(children.size());
for (Object child : children) {
if (!(child instanceof StructurePiece monumentPiece)
|| child.getClass().getEnclosingClass() != OceanMonumentPieces.class) {
throw new IllegalStateException("Ocean Monument child-pieces list contains "
+ (child == null ? "null" : child.getClass().getName()));
}
pieces.add(monumentPiece);
}
return List.copyOf(pieces);
}
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");
Objects.requireNonNull(surfaceHeight, "Underground native structure requires a terrain height resolver");
long width = (long) bounds.maxX() - bounds.minX() + 1L;
long depth = (long) bounds.maxZ() - bounds.minZ() + 1L;
if (width <= 0L || depth <= 0L || width > MAX_BURIAL_COLUMNS / depth) {
throw new IllegalStateException("Underground native structure burial footprint is invalid or exceeds "
+ MAX_BURIAL_COLUMNS + " columns");
}
int maximumOffset = requestedOffset;
for (int x = bounds.minX(); x <= bounds.maxX(); x++) {
for (int z = bounds.minZ(); z <= bounds.maxZ(); z++) {
int allowedTopY = surfaceHeight.applyAsInt(x, z) - UNDERGROUND_SURFACE_CLEARANCE;
maximumOffset = Math.min(maximumOffset, allowedTopY - bounds.maxY());
}
}
int clampedOffset = StructureVerticalBounds.clampOffset(
bounds.minY(), bounds.maxY(), maximumOffset, worldMinY, worldMaxYExclusive);
if (clampedOffset > maximumOffset) {
IrisLogging.warn("Native structure burial at " + bounds.minX() + "," + bounds.minZ()
+ " clamped to world floor: wanted " + maximumOffset + ", used " + clampedOffset);
}
return clampedOffset;
}
}
@@ -1,643 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
import art.arcane.iris.generation.runtime.IrisEngine;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.structure.nativegen.NativeStructureGenerationPolicy;
import art.arcane.iris.structure.nativegen.NativeStructureOwnershipRecord;
import art.arcane.iris.structure.nativegen.NativeStructurePlacementPlanner;
import art.arcane.iris.structure.nativegen.NativeStructureStartPlan;
import art.arcane.iris.structure.nativegen.NativeStructureVolume;
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
import art.arcane.iris.structure.nativegen.IrisNativeStructureDecision;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.volmlib.util.collection.KList;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
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.BiomeSource;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
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.StructureSet;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.IntBinaryOperator;
import java.util.function.IntConsumer;
import java.util.function.Supplier;
/**
* Resolves the world-space piece bounds of every native structure that will generate around a query rect.
*
* <p>The answer is derived from the seed, the registries and this pack's structure policy only: candidate origins
* come from the placement grid and from vanilla structure placements, and each candidate is assembled through the
* same {@link NativeStructureFactory} path chunk generation uses. Nothing here reads chunk state, ownership records
* or generation progress, so the same query answers identically no matter which chunks already exist.
*/
public final class NativeStructureVolumeIndex {
private static final int ORIGIN_REACH_CHUNKS = NativeStructureOwnershipRecord.MAX_REFERENCE_DISTANCE_CHUNKS;
private static final int ORIGIN_WINDOW_DIAMETER = ORIGIN_REACH_CHUNKS * 2 + 1;
private static final int ORIGIN_WINDOW_STRIPE_COUNT = Long.SIZE;
private static final int MAX_CACHED_ORIGIN_CHUNKS = 16_384;
private static final int MAX_CACHED_QUERY_CHUNKS = 4_096;
private static final Map<Engine, NativeStructureVolumeIndex> INDEXES =
Collections.synchronizedMap(new WeakHashMap<>());
private static final Set<String> WARNED_RESOLUTION_FAILURES = ConcurrentHashMap.newKeySet();
private final Context context;
private final OriginResolver origins;
private final Map<RuntimeChunkKey, KList<NativeStructureVolume>> originCache = lru(MAX_CACHED_ORIGIN_CHUNKS);
private final Map<RuntimeChunkKey, KList<NativeStructureVolume>> queryCache = lru(MAX_CACHED_QUERY_CHUNKS);
private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> originBuilds =
new ConcurrentHashMap<>();
private final ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> queryBuilds =
new ConcurrentHashMap<>();
private final ReentrantLock[] originWindowStripes = createOriginWindowStripes();
private final IntConsumer retirementListener = this::evictRuntime;
private NativeStructureVolumeIndex(Context context) {
this.context = Objects.requireNonNull(context, "Native structure volume context must not be null");
this.origins = this::buildOriginVolumes;
}
private NativeStructureVolumeIndex(OriginResolver origins) {
this.context = null;
this.origins = Objects.requireNonNull(origins, "Native structure origin resolver must not be null");
}
public static void install(Engine engine, Context context) {
Engine requiredEngine = Objects.requireNonNull(engine, "Native structure volume index requires an engine");
NativeStructureVolumeIndex installed = new NativeStructureVolumeIndex(context);
synchronized (INDEXES) {
NativeStructureVolumeIndex previous = INDEXES.put(requiredEngine, installed);
if (previous != null) {
previous.detachRetirementListener(requiredEngine);
}
installed.attachRetirementListener(requiredEngine);
}
}
public static void uninstall(Engine engine) {
if (engine != null) {
synchronized (INDEXES) {
NativeStructureVolumeIndex removed = INDEXES.remove(engine);
if (removed != null) {
removed.detachRetirementListener(engine);
}
}
}
}
public static void invalidate(Engine engine) {
if (engine == null) {
return;
}
synchronized (INDEXES) {
NativeStructureVolumeIndex current = INDEXES.get(engine);
if (current != null) {
NativeStructureVolumeIndex replacement = current.fresh();
current.detachRetirementListener(engine);
INDEXES.put(engine, replacement);
replacement.attachRetirementListener(engine);
}
}
}
public static KList<NativeStructureVolume> volumes(Engine engine, int minX, int minZ, int maxX, int maxZ) {
NativeStructureVolumeIndex index = engine == null ? null : INDEXES.get(engine);
return index == null ? NativeStructureVolume.NONE : index.resolve(engine, minX, minZ, maxX, maxZ);
}
static NativeStructureVolumeIndex forTesting(OriginResolver origins) {
return new NativeStructureVolumeIndex(origins);
}
static int originReachChunks() {
return ORIGIN_REACH_CHUNKS;
}
private NativeStructureVolumeIndex fresh() {
return context == null
? new NativeStructureVolumeIndex(origins)
: new NativeStructureVolumeIndex(context);
}
KList<NativeStructureVolume> resolve(Engine engine, int minX, int minZ, int maxX, int maxZ) {
int runtimeId = runtimeId(engine);
int fromChunkX = Math.min(minX, maxX) >> 4;
int toChunkX = Math.max(minX, maxX) >> 4;
int fromChunkZ = Math.min(minZ, maxZ) >> 4;
int toChunkZ = Math.max(minZ, maxZ) >> 4;
KList<NativeStructureVolume> matches = null;
for (int chunkX = fromChunkX; chunkX <= toChunkX; chunkX++) {
for (int chunkZ = fromChunkZ; chunkZ <= toChunkZ; chunkZ++) {
for (NativeStructureVolume volume : chunkVolumes(engine, runtimeId, chunkX, chunkZ)) {
if (!volume.intersectsRect(minX, minZ, maxX, maxZ)) {
continue;
}
if (matches == null) {
matches = new KList<>();
}
if (!matches.contains(volume)) {
matches.add(volume);
}
}
}
}
return matches == null ? NativeStructureVolume.NONE : matches;
}
private KList<NativeStructureVolume> chunkVolumes(
Engine engine,
int runtimeId,
int chunkX,
int chunkZ
) {
return cached(queryCache, queryBuilds, new RuntimeChunkKey(runtimeId, chunkKey(chunkX, chunkZ)),
() -> buildChunkVolumes(engine, chunkX, chunkZ));
}
private KList<NativeStructureVolume> buildChunkVolumes(Engine engine, int chunkX, int chunkZ) {
long stripeMask = originWindowStripeMask(chunkX, chunkZ);
lockOriginWindow(stripeMask);
try {
return buildChunkVolumesCoordinated(engine, chunkX, chunkZ);
} finally {
unlockOriginWindow(stripeMask);
}
}
private KList<NativeStructureVolume> buildChunkVolumesCoordinated(Engine engine, int chunkX, int chunkZ) {
int minX = chunkX << 4;
int minZ = chunkZ << 4;
int maxX = minX + 15;
int maxZ = minZ + 15;
Integer pinnedRuntimeId = pinnedRuntimeId(engine);
KList<NativeStructureVolume> volumes = null;
for (int originX = chunkX - ORIGIN_REACH_CHUNKS; originX <= chunkX + ORIGIN_REACH_CHUNKS; originX++) {
for (int originZ = chunkZ - ORIGIN_REACH_CHUNKS; originZ <= chunkZ + ORIGIN_REACH_CHUNKS; originZ++) {
for (NativeStructureVolume volume : scopedOriginVolumes(engine, originX, originZ, pinnedRuntimeId)) {
if (!volume.intersectsRect(minX, minZ, maxX, maxZ)) {
continue;
}
if (volumes == null) {
volumes = new KList<>();
}
volumes.add(volume);
}
}
}
return volumes == null ? NativeStructureVolume.NONE : volumes;
}
private static Integer pinnedRuntimeId(Engine engine) {
if (!(engine instanceof IrisEngine irisEngine) || !irisEngine.hasGenerationRuntimeScope()) {
return null;
}
GenerationHistoryRuntimeRouter router = irisEngine.getGenerationHistoryRuntimeRouter().orElse(null);
if (router == null) {
return null;
}
GenerationHistoryRuntimeRouter.RuntimeOwnership ownership = router.currentRuntimeOwnership().orElse(null);
int runtimeId = runtimeId(engine);
return ownership != null && ownership.binding().runtimeId() == runtimeId ? runtimeId : null;
}
private KList<NativeStructureVolume> scopedOriginVolumes(
Engine engine, int chunkX, int chunkZ, Integer pinnedRuntimeId
) {
if (pinnedRuntimeId != null) {
synchronized (originCache) {
KList<NativeStructureVolume> cached = originCache.get(
new RuntimeChunkKey(pinnedRuntimeId, chunkKey(chunkX, chunkZ)));
if (cached != null) {
return cached;
}
}
}
return originVolumes(engine, chunkX, chunkZ);
}
static long originWindowStripeMask(int chunkX, int chunkZ) {
int minCellX = Math.floorDiv(chunkX - ORIGIN_REACH_CHUNKS, ORIGIN_WINDOW_DIAMETER);
int maxCellX = Math.floorDiv(chunkX + ORIGIN_REACH_CHUNKS, ORIGIN_WINDOW_DIAMETER);
int minCellZ = Math.floorDiv(chunkZ - ORIGIN_REACH_CHUNKS, ORIGIN_WINDOW_DIAMETER);
int maxCellZ = Math.floorDiv(chunkZ + ORIGIN_REACH_CHUNKS, ORIGIN_WINDOW_DIAMETER);
long mask = 0L;
for (int cellX = minCellX; cellX <= maxCellX; cellX++) {
for (int cellZ = minCellZ; cellZ <= maxCellZ; cellZ++) {
mask |= 1L << originWindowStripe(cellX, cellZ);
}
}
return mask;
}
KList<NativeStructureVolume> originVolumes(Engine engine, int chunkX, int chunkZ) {
try (GenerationHistoryRuntimeRouter.CoordinateScope ignored =
openHistoryCoordinateScope(engine, chunkX, chunkZ)) {
int runtimeId = runtimeId(engine);
return cached(
originCache,
originBuilds,
new RuntimeChunkKey(runtimeId, chunkKey(chunkX, chunkZ)),
() -> origins.volumesAt(engine, chunkX, chunkZ)
);
}
}
private KList<NativeStructureVolume> buildOriginVolumes(Engine engine, int chunkX, int chunkZ) {
ChunkGeneratorStructureState state = context.structureState().get();
ChunkGenerator generator = context.generator().get();
BiomeSource biomeSource = context.biomeSource().get();
if (state == null || generator == null || biomeSource == null) {
return NativeStructureVolume.NONE;
}
Registry<Structure> registry = context.registryAccess().lookupOrThrow(Registries.STRUCTURE);
Bindings bindings = new Bindings(state, generator, biomeSource);
KList<NativeStructureVolume> volumes = appendPlannedVolumes(engine, bindings, registry, chunkX, chunkZ, null);
volumes = appendVanillaVolumes(engine, bindings, registry, chunkX, chunkZ, volumes);
return volumes == null ? NativeStructureVolume.NONE : volumes;
}
private KList<NativeStructureVolume> appendPlannedVolumes(Engine engine, Bindings bindings,
Registry<Structure> registry, int chunkX, int chunkZ,
KList<NativeStructureVolume> volumes) {
KList<NativeStructureStartPlan> plans;
try {
plans = NativeStructurePlacementPlanner.plansAt(engine, chunkX, chunkZ);
} catch (RuntimeException | Error error) {
warnOnce("iris-placements", error);
return volumes;
}
KList<NativeStructureVolume> target = volumes;
for (NativeStructureStartPlan plan : plans) {
IrisNativeStructureDecision decision = NativeStructurePlacementPlanner.decisionFor(plan);
if (!decision.generate()) {
continue;
}
Identifier identifier = Identifier.tryParse(plan.source().getStructure());
if (identifier == null) {
continue;
}
Structure structure = registry.getValue(identifier);
if (structure == null) {
continue;
}
String structureId = identifier.toString();
try {
StructureStart generated = NativeStructureFactory.generate(
generationContext(engine, bindings), registry.wrapAsHolder(structure), plan, 0);
target = appendPieces(target, structureId, generated);
} catch (RuntimeException | Error error) {
warnOnce(structureId, error);
}
}
return target;
}
private KList<NativeStructureVolume> appendVanillaVolumes(Engine engine, Bindings bindings,
Registry<Structure> registry, int chunkX, int chunkZ,
KList<NativeStructureVolume> volumes) {
ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
IntBinaryOperator surfaceHeight = surfaceHeight(engine);
int worldMinY = context.heightAccessor().getMinY();
int worldMaxYExclusive = worldMinY + context.heightAccessor().getHeight();
KList<NativeStructureVolume> target = volumes;
for (Holder<StructureSet> setHolder : bindings.state().possibleStructureSets()) {
StructureSet set = setHolder.value();
if (!set.placement().isStructureChunk(bindings.state(), chunkX, chunkZ)) {
continue;
}
for (StructureSet.StructureSelectionEntry entry : set.structures()) {
Holder<Structure> holder = entry.structure();
Structure structure = holder.value();
Identifier identifier = registry.getKey(structure);
if (identifier == null) {
continue;
}
String structureId = identifier.toString();
boolean undergroundStep = NativeStructureVegetationClearer.isUndergroundStep(structure.step());
try {
IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(
engine, structureId, undergroundStep);
if (!decision.generate()) {
continue;
}
StructureStart generated = structure.generate(
holder,
context.levelKey(),
context.registryAccess(),
bindings.generator(),
bindings.biomeSource(),
bindings.state().randomState().createClimateSampler(SamplerContext.EMPTY_UNCACHED),
bindings.state().randomState(),
context.templateManager(),
bindings.state().getLevelSeed(),
chunkPos,
0,
context.heightAccessor(),
structure.biomes()::contains
);
if (generated == null || !generated.isValid()) {
continue;
}
NativeStructureVerticalPlacer.applyVerticalPlacement(
generated,
structureId,
decision.yShift(),
bindings.generator().getSeaLevel(),
worldMinY,
worldMaxYExclusive,
undergroundStep,
decision.preserveSourceY(),
decision.yBand(),
surfaceHeight);
target = appendPieces(target, structureId, generated);
} catch (RuntimeException | Error error) {
warnOnce(structureId, error);
}
}
}
return target;
}
private NativeStructureFactory.GenerationContext generationContext(Engine engine, Bindings bindings) {
return new NativeStructureFactory.GenerationContext(
context.registryAccess(),
bindings.generator(),
bindings.biomeSource(),
bindings.state().randomState(),
context.templateManager(),
bindings.state().getLevelSeed(),
context.levelKey(),
context.heightAccessor(),
biome -> true,
bindings.generator().getSeaLevel(),
surfaceHeight(engine)
);
}
private record Bindings(ChunkGeneratorStructureState state, ChunkGenerator generator, BiomeSource biomeSource) {
}
@FunctionalInterface
interface OriginResolver {
KList<NativeStructureVolume> volumesAt(Engine engine, int chunkX, int chunkZ);
}
private static IntBinaryOperator surfaceHeight(Engine engine) {
return (x, z) -> Engine.hostHeight(engine, x, z, true) + engine.getMinHeight();
}
static KList<NativeStructureVolume> appendPieces(KList<NativeStructureVolume> volumes,
String structureId, StructureStart start) {
if (start == null || !start.isValid()) {
return volumes;
}
KList<NativeStructureVolume> target = volumes;
for (StructurePiece piece : start.getPieces()) {
BoundingBox bounds = piece.getBoundingBox();
if (target == null) {
target = new KList<>();
}
target.add(new NativeStructureVolume(structureId,
bounds.minX(), bounds.minY(), bounds.minZ(),
bounds.maxX(), bounds.maxY(), bounds.maxZ()));
}
return target;
}
void evictRuntime(int runtimeId) {
synchronized (originCache) {
originBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
originCache.keySet().removeIf(key -> key.runtimeId() == runtimeId);
}
synchronized (queryCache) {
queryBuilds.keySet().removeIf(key -> key.runtimeId() == runtimeId);
queryCache.keySet().removeIf(key -> key.runtimeId() == runtimeId);
}
}
private KList<NativeStructureVolume> cached(
Map<RuntimeChunkKey, KList<NativeStructureVolume>> cache,
ConcurrentHashMap<RuntimeChunkKey, CompletableFuture<KList<NativeStructureVolume>>> builds,
RuntimeChunkKey key,
Supplier<KList<NativeStructureVolume>> loader
) {
synchronized (cache) {
KList<NativeStructureVolume> hit = cache.get(key);
if (hit != null) {
return hit;
}
}
CompletableFuture<KList<NativeStructureVolume>> future = new CompletableFuture<>();
CompletableFuture<KList<NativeStructureVolume>> existing = builds.putIfAbsent(key, future);
if (existing != null) {
return NativeBuildFutures.awaitBuild(existing, "Native structure volume build");
}
// Close the check-then-claim window: a racer whose cache check missed before the
// previous builder cached could claim the build slot after it retired and rebuild.
synchronized (cache) {
KList<NativeStructureVolume> published = cache.get(key);
if (published != null) {
future.complete(published);
builds.remove(key, future);
return published;
}
}
try {
KList<NativeStructureVolume> built = loader.get();
synchronized (cache) {
if (builds.get(key) == future) {
cache.put(key, built);
}
}
future.complete(built);
return built;
} catch (RuntimeException | Error error) {
future.completeExceptionally(error);
throw error;
} finally {
builds.remove(key, future);
}
}
private static void warnOnce(String structureId, Throwable error) {
if (WARNED_RESOLUTION_FAILURES.add(structureId)) {
IrisLogging.reportError("Native structure volume resolution failed for '" + structureId
+ "'; objects will not be vetoed against it.", error);
}
}
private static long chunkKey(int chunkX, int chunkZ) {
return ((long) chunkX << 32) ^ (chunkZ & 0xffffffffL);
}
private static int originWindowStripe(int cellX, int cellZ) {
long mixed = chunkKey(cellX, cellZ);
mixed ^= mixed >>> 33;
mixed *= 0xff51afd7ed558ccdL;
mixed ^= mixed >>> 33;
mixed *= 0xc4ceb9fe1a85ec53L;
mixed ^= mixed >>> 33;
return (int) mixed & (ORIGIN_WINDOW_STRIPE_COUNT - 1);
}
private static ReentrantLock[] createOriginWindowStripes() {
ReentrantLock[] stripes = new ReentrantLock[ORIGIN_WINDOW_STRIPE_COUNT];
for (int i = 0; i < stripes.length; i++) {
stripes[i] = new ReentrantLock();
}
return stripes;
}
private void lockOriginWindow(long stripeMask) {
long pending = stripeMask;
while (pending != 0L) {
int stripe = Long.numberOfTrailingZeros(pending);
lockOriginStripe(originWindowStripes[stripe]);
pending &= pending - 1L;
}
}
private static void lockOriginStripe(ReentrantLock lock) {
if (lock.tryLock()) {
return;
}
if (!ForkJoinTask.inForkJoinPool()) {
lock.lock();
return;
}
OriginLockBlocker blocker = new OriginLockBlocker(lock);
try {
ForkJoinPool.managedBlock(blocker);
} catch (InterruptedException interruption) {
Thread.currentThread().interrupt();
blocker.block();
} catch (RejectedExecutionException exhaustedPool) {
blocker.block();
}
}
private void unlockOriginWindow(long stripeMask) {
long pending = stripeMask;
while (pending != 0L) {
int stripe = Long.SIZE - 1 - Long.numberOfLeadingZeros(pending);
originWindowStripes[stripe].unlock();
pending &= ~(1L << stripe);
}
}
private static final class OriginLockBlocker implements ForkJoinPool.ManagedBlocker {
private final ReentrantLock lock;
private boolean acquired;
private OriginLockBlocker(ReentrantLock lock) {
this.lock = lock;
}
@Override
public boolean block() {
if (!acquired) {
lock.lock();
acquired = true;
}
return true;
}
@Override
public boolean isReleasable() {
if (!acquired) {
acquired = lock.tryLock();
}
return acquired;
}
}
private static GenerationHistoryRuntimeRouter.CoordinateScope openHistoryCoordinateScope(
Engine engine,
int chunkX,
int chunkZ
) {
if (!(engine instanceof IrisEngine irisEngine)) {
return null;
}
try {
return irisEngine.openGenerationHistoryCoordinateScope(chunkX << 4, chunkZ << 4);
} catch (IOException failure) {
throw new IllegalStateException("Unable to route native structure origin "
+ chunkX + "," + chunkZ + " through generation history.", failure);
}
}
private static int runtimeId(Engine engine) {
return engine == null ? 0 : engine.getCacheID();
}
private void attachRetirementListener(Engine engine) {
if (engine instanceof IrisEngine irisEngine) {
irisEngine.addGenerationRuntimeRetirementListener(retirementListener);
}
}
private void detachRetirementListener(Engine engine) {
if (engine instanceof IrisEngine irisEngine) {
irisEngine.removeGenerationRuntimeRetirementListener(retirementListener);
}
}
private static Map<RuntimeChunkKey, KList<NativeStructureVolume>> lru(int capacity) {
return new LinkedHashMap<>(16, 0.75F, true) {
@Override
protected boolean removeEldestEntry(
Map.Entry<RuntimeChunkKey, KList<NativeStructureVolume>> eldest
) {
return size() > capacity;
}
};
}
private record RuntimeChunkKey(int runtimeId, long chunkKey) {
}
/**
* The generator, biome source and structure state arrive as suppliers so an installed index never holds the
* level (and through it the engine) strongly. That keeps the weakly keyed registry collectable on world unload.
*/
public record Context(
RegistryAccess registryAccess,
StructureTemplateManager templateManager,
ResourceKey<Level> levelKey,
LevelHeightAccessor heightAccessor,
Supplier<ChunkGenerator> generator,
Supplier<BiomeSource> biomeSource,
Supplier<ChunkGeneratorStructureState> structureState
) {
}
}
@@ -1,42 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
import net.minecraft.world.level.levelgen.Heightmap;
import java.util.OptionalInt;
import java.util.function.Supplier;
public final class NativeTerrainHeightCache {
private static final int MAXIMUM_CACHED_QUERIES = 65_536;
private final Object2IntLinkedOpenHashMap<Query> heights = new Object2IntLinkedOpenHashMap<>();
public OptionalInt resolvedHeight(Query query, Supplier<OptionalInt> resolver) {
synchronized (heights) {
if (heights.containsKey(query)) {
return OptionalInt.of(heights.getAndMoveToFirst(query));
}
}
OptionalInt resolved = resolver.get();
if (resolved.isEmpty()) {
return resolved;
}
int height = resolved.getAsInt();
synchronized (heights) {
heights.putAndMoveToFirst(query, height);
while (heights.size() > MAXIMUM_CACHED_QUERIES) {
heights.removeLastInt();
}
}
return resolved;
}
public void evictRuntime(int runtimeId) {
synchronized (heights) {
heights.keySet().removeIf(query -> query.runtimeId() == runtimeId);
}
}
public record Query(int runtimeId, int x, int z, Heightmap.Types type, int minimumY, int height) {
}
}
@@ -1,63 +0,0 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.world.history.TerrainBoundarySignature;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformBlockState;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.commands.arguments.blocks.BlockStateParser;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;
import java.util.HashMap;
import java.util.Map;
public final class NativeTransitionColumn {
private NativeTransitionColumn() {
}
public static NoiseColumn column(TerrainBoundarySignature signature, LevelHeightAccessor height) {
BlockState[] states = new BlockState[height.getHeight()];
Map<String, BlockState> palette = new HashMap<>();
for (int offset = 0; offset < states.length; offset++) {
String state = signature.geometry().voxelAt(height.getMinY() + offset).stateKey();
states[offset] = palette.computeIfAbsent(state, NativeTransitionColumn::parse);
}
return new NoiseColumn(height.getMinY(), states);
}
public static int height(TerrainBoundarySignature signature, Heightmap.Types type, LevelHeightAccessor height) {
Map<String, BlockState> palette = new HashMap<>();
for (int offset = height.getHeight() - 1; offset >= 0; offset--) {
String state = signature.geometry().voxelAt(height.getMinY() + offset).stateKey();
if (type.isOpaque().test(palette.computeIfAbsent(state, NativeTransitionColumn::parse))) {
return height.getMinY() + offset + 1;
}
}
return height.getMinY();
}
private static BlockState parse(String key) {
String nativeKey = key;
if (!key.startsWith("minecraft:") && IrisPlatforms.isBound()) {
PlatformBlockState logicalState = IrisPlatforms.get().registries().blockOrNull(key);
PlatformBlockState baseState = logicalState == null ? null : logicalState.placementBaseState();
if (baseState != null) {
nativeKey = baseState.key();
}
}
StringReader reader = new StringReader(nativeKey);
try {
BlockState state = BlockStateParser.parseForBlock(BuiltInRegistries.BLOCK, reader, false).blockState();
if (reader.canRead()) {
throw new IllegalArgumentException("Unexpected terrain block state suffix: " + reader.getRemaining());
}
return state;
} catch (CommandSyntaxException failure) {
throw new IllegalArgumentException("Saved terrain block state cannot be resolved: " + key, failure);
}
}
}
@@ -1,113 +0,0 @@
package art.arcane.iris.nativegen.v26_3_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.
*/
public final class WorldgenTerrainHeightmaps {
private static final int COLUMNS = 256;
private static final int PLACEMENT_CHUNK_MARGIN = 1;
private WorldgenTerrainHeightmaps() {
}
public 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);
}
public static void primeStructurePlacement(WorldGenLevel world, ChunkPos generationCenter,
List<StructureStart> starts,
IntBinaryOperator surfaceFirstFreeY,
IntBinaryOperator floorFirstFreeY) {
Objects.requireNonNull(world,
"Iris worldgen heightmap priming requires a generation level");
Objects.requireNonNull(generationCenter,
"Iris worldgen heightmap priming requires a generation center");
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 (Math.abs(chunkX - generationCenter.x()) > PLACEMENT_CHUNK_MARGIN
|| Math.abs(chunkZ - generationCenter.z()) > PLACEMENT_CHUNK_MARGIN) {
continue;
}
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);
}
}
@@ -1,513 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_3_R1;
import net.minecraft.world.level.levelgen.structure.placement.AbstractSpreadingStructurePlacement;
import art.arcane.iris.pack.datapack.DatapackStructureScopeIndex;
import art.arcane.iris.structure.nativegen.NativeStructureFrequencyScale;
import art.arcane.iris.structure.nativegen.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.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 DatapackStructureStateFilter {
private DatapackStructureStateFilter() {
}
static Selection filter(
List<Holder<StructureSet>> structureSets,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources
) {
return filter(structureSets, scopeIndex, declaredSources, new IrisImportedStructureControl());
}
static Selection filter(
List<Holder<StructureSet>> structureSets,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources,
IrisImportedStructureControl importedStructures
) {
return filter(
structureSets,
scopeIndex,
declaredSources,
importedStructures,
keyIndex(structureSets));
}
static Selection filter(
List<Holder<StructureSet>> structureSets,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources,
IrisImportedStructureControl importedStructures,
StructureSetKeyIndex keyIndex
) {
if (scopeIndex.isEmpty()) {
return new Selection(
scaleFrequencyOverrides(structureSets, importedStructures, keyIndex),
0,
0);
}
Map<String, Holder<StructureSet>> holdersByKey = new HashMap<>();
for (Holder<StructureSet> holder : structureSets) {
String key = structureSetKey(holder, keyIndex);
if (key != null) {
holdersByKey.putIfAbsent(key, holder);
}
}
Map<Holder<StructureSet>, Holder<StructureSet>> scopedByIdentity = new IdentityHashMap<>();
Set<Holder<StructureSet>> visiting = Collections.newSetFromMap(new IdentityHashMap<>());
List<Holder<StructureSet>> filteredSets = new ArrayList<>(structureSets.size());
int retainedManagedSets = 0;
int excludedManagedSets = 0;
for (Holder<StructureSet> holder : structureSets) {
String setKey = structureSetKey(holder, keyIndex);
boolean managedSet = setKey != null && scopeIndex.isManagedStructureSet(setKey);
Holder<StructureSet> scopedHolder = scopeHolder(
holder,
scopeIndex,
declaredSources,
holdersByKey,
scopedByIdentity,
visiting,
keyIndex);
if (scopedHolder == null) {
if (managedSet) {
excludedManagedSets++;
}
continue;
}
if (managedSet) {
retainedManagedSets++;
}
filteredSets.add(scopedHolder);
}
return new Selection(
scaleFrequencyOverrides(
List.copyOf(filteredSets), importedStructures, keyIndex),
retainedManagedSets,
excludedManagedSets);
}
static String structureSetKey(Holder<StructureSet> holder) {
return structureSetKey(holder, keyIndex(List.of(holder)));
}
static StructureSetKeyIndex keyIndex(
Iterable<? extends Holder<StructureSet>> registeredStructureSets
) {
return StructureSetKeyIndex.create(registeredStructureSets);
}
private static String structureSetKey(
Holder<StructureSet> holder,
StructureSetKeyIndex keyIndex
) {
return keyIndex.resolve(holder)
.map(key -> key.identifier().toString())
.orElse(null);
}
private static List<StructureSet.StructureSelectionEntry> filterEntries(
List<StructureSet.StructureSelectionEntry> entries,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources
) {
List<StructureSet.StructureSelectionEntry> filtered = new ArrayList<>(entries.size());
for (StructureSet.StructureSelectionEntry entry : entries) {
String structureKey = entry.structure().unwrapKey()
.map(key -> key.identifier().toString())
.orElse(null);
if (structureKey == null || scopeIndex.allowsStructure(structureKey, declaredSources)) {
filtered.add(entry);
}
}
return filtered;
}
private static Holder<StructureSet> scopeHolder(
Holder<StructureSet> holder,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources,
Map<String, Holder<StructureSet>> holdersByKey,
Map<Holder<StructureSet>, Holder<StructureSet>> scopedByIdentity,
Set<Holder<StructureSet>> visiting,
StructureSetKeyIndex keyIndex
) {
if (scopedByIdentity.containsKey(holder)) {
return scopedByIdentity.get(holder);
}
if (!visiting.add(holder)) {
return null;
}
try {
String setKey = structureSetKey(holder, keyIndex);
if (setKey != null && scopeIndex.isManagedStructureSet(setKey)
&& !scopeIndex.allowsStructureSet(setKey, declaredSources)) {
scopedByIdentity.put(holder, null);
return null;
}
StructureSet originalSet = holder.value();
List<StructureSet.StructureSelectionEntry> entries = filterEntries(
originalSet.structures(), scopeIndex, declaredSources);
if (entries.isEmpty()) {
scopedByIdentity.put(holder, null);
return null;
}
StructurePlacement placement = scopePlacement(
originalSet.placement(),
scopeIndex,
declaredSources,
holdersByKey,
scopedByIdentity,
visiting,
keyIndex);
if (entries.size() == originalSet.structures().size()
&& placement == originalSet.placement()) {
scopedByIdentity.put(holder, holder);
return holder;
}
Holder<StructureSet> scoped = replacementHolder(
holder, new StructureSet(entries, placement), keyIndex);
scopedByIdentity.put(holder, scoped);
return scoped;
} finally {
visiting.remove(holder);
}
}
static Optional<AbstractSpreadingStructurePlacement.ExclusionZone> exclusionZone(StructurePlacement placement) {
if (!(placement instanceof AbstractSpreadingStructurePlacement)) {
return Optional.empty();
}
Object value = declaredFieldValue(AbstractSpreadingStructurePlacement.class, placement, Optional.class);
if (value instanceof Optional<?> optional && (optional.isEmpty()
|| optional.get() instanceof AbstractSpreadingStructurePlacement.ExclusionZone)) {
@SuppressWarnings("unchecked")
Optional<AbstractSpreadingStructurePlacement.ExclusionZone> resolved =
(Optional<AbstractSpreadingStructurePlacement.ExclusionZone>) optional;
return resolved;
}
throw new IllegalStateException("Could not read structure placement exclusion zone from "
+ placement.getClass().getName());
}
private static StructurePlacement scopePlacement(
StructurePlacement placement,
DatapackStructureScopeIndex scopeIndex,
Set<String> declaredSources,
Map<String, Holder<StructureSet>> holdersByKey,
Map<Holder<StructureSet>, Holder<StructureSet>> scopedByIdentity,
Set<Holder<StructureSet>> visiting,
StructureSetKeyIndex keyIndex
) {
Optional<AbstractSpreadingStructurePlacement.ExclusionZone> currentZone = exclusionZone(placement);
if (currentZone.isEmpty()) {
return placement;
}
Holder<StructureSet> target = currentZone.get().otherSet();
String targetKey = structureSetKey(target, keyIndex);
if (targetKey != null) {
target = holdersByKey.getOrDefault(targetKey, target);
}
Holder<StructureSet> scopedTarget = scopeHolder(
target,
scopeIndex,
declaredSources,
holdersByKey,
scopedByIdentity,
visiting,
keyIndex);
Optional<AbstractSpreadingStructurePlacement.ExclusionZone> scopedZone = Optional.empty();
if (scopedTarget != null) {
scopedZone = Optional.of(new AbstractSpreadingStructurePlacement.ExclusionZone(
scopedTarget,
currentZone.get().chunkCount()));
}
if (scopedTarget == currentZone.get().otherSet()) {
return placement;
}
return copyPlacement(placement, scopedZone, 1D);
}
private static List<Holder<StructureSet>> scaleFrequencyOverrides(
List<Holder<StructureSet>> structureSets,
IrisImportedStructureControl importedStructures,
StructureSetKeyIndex keyIndex
) {
if (!importedStructures.hasFrequencyOverrides()) {
return structureSets;
}
Map<String, Holder<StructureSet>> holdersByKey = new HashMap<>();
for (Holder<StructureSet> holder : structureSets) {
String key = structureSetKey(holder, keyIndex);
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 = scaleFrequencyHolder(
holder,
importedStructures,
holdersByKey,
scaledByIdentity,
keyIndex);
scaledSets.add(scaled);
changed |= scaled != holder;
}
return changed ? List.copyOf(scaledSets) : structureSets;
}
private static Holder<StructureSet> scaleFrequencyHolder(
Holder<StructureSet> holder,
IrisImportedStructureControl importedStructures,
Map<String, Holder<StructureSet>> holdersByKey,
Map<Holder<StructureSet>, Holder<StructureSet>> scaledByIdentity,
StructureSetKeyIndex keyIndex
) {
if (scaledByIdentity.containsKey(holder)) {
return scaledByIdentity.get(holder);
}
if (!dependsOnFrequencyOverride(
holder, importedStructures, holdersByKey, keyIndex)) {
scaledByIdentity.put(holder, holder);
return holder;
}
ResourceKey<StructureSet> holderKey = keyIndex.resolve(holder).orElseThrow(() ->
new IllegalStateException(
"An affected native structure-set exclusion graph has an unkeyed holder"));
ReboundStructureSetHolder scaledHolder = new ReboundStructureSetHolder(holderKey);
scaledByIdentity.put(holder, scaledHolder);
StructureSet originalSet = holder.value();
StructurePlacement originalPlacement = originalSet.placement();
Optional<AbstractSpreadingStructurePlacement.ExclusionZone> originalZone = exclusionZone(originalPlacement);
Optional<AbstractSpreadingStructurePlacement.ExclusionZone> scaledZone = originalZone;
if (originalZone.isPresent()) {
Holder<StructureSet> target = canonicalHolder(
originalZone.get().otherSet(), holdersByKey, keyIndex);
Holder<StructureSet> scaledTarget = scaleFrequencyHolder(
target,
importedStructures,
holdersByKey,
scaledByIdentity,
keyIndex);
if (scaledTarget != originalZone.get().otherSet()) {
scaledZone = Optional.of(new AbstractSpreadingStructurePlacement.ExclusionZone(
scaledTarget, originalZone.get().chunkCount()));
}
}
double multiplier = importedStructures.frequencyMultiplier(
structureSetKey(holder, keyIndex));
StructurePlacement scaledPlacement = multiplier == 1D && scaledZone.equals(originalZone)
? originalPlacement
: copyPlacement(originalPlacement, scaledZone, multiplier);
scaledHolder.bind(new StructureSet(originalSet.structures(), scaledPlacement));
return scaledHolder;
}
private static boolean dependsOnFrequencyOverride(
Holder<StructureSet> holder,
IrisImportedStructureControl importedStructures,
Map<String, Holder<StructureSet>> holdersByKey,
StructureSetKeyIndex keyIndex
) {
Set<Holder<StructureSet>> visited = Collections.newSetFromMap(new IdentityHashMap<>());
Holder<StructureSet> current = holder;
while (visited.add(current)) {
if (importedStructures.frequencyMultiplier(
structureSetKey(current, keyIndex)) != 1D) {
return true;
}
Optional<AbstractSpreadingStructurePlacement.ExclusionZone> zone =
exclusionZone(current.value().placement());
if (zone.isEmpty()) {
return false;
}
current = canonicalHolder(zone.get().otherSet(), holdersByKey, keyIndex);
}
return false;
}
private static Holder<StructureSet> canonicalHolder(
Holder<StructureSet> holder,
Map<String, Holder<StructureSet>> holdersByKey,
StructureSetKeyIndex keyIndex
) {
String key = structureSetKey(holder, keyIndex);
return key == null ? holder : holdersByKey.getOrDefault(key, holder);
}
private static Holder<StructureSet> replacementHolder(
Holder<StructureSet> original,
StructureSet replacement,
StructureSetKeyIndex keyIndex
) {
Optional<ResourceKey<StructureSet>> key = keyIndex.resolve(original);
if (key.isEmpty()) {
return Holder.direct(replacement);
}
ReboundStructureSetHolder holder = new ReboundStructureSetHolder(key.get());
holder.bind(replacement);
return holder;
}
private static StructurePlacement copyPlacement(
StructurePlacement placement,
Optional<AbstractSpreadingStructurePlacement.ExclusionZone> exclusionZone,
double frequencyMultiplier
) {
Vec3i locateOffset = (Vec3i) declaredFieldValue(
AbstractSpreadingStructurePlacement.class, placement, Vec3i.class);
AbstractSpreadingStructurePlacement.FrequencyReductionMethod frequencyReductionMethod =
(AbstractSpreadingStructurePlacement.FrequencyReductionMethod) declaredFieldValue(
AbstractSpreadingStructurePlacement.class,
placement,
AbstractSpreadingStructurePlacement.FrequencyReductionMethod.class);
float frequency = (float) declaredFieldValue(
AbstractSpreadingStructurePlacement.class, placement, float.class);
int salt = (int) declaredFieldValue(
AbstractSpreadingStructurePlacement.class, placement, int.class);
if (placement instanceof RandomSpreadStructurePlacement randomSpread) {
NativeStructureFrequencyScale scale = NativeStructureFrequencyScale.randomSpread(
frequency, randomSpread.spacing(), randomSpread.separation(), frequencyMultiplier);
return new RandomSpreadStructurePlacement(
locateOffset,
frequencyReductionMethod,
scale.frequency(),
salt,
exclusionZone,
scale.spacing(),
randomSpread.separation(),
randomSpread.spreadType());
}
if (placement.getClass() == ConcentricRingsStructurePlacement.class) {
ConcentricRingsStructurePlacement rings =
(ConcentricRingsStructurePlacement) placement;
float scaledFrequency = NativeStructureFrequencyScale.probability(
frequency, frequencyMultiplier);
return new ConcentricRingsStructurePlacement(
locateOffset,
frequencyReductionMethod,
scaledFrequency,
salt,
exclusionZone,
rings.distance(),
rings.spread(),
rings.count(),
rings.preferredBiomes());
}
throw new IllegalStateException("Unsupported native structure placement in an affected "
+ "frequency-override or datapack-scope graph: "
+ placement.getClass().getName());
}
private static Object declaredFieldValue(
Class<?> declaringType,
Object target,
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) {
throw new IllegalStateException("Missing " + fieldType.getName() + " field on "
+ declaringType.getName());
}
try {
match.setAccessible(true);
return match.get(target);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Could not read " + fieldType.getName() + " field on "
+ declaringType.getName(), e);
}
}
static final class StructureSetKeyIndex {
private final Map<List<StructureSet.StructureSelectionEntry>, ResourceKey<StructureSet>>
keysByEntries;
private StructureSetKeyIndex(
Map<List<StructureSet.StructureSelectionEntry>, ResourceKey<StructureSet>>
keysByEntries
) {
this.keysByEntries = keysByEntries;
}
private static StructureSetKeyIndex create(
Iterable<? extends Holder<StructureSet>> registeredStructureSets
) {
Map<List<StructureSet.StructureSelectionEntry>, ResourceKey<StructureSet>> keys =
new IdentityHashMap<>();
for (Holder<StructureSet> holder : registeredStructureSets) {
Optional<ResourceKey<StructureSet>> key = holder.unwrapKey();
if (key.isEmpty()) {
continue;
}
List<StructureSet.StructureSelectionEntry> entries = holder.value().structures();
ResourceKey<StructureSet> previous = keys.put(entries, key.get());
if (previous != null && !previous.equals(key.get())) {
throw new IllegalStateException(
"Multiple registered structure sets share the same entry list");
}
}
return new StructureSetKeyIndex(keys);
}
private Optional<ResourceKey<StructureSet>> resolve(Holder<StructureSet> holder) {
Optional<ResourceKey<StructureSet>> key = holder.unwrapKey();
if (key.isPresent()) {
return key;
}
return Optional.ofNullable(keysByEntries.get(holder.value().structures()));
}
}
record Selection(
List<Holder<StructureSet>> structureSets,
int retainedManagedSets,
int excludedManagedSets
) {
}
private static final class ReboundStructureSetHolder extends Holder.Reference<StructureSet> {
private ReboundStructureSetHolder(ResourceKey<StructureSet> key) {
super(Type.STAND_ALONE, new HolderOwner<>() {
}, key, null);
}
private void bind(StructureSet structureSet) {
bindValue(structureSet);
}
}
}
@@ -1,441 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_3_R1;
import net.minecraft.world.level.levelgen.placement.FeaturePlacer;
import art.arcane.iris.generation.runtime.DimensionStackContext;
import art.arcane.iris.generation.runtime.DimensionStackLayout;
import art.arcane.iris.nativegen.v26_3_R1.NativeGenerationWriteGuard;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.structure.nativegen.NativeFeatureGenerationPolicy;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.biome.IrisBiomeCustom;
import art.arcane.iris.generation.decoration.IrisDecorationStep;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.structure.nativegen.IrisImportedFeatureControl;
import art.arcane.iris.structure.object.IrisStaticObjectLayer;
import art.arcane.iris.spi.IrisLogging;
import it.unimi.dsi.fastutil.ints.IntArraySet;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.Registry;
import net.minecraft.core.SectionPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeGenerationSettings;
import net.minecraft.world.level.biome.FeatureSorter;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.RandomSupport;
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.placement.PlacedFeature;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.IntBinaryOperator;
/**
* Bukkit twin of the modded imported-feature stage. Same control, same semantics, same seeds: with
* {@code importedFeatures.enabled} the vanilla placed-feature pass runs over Iris terrain, and with the
* control off nothing here allocates or runs.
*
* <p>Iris does its own native structure pass and calls the delegate with {@code addVanillaDecorations=false},
* so the vanilla feature half never runs on its own. This reproduces that half only - structures are never
* placed twice.
*
* <p>Threading: called from {@code applyBiomeDecoration} on the worldgen thread that owns the chunk. The
* FEATURES chunk step is not parallel-safe.
*/
final class ImportedFeatureStage {
private static final String CYCLE_MARKER = "Feature order cycle found";
private final Engine engine;
private final ConcurrentHashMap<Integer, RuntimeFeatureState> runtimeStates = new ConcurrentHashMap<>();
ImportedFeatureStage(Engine engine) {
this.engine = engine;
}
/**
* Generation settings for one biome holder, mapping Iris custom biomes onto their vanilla derivative.
* Pass-through while the control is off, which is what keeps {@code BiomeFilter} behaving exactly as it
* does today.
*/
BiomeGenerationSettings generationSettings(Holder<Biome> biome) {
RuntimeFeatureState state = currentState();
FeatureTable table = state == null ? null : state.featureTable();
if (table == null) {
return null;
}
Holder<Biome> mapped = table.derivatives().get(holderKey(biome));
return mapped == null ? null : mapped.value().getGenerationSettings();
}
/**
* Builds the table on the first decorated chunk, or leaves the stage inert. A feature-order cycle is
* reported once here and degrades to features-off; it never reaches chunk generation as a crash.
*/
void prepare(WorldGenLevel level) {
IrisDimension dimension = engine.getDimension();
int runtimeId = engine.getCacheID();
if (settled(runtimeStates.get(runtimeId), dimension)) {
return;
}
synchronized (this) {
if (settled(runtimeStates.get(runtimeId), dimension)) {
return;
}
runtimeStates.put(runtimeId, build(level, dimension));
}
}
void evictRuntime(int runtimeId) {
runtimeStates.remove(runtimeId);
}
private RuntimeFeatureState currentState() {
IrisDimension dimension = engine.getDimension();
RuntimeFeatureState state = runtimeStates.get(engine.getCacheID());
return settled(state, dimension) ? state : null;
}
private static boolean settled(RuntimeFeatureState state, IrisDimension dimension) {
return state != null && state.dimension() == dimension;
}
private RuntimeFeatureState build(WorldGenLevel level, IrisDimension dimension) {
IrisImportedFeatureControl control;
try {
control = NativeFeatureGenerationPolicy.control(engine);
} catch (RuntimeException error) {
IrisLogging.error("Iris could not read importedFeatures for this dimension; features off: "
+ error);
return new RuntimeFeatureState(dimension, null);
}
if (!control.shouldGenerateFeatures()) {
return new RuntimeFeatureState(dimension, null);
}
FeatureTable built;
try {
built = buildTable(level, control, dimension);
} catch (Throwable error) {
IrisLogging.error("Iris importedFeatures is off for " + dimensionKey()
+ ": feature table construction failed: " + error);
return new RuntimeFeatureState(dimension, null);
}
if (built == null) {
return new RuntimeFeatureState(dimension, null);
}
IrisLogging.info("Iris importedFeatures on for " + dimensionKey() + ": " + built.biomes().size()
+ " biomes, " + built.steps().size() + " steps, " + built.derivatives().size()
+ " custom-biome derivative maps");
return new RuntimeFeatureState(dimension, built);
}
private FeatureTable buildTable(WorldGenLevel level, IrisImportedFeatureControl control,
IrisDimension dimension) {
Registry<Biome> registry = level.registryAccess().lookupOrThrow(Registries.BIOME);
Set<String> visibleKeys = visibleBiomeKeys();
// Registry-ordered walk, never a hash-ordered set: FeatureSorter's cycle detection walks this list and
// an unordered walk makes detection depend on JVM hash order.
List<Holder<Biome>> biomes = new ArrayList<>();
Map<String, Holder<Biome>> byKey = new HashMap<>();
registry.listElements().forEach((Holder.Reference<Biome> reference) -> {
String key = holderKey(reference);
if (key != null && visibleKeys.contains(key)) {
biomes.add(reference);
byKey.put(key, reference);
}
});
if (biomes.isEmpty()) {
IrisLogging.error("Iris importedFeatures is on but " + dimensionKey()
+ " exposes no registered biomes; features off");
return null;
}
Map<String, Holder<Biome>> derivatives = customBiomeDerivatives(registry, byKey);
List<FeatureSorter.StepFeatureData> steps;
try {
steps = FeatureSorter.buildFeaturesPerStep(biomes,
(Holder<Biome> biome) -> settingsFor(biome, derivatives).features(), true);
} catch (IllegalStateException error) {
String message = error.getMessage();
if (message == null || !message.contains(CYCLE_MARKER)) {
throw error;
}
IrisLogging.error("Iris importedFeatures is off for " + dimensionKey()
+ ": the registered placed features cannot be ordered. " + message
+ ". Remove or reorder the conflicting content, or leave importedFeatures.enabled false.");
return null;
}
boolean filtered = control.getDisabled() != null && !control.getDisabled().isEmpty();
return new FeatureTable(dimension, control, List.copyOf(biomes), Set.copyOf(biomes),
Map.copyOf(byKey), steps, Map.copyOf(derivatives), filtered);
}
/**
* Every biome key Iris can write into a chunk section: the structure derivative, the raw derivative, every
* scatter entry, and every generated custom biome.
*/
private Set<String> visibleBiomeKeys() {
Set<String> keys = new LinkedHashSet<>();
for (IrisBiome irisBiome : engine.getDimension().getReachableBiomes(engine)) {
addKey(keys, irisBiome.getStructureDerivativeKey());
addKey(keys, irisBiome.getDerivativeKey());
addKey(keys, irisBiome.getVanillaDerivativeKey());
for (String scatter : irisBiome.getBiomeScatter()) {
addKey(keys, scatter);
}
for (String scatter : irisBiome.getBiomeSkyScatter()) {
addKey(keys, scatter);
}
if (!irisBiome.isCustom()) {
continue;
}
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
addKey(keys, engine.getData().customBiomeResourceKey(engine.getDimension(), customBiome));
}
}
return keys;
}
/**
* Maps every generated Iris custom biome key onto the registry holder of its Iris biome's vanilla
* derivative. The custom biome's own datapack JSON carries no features by design.
*/
private Map<String, Holder<Biome>> customBiomeDerivatives(Registry<Biome> registry,
Map<String, Holder<Biome>> byKey) {
Map<String, Holder<Biome>> derivatives = new HashMap<>();
for (IrisBiome irisBiome : engine.getDimension().getReachableBiomes(engine)) {
if (!irisBiome.isCustom()) {
continue;
}
String derivativeKey = normalizeKey(irisBiome.getVanillaDerivativeKey());
// Resolve from the registry, not only from the visible biome set: a sea or shore biome's structure
// derivative is rewritten away from its vanilla derivative, so the derivative whose features we
// want is not always a biome Iris can emit.
Holder<Biome> derivative = byKey.get(derivativeKey);
if (derivative == null) {
derivative = resolveHolder(registry, derivativeKey);
}
if (derivative == null) {
IrisLogging.warn("Iris importedFeatures: vanilla derivative " + derivativeKey + " of biome "
+ irisBiome.getLoadKey()
+ " is not registered; its custom biomes generate no imported features");
continue;
}
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
derivatives.put(
engine.getData().customBiomeResourceKey(engine.getDimension(), customBiome),
derivative
);
}
}
return derivatives;
}
private static Holder<Biome> resolveHolder(Registry<Biome> registry, String key) {
if (registry == null || key == null || key.isBlank()) {
return null;
}
Identifier identifier = Identifier.tryParse(key);
if (identifier == null) {
return null;
}
return registry.get(identifier).<Holder<Biome>>map((Holder.Reference<Biome> reference) -> reference)
.orElse(null);
}
private static BiomeGenerationSettings settingsFor(Holder<Biome> biome,
Map<String, Holder<Biome>> derivatives) {
Holder<Biome> mapped = derivatives.get(holderKey(biome));
return mapped == null
? biome.value().getGenerationSettings()
: mapped.value().getGenerationSettings();
}
/**
* Runs the vanilla placed-feature pass for one chunk on the calling worldgen thread. A no-op while the
* control is disabled or the table degraded.
*/
void run(WorldGenLevel level, ChunkAccess chunk, ChunkGenerator owner) {
RuntimeFeatureState state = currentState();
FeatureTable table = state == null ? null : state.featureTable();
if (table == null) {
return;
}
ChunkPos centerPos = chunk.getPos();
SectionPos sectionPos = SectionPos.of(centerPos, level.getMinSectionY());
BlockPos origin = sectionPos.origin();
Registry<PlacedFeature> featureRegistry = level.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE);
List<FeatureSorter.StepFeatureData> steps = table.steps();
WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
long decorationSeed = random.setDecorationSeed(level.getSeed(), origin.getX(), origin.getZ());
IrisStaticObjectLayer staticObjects = engine.getDimension().getStaticObjectLayer(engine.getData());
int staticMinY = engine.getMinHeight();
DimensionStackContext stackContext = engine.getDimensionStackContext();
Set<Holder<Biome>> chunkBiomes = chunkBiomes(level, sectionPos, table, stackContext);
IntBinaryOperator surfaceFirstFreeY = stackContext == null
? (x, z) -> level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z)
: (x, z) -> Engine.hostHeight(engine, x, z, false) + staticMinY + 1;
IntBinaryOperator floorFirstFreeY = stackContext == null
? (x, z) -> level.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, x, z)
: (x, z) -> Engine.hostHeight(engine, x, z, true) + staticMinY + 1;
WorldGenLevel placementLevel = staticObjects.isEmpty() && stackContext == null
? level
: NativeStructureWorldgenAccess.create(
level, centerPos,
surfaceFirstFreeY,
floorFirstFreeY,
stackContext != null,
NativeGenerationWriteGuard.protectedPositions(staticObjects, stackContext, staticMinY));
try {
for (int stepIndex = 0; stepIndex < steps.size(); stepIndex++) {
if (!table.control().shouldGenerateStep(IrisDecorationStep.byOrdinal(stepIndex))) {
continue;
}
placeStep(placementLevel, table, steps.get(stepIndex), featureRegistry, chunkBiomes, owner,
random, decorationSeed, origin, stepIndex);
}
} catch (Throwable error) {
throw new IllegalStateException("Iris imported feature placement failed for chunk "
+ centerPos.x() + "," + centerPos.z(), error);
} finally {
level.setCurrentlyGenerating(null);
}
}
private void placeStep(WorldGenLevel level, FeatureTable table, FeatureSorter.StepFeatureData stepData,
Registry<PlacedFeature> featureRegistry, Set<Holder<Biome>> chunkBiomes,
ChunkGenerator owner, WorldgenRandom random, long decorationSeed,
BlockPos origin, int stepIndex) {
IntSet stepFeatures = new IntArraySet();
for (Holder<Biome> biome : chunkBiomes) {
List<HolderSet<PlacedFeature>> biomeFeatures = settingsFor(biome, table.derivatives()).features();
if (stepIndex >= biomeFeatures.size()) {
continue;
}
for (Holder<PlacedFeature> feature : biomeFeatures.get(stepIndex)) {
stepFeatures.add(stepData.indexMapping().applyAsInt(feature.value()));
}
}
if (stepFeatures.isEmpty()) {
return;
}
// Sorted global indices: identical ordering to vanilla, and each feature's seed comes from its own
// global index, so denying one feature never shifts another.
int[] featureIndices = stepFeatures.toIntArray();
Arrays.sort(featureIndices);
for (int globalIndex : featureIndices) {
PlacedFeature feature = stepData.features().get(globalIndex);
if (table.filtered()) {
Identifier featureId = featureRegistry.getKey(feature);
if (featureId != null && !table.control().shouldGenerate(featureId.toString())) {
continue;
}
}
random.setFeatureSeed(decorationSeed, globalIndex, stepIndex);
level.setCurrentlyGenerating(() -> describeFeature(featureRegistry, feature));
new FeaturePlacer(level, owner).placeWithBiomeCheck(feature, random, origin);
}
}
private static String describeFeature(Registry<PlacedFeature> registry, PlacedFeature feature) {
Identifier id = registry.getKey(feature);
return id == null ? feature.toString() : id.toString();
}
private Set<Holder<Biome>> chunkBiomes(
WorldGenLevel level,
SectionPos sectionPos,
FeatureTable table,
DimensionStackContext stackContext
) {
List<Holder<Biome>> collected = new ArrayList<>();
ChunkPos.rangeClosed(sectionPos.chunk(), 1).forEach((ChunkPos chunkPos) -> {
ChunkAccess neighbour = level.getChunk(chunkPos.x(), chunkPos.z());
LevelChunkSection[] sections = neighbour.getSections();
for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {
LevelChunkSection section = sections[sectionIndex];
if (stackContext == null) {
section.getBiomes().getAll(collected::add);
continue;
}
int sectionMinimumY = neighbour.getSectionYFromSectionIndex(sectionIndex) << 4;
for (int quartY = 0; quartY < 4; quartY++) {
int internalY = sectionMinimumY + (quartY << 2) - level.getMinY();
for (int quartX = 0; quartX < 4; quartX++) {
int blockX = chunkPos.getMinBlockX() + (quartX << 2);
for (int quartZ = 0; quartZ < 4; quartZ++) {
int blockZ = chunkPos.getMinBlockZ() + (quartZ << 2);
DimensionStackLayout.Layer layer = stackContext.getLayerAt(
blockX, internalY, blockZ);
if (layer.terrainContext().isSelfReferencing()) {
collected.add(section.getBiomes().get(quartX, quartY, quartZ));
}
}
}
}
}
});
Set<Holder<Biome>> present = new LinkedHashSet<>();
for (Holder<Biome> biome : collected) {
if (table.biomeSet().contains(biome)) {
present.add(biome);
continue;
}
String key = holderKey(biome);
Holder<Biome> canonical = key == null ? null : table.byKey().get(key);
if (canonical != null) {
present.add(canonical);
}
}
return present;
}
private static void addKey(Set<String> keys, String key) {
String normalized = normalizeKey(key);
if (normalized != null) {
keys.add(normalized);
}
}
private String dimensionKey() {
IrisDimension dimension = engine.getDimension();
return dimension == null ? "<unbound>" : dimension.getLoadKey();
}
private static String holderKey(Holder<Biome> holder) {
return holder.unwrapKey()
.map(key -> key.identifier().toString().toLowerCase(Locale.ROOT))
.orElse(null);
}
private static String normalizeKey(String key) {
Identifier identifier = key == null ? null : Identifier.tryParse(key);
return identifier == null ? null : identifier.toString().toLowerCase(Locale.ROOT);
}
private record FeatureTable(IrisDimension dimension, IrisImportedFeatureControl control,
List<Holder<Biome>> biomes, Set<Holder<Biome>> biomeSet,
Map<String, Holder<Biome>> byKey,
List<FeatureSorter.StepFeatureData> steps,
Map<String, Holder<Biome>> derivatives, boolean filtered) {
}
private record RuntimeFeatureState(IrisDimension dimension, FeatureTable featureTable) {
}
}
@@ -1,132 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_3_R1;
import art.arcane.iris.spi.IrisLogging;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.util.random.WeightedList;
import net.minecraft.world.attribute.EnvironmentAttributes;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.MobSpawnSettings;
import net.minecraft.world.level.gamerules.GameRules;
import org.bukkit.event.entity.CreatureSpawnEvent;
import java.util.Optional;
final class InitialMobSpawner {
private InitialMobSpawner() {
}
static void spawn(
WorldGenRegion level, Holder<Biome> biome, ChunkPos chunkPos, RandomSource random
) {
if (level.getLevel().getGameRules().get(GameRules.SPAWN_MOBS)) {
MobSpawnSettings mobSettings = biome.value().getAttributes().applyModifier(
EnvironmentAttributes.NATURAL_MOB_SPAWNS, EnvironmentAttributes.NATURAL_MOB_SPAWNS.defaultValue());
WeightedList<MobSpawnSettings.SpawnerData> mobs = mobSettings.getMobsToSpawn(MobCategory.CREATURE);
if (!mobs.isEmpty()) {
float creatureProbability = biome.value().getAttributes().applyModifier(
EnvironmentAttributes.CREATURE_WORLD_GEN_SPAWN_PROBABILITY,
EnvironmentAttributes.CREATURE_WORLD_GEN_SPAWN_PROBABILITY.defaultValue());
int xo = chunkPos.getMinBlockX();
int zo = chunkPos.getMinBlockZ();
while (random.nextFloat() < creatureProbability) {
Optional<MobSpawnSettings.SpawnerData> nextSpawnerData = mobs.getRandom(random);
if (!nextSpawnerData.isEmpty()) {
MobSpawnSettings.SpawnerData spawnerData = nextSpawnerData.get();
int count = spawnerData.count().sample(random);
SpawnGroupData groupSpawnData = null;
int x = xo + random.nextInt(16);
int z = zo + random.nextInt(16);
int startX = x;
int startZ = z;
for (int i = 0; i < count; i++) {
boolean success = false;
for (int attempts = 0; !success && attempts < 4; attempts++) {
BlockPos pos = getTopNonCollidingPos(level, spawnerData.type(), x, z);
if (spawnerData.type().canSummon() && SpawnPlacements.isSpawnPositionOk(spawnerData.type(), level, pos)) {
float width = spawnerData.type().getWidth();
double fx = Mth.clamp(x, (double)xo + width, xo + 16.0 - width);
double fz = Mth.clamp(z, (double)zo + width, zo + 16.0 - width);
if (!level.noCollision(spawnerData.type().getSpawnAABB(fx, pos.getY(), fz))
|| !SpawnPlacements.checkSpawnRules(
spawnerData.type(),
level,
EntitySpawnReason.CHUNK_GENERATION,
BlockPos.containing(fx, pos.getY(), fz),
level.getRandom()
)) {
continue;
}
Entity entity;
try {
entity = spawnerData.type().create(level.getLevel(), EntitySpawnReason.NATURAL);
} catch (Exception e) {
IrisLogging.reportError("Failed to create initial mob at chunk " + chunkPos, e);
continue;
}
if (entity == null) {
continue;
}
entity.snapTo(fx, pos.getY(), fz, random.nextFloat() * 360.0F, 0.0F);
if (entity instanceof Mob mob
&& mob.checkSpawnRules(level, EntitySpawnReason.CHUNK_GENERATION)
&& mob.checkSpawnObstruction(level)) {
groupSpawnData = mob.finalizeSpawn(
level, level.getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.CHUNK_GENERATION, groupSpawnData
);
level.addFreshEntityWithPassengers(mob, CreatureSpawnEvent.SpawnReason.CHUNK_GEN);
success = true;
}
}
x += random.nextInt(5) - random.nextInt(5);
for (z += random.nextInt(5) - random.nextInt(5);
x < xo || x >= xo + 16 || z < zo || z >= zo + 16;
z = startZ + random.nextInt(5) - random.nextInt(5)
) {
x = startX + random.nextInt(5) - random.nextInt(5);
}
}
}
}
}
}
}
}
private static BlockPos getTopNonCollidingPos(LevelReader level, EntityType<?> type, int x, int z) {
int levelHeight = level.getHeight(SpawnPlacements.getHeightmapType(type), x, z);
BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(x, levelHeight, z);
if (level.dimensionType().hasCeiling()) {
do {
pos.move(Direction.DOWN);
} while (!level.getBlockState(pos).isAir());
do {
pos.move(Direction.DOWN);
} while (level.getBlockState(pos).isAir() && pos.getY() > level.getMinY());
}
return SpawnPlacements.getPlacementType(type).adjustSpawnPosition(level, pos.immutable());
}
}
@@ -1,142 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_3_R1;
import ca.spottedleaf.moonrise.common.util.TickThread;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.SectionPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.world.entity.ai.village.poi.PoiRecord;
import net.minecraft.world.entity.ai.village.poi.PoiSection;
import net.minecraft.world.entity.ai.village.poi.PoiType;
import net.minecraft.world.entity.ai.village.poi.PoiTypes;
import net.minecraft.world.level.WorldGenLevel;
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.ImposterProtoChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.ProtoChunk;
import org.bukkit.NamespacedKey;
import org.bukkit.persistence.PersistentDataType;
import java.util.Arrays;
import java.util.Optional;
final class NativeStructurePoiUpdates {
private static final NamespacedKey DIRTY_SECTIONS = new NamespacedKey("iris", "native_poi_sections");
private final int skipPoiFlag;
NativeStructurePoiUpdates(int skipPoiFlag) {
this.skipPoiFlag = skipPoiFlag;
}
static NativeStructurePoiUpdates create() {
try {
return new NativeStructurePoiUpdates(Block.class.getField("UPDATE_SKIP_POI").getInt(null));
} catch (NoSuchFieldException e) {
return null;
} catch (IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
boolean setBlock(WorldGenLevel world, BlockPos position, BlockState state, int flags, int limit) {
if ((flags & skipPoiFlag) != 0) {
return world.setBlock(position, state, flags, limit);
}
ChunkAccess chunk = world.getChunk(position);
if (!(chunk instanceof ProtoChunk)) {
return world.setBlock(position, state, flags, limit);
}
if (!(chunk instanceof ImposterProtoChunk)
&& !PoiTypes.forState(chunk.getBlockState(position)).equals(PoiTypes.forState(state))) {
markSection(chunk, chunk.getSectionIndex(position.getY()));
}
return world.setBlock(position, state, flags | skipPoiFlag, limit);
}
static void reconcile(LevelChunk chunk) {
long[] sections = chunk.persistentDataContainer.get(DIRTY_SECTIONS, PersistentDataType.LONG_ARRAY);
if (sections == null) {
return;
}
validateSections(chunk, sections);
ServerLevel world = (ServerLevel) chunk.getLevel();
TickThread.ensureTickThread(world, chunk.getPos(), "Native structure POIs require the chunk owning thread");
PoiManager manager = world.getPoiManager();
for (int word = 0; word < sections.length; word++) {
long remaining = sections[word];
while (remaining != 0) {
int index = (word << 6) + Long.numberOfTrailingZeros(remaining);
reconcileSection(chunk, manager, index);
remaining &= remaining - 1;
}
}
chunk.persistentDataContainer.remove(DIRTY_SECTIONS);
chunk.markUnsaved();
}
private static void markSection(ChunkAccess chunk, int index) {
long[] sections = chunk.persistentDataContainer.get(DIRTY_SECTIONS, PersistentDataType.LONG_ARRAY);
int word = index >>> 6;
long bit = 1L << (index & 63);
if (sections == null) {
sections = new long[word + 1];
} else {
validateSections(chunk, sections);
if (word < sections.length && (sections[word] & bit) != 0) {
return;
}
if (word >= sections.length) {
sections = Arrays.copyOf(sections, word + 1);
}
}
sections[word] |= bit;
chunk.persistentDataContainer.set(DIRTY_SECTIONS, PersistentDataType.LONG_ARRAY, sections);
}
private static void validateSections(ChunkAccess chunk, long[] sections) {
int count = chunk.getSectionsCount();
int words = (count + 63) >>> 6;
if (sections.length > words
|| sections.length == words && (count & 63) != 0
&& (sections[words - 1] >>> (count & 63)) != 0) {
throw new IllegalArgumentException("Native POI repair sections exceed chunk height at " + chunk.getPos());
}
}
private static void reconcileSection(LevelChunk chunk, PoiManager manager, int index) {
SectionPos sectionPosition = SectionPos.of(chunk.getPos(), chunk.getSectionYFromSectionIndex(index));
Optional<PoiSection> existing = manager.getOrLoad(sectionPosition.asLong());
if (existing.isPresent()) {
for (PoiRecord record : existing.get().getRecords(type -> true, PoiManager.Occupancy.ANY).toList()) {
if (!PoiTypes.forState(chunk.getBlockState(record.getPos())).equals(Optional.of(record.getPoiType()))) {
manager.remove(record.getPos());
}
}
}
LevelChunkSection section = chunk.getSection(index);
if (!section.maybeHas(PoiTypes::hasPoi)) {
return;
}
BlockPos.MutableBlockPos position = new BlockPos.MutableBlockPos();
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
Optional<Holder<PoiType>> type = PoiTypes.forState(section.getBlockState(x, y, z));
if (type.isEmpty()) {
continue;
}
position.set(sectionPosition.minBlockX() + x, sectionPosition.minBlockY() + y,
sectionPosition.minBlockZ() + z);
if (!manager.getType(position).equals(type)) {
manager.add(position.immutable(), type.get());
}
}
}
}
}
}
@@ -1,606 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_3_R1;
import art.arcane.iris.nativegen.v26_3_R1.WorldgenTerrainHeightmaps;
import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.QuartPos;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.attribute.EnvironmentAttributeReader;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.EmptyLevelChunk;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.ticks.LevelTickAccess;
import net.minecraft.world.ticks.ScheduledTick;
import org.bukkit.event.entity.CreatureSpawnEvent;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.IntBinaryOperator;
import java.util.function.Predicate;
import java.util.function.Supplier;
final class NativeStructureWorldgenAccess implements WorldGenLevel {
private static final int WRITE_RADIUS = 1;
private static final NativeStructurePoiUpdates POI_UPDATES = NativeStructurePoiUpdates.create();
private final WorldGenLevel delegate;
private final ChunkPos generationCenter;
private final IntBinaryOperator surfaceFirstFreeY;
private final IntBinaryOperator floorFirstFreeY;
private final boolean virtualizeHeights;
private final Predicate<BlockPos> protectedPosition;
private final Holder<Biome> fallbackBiome;
private final BiomeManager biomeManager;
private final AABB generationBounds;
private final LevelTickAccess<Block> blockTicks;
private final LevelTickAccess<Fluid> fluidTicks;
private final Long2LongOpenHashMap terrainHeights;
private final Long2ObjectOpenHashMap<ChunkAccess> outsideChunks;
private NativeStructureWorldgenAccess(WorldGenLevel delegate, Boundary boundary) {
this.delegate = Objects.requireNonNull(delegate, "Native structure world access requires a delegate");
this.generationCenter = Objects.requireNonNull(
boundary.generationCenter(), "Native structure world access requires a generation center");
this.surfaceFirstFreeY = Objects.requireNonNull(
boundary.surfaceFirstFreeY(), "Native structure world access requires a surface resolver");
this.floorFirstFreeY = Objects.requireNonNull(
boundary.floorFirstFreeY(), "Native structure world access requires an ocean-floor resolver");
this.virtualizeHeights = boundary.virtualizeHeights();
this.protectedPosition = Objects.requireNonNull(
boundary.protectedPosition(), "World access requires a protected-position predicate");
BlockPos biomeSample = generationCenter.getMiddleBlockPosition(delegate.getSeaLevel());
this.fallbackBiome = delegate.getBiome(biomeSample);
this.biomeManager = delegate.getBiomeManager().withDifferentSource(this);
this.generationBounds = new AABB(
generationCenter.getMinBlockX() - 16,
delegate.getMinY(),
generationCenter.getMinBlockZ() - 16,
generationCenter.getMaxBlockX() + 17,
delegate.getMinY() + delegate.getHeight(),
generationCenter.getMaxBlockZ() + 17);
this.blockTicks = new BoundedTickAccess<>(delegate.getBlockTicks(), this::isWritable);
this.fluidTicks = new BoundedTickAccess<>(delegate.getFluidTicks(), this::isWritable);
this.terrainHeights = new Long2LongOpenHashMap();
this.terrainHeights.defaultReturnValue(Long.MIN_VALUE);
this.outsideChunks = new Long2ObjectOpenHashMap<>();
}
static NativeStructureWorldgenAccess create(WorldGenLevel delegate, ChunkPos generationCenter,
IntBinaryOperator surfaceFirstFreeY,
IntBinaryOperator floorFirstFreeY,
boolean virtualizeHeights,
Predicate<BlockPos> protectedPosition) {
return new NativeStructureWorldgenAccess(delegate, new Boundary(
generationCenter, surfaceFirstFreeY, floorFirstFreeY, virtualizeHeights, protectedPosition));
}
@Override
public long getSeed() {
return delegate.getSeed();
}
@Override
public boolean ensureCanWrite(BlockPos position) {
return isWritable(position) && delegate.ensureCanWrite(position);
}
@Override
public void setCurrentlyGenerating(Supplier<String> description) {
delegate.setCurrentlyGenerating(description);
}
@Override
public ServerLevel getLevel() {
return delegate.getLevel();
}
@Override
public DifficultyInstance getCurrentDifficultyAt(BlockPos position) {
if (isReadable(position)) {
return delegate.getCurrentDifficultyAt(position);
}
return delegate.getCurrentDifficultyAt(generationCenter.getMiddleBlockPosition(
Mth.clamp(position.getY(), getMinY(), getMaxY() - 1)));
}
@Override
public long nextSubTickCount() {
return delegate.nextSubTickCount();
}
@Override
public LevelTickAccess<Block> getBlockTicks() {
return blockTicks;
}
@Override
public LevelTickAccess<Fluid> getFluidTicks() {
return fluidTicks;
}
@Override
public LevelData getLevelData() {
return delegate.getLevelData();
}
@Override
public MinecraftServer getServer() {
return delegate.getServer();
}
@Override
public ChunkSource getChunkSource() {
return delegate.getChunkSource();
}
@Override
public RandomSource getRandom() {
return delegate.getRandom();
}
@Override
public void updateNeighborsAt(BlockPos position, Block block) {
if (isWritableNeighbourhood(position)) {
delegate.updateNeighborsAt(position, block);
}
}
@Override
public void neighborShapeChanged(Direction direction, BlockPos position,
BlockPos neighbourPosition, BlockState neighbourState,
int updateFlags, int updateLimit) {
if (isWritable(position) && isWritable(neighbourPosition)) {
delegate.neighborShapeChanged(
direction, position, neighbourPosition, neighbourState, updateFlags, updateLimit);
}
}
@Override
public void playSound(Entity source, BlockPos position, SoundEvent sound,
SoundSource soundSource, float volume, float pitch) {
if (isWritable(position)) {
delegate.playSound(source, position, sound, soundSource, volume, pitch);
}
}
@Override
public void addParticle(ParticleOptions particle, double x, double y, double z,
double velocityX, double velocityY, double velocityZ) {
if (isWritable(BlockPos.containing(x, y, z))) {
delegate.addParticle(particle, x, y, z, velocityX, velocityY, velocityZ);
}
}
@Override
public void levelEvent(Entity source, int eventId, BlockPos position, int data) {
if (isWritable(position)) {
delegate.levelEvent(source, eventId, position, data);
}
}
@Override
public void gameEvent(Holder<GameEvent> event, Vec3 position, GameEvent.Context context) {
if (isWritable(BlockPos.containing(position))) {
delegate.gameEvent(event, position, context);
}
}
@Override
public ChunkAccess getChunk(int chunkX, int chunkZ) {
if (isInsideGenerationRegion(chunkX, chunkZ)) {
return delegate.getChunk(chunkX, chunkZ);
}
return outsideChunk(chunkX, chunkZ);
}
@Override
public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus status, boolean create) {
if (isInsideGenerationRegion(chunkX, chunkZ)) {
return delegate.getChunk(chunkX, chunkZ, status, create);
}
return create ? outsideChunk(chunkX, chunkZ) : null;
}
@Override
public ChunkAccess getChunkIfLoadedImmediately(int chunkX, int chunkZ) {
if (!isInsideGenerationRegion(chunkX, chunkZ)) {
return null;
}
return delegate.getChunkIfLoadedImmediately(chunkX, chunkZ);
}
@Override
public boolean hasChunk(int chunkX, int chunkZ) {
return isInsideGenerationRegion(chunkX, chunkZ) && delegate.hasChunk(chunkX, chunkZ);
}
@Override
public int getHeight(Heightmap.Types type, int x, int z) {
if (!virtualizeHeights && isInsideGenerationRegion(x >> 4, z >> 4)) {
return delegate.getHeight(type, x, z);
}
return height(type, x, z);
}
@Override
public int getSkyDarken() {
return delegate.getSkyDarken();
}
@Override
public BiomeManager getBiomeManager() {
return biomeManager;
}
@Override
public Holder<Biome> getNoiseBiome(int quartX, int quartY, int quartZ) {
return getUncachedNoiseBiome(quartX, quartY, quartZ);
}
@Override
public Holder<Biome> getUncachedNoiseBiome(int quartX, int quartY, int quartZ) {
if (isInsideGenerationRegion(QuartPos.toSection(quartX), QuartPos.toSection(quartZ))) {
return delegate.getUncachedNoiseBiome(quartX, quartY, quartZ);
}
return fallbackBiome;
}
@Override
public boolean isClientSide() {
return delegate.isClientSide();
}
@Override
public int getSeaLevel() {
return delegate.getSeaLevel();
}
@Override
public DimensionType dimensionType() {
return delegate.dimensionType();
}
@Override
public int getMinY() {
return delegate.getMinY();
}
@Override
public int getHeight() {
return delegate.getHeight();
}
@Override
public RegistryAccess registryAccess() {
return delegate.registryAccess();
}
@Override
public FeatureFlagSet enabledFeatures() {
return delegate.enabledFeatures();
}
@Override
public EnvironmentAttributeReader environmentAttributes() {
return delegate.environmentAttributes();
}
@Override
public LevelLightEngine getLightEngine() {
return delegate.getLightEngine();
}
@Override
public int getBrightness(LightLayer layer, BlockPos position) {
if (isReadable(position)) {
return delegate.getBrightness(layer, position);
}
return layer == LightLayer.SKY && canSeeSky(position) ? 15 : 0;
}
@Override
public int getRawBrightness(BlockPos position, int ambientDarkening) {
if (isReadable(position)) {
return delegate.getRawBrightness(position, ambientDarkening);
}
return Math.max(0, getBrightness(LightLayer.SKY, position) - ambientDarkening);
}
@Override
public boolean canSeeSky(BlockPos position) {
if (isReadable(position)) {
return delegate.canSeeSky(position);
}
return position.getY() >= height(Heightmap.Types.WORLD_SURFACE_WG, position.getX(), position.getZ());
}
@Override
public WorldBorder getWorldBorder() {
return delegate.getWorldBorder();
}
@Override
public BlockGetter getChunkForCollisions(int chunkX, int chunkZ) {
if (isInsideGenerationRegion(chunkX, chunkZ)) {
return delegate.getChunkForCollisions(chunkX, chunkZ);
}
return outsideChunk(chunkX, chunkZ);
}
@Override
public BlockEntity getBlockEntity(BlockPos position) {
return isWritable(position) ? delegate.getBlockEntity(position) : null;
}
@Override
public <T extends BlockEntity> Optional<T> getBlockEntity(
BlockPos position, BlockEntityType<T> type) {
return isWritable(position) ? delegate.getBlockEntity(position, type) : Optional.empty();
}
@Override
public BlockState getBlockState(BlockPos position) {
return isReadable(position) ? delegate.getBlockState(position) : terrainState(position);
}
@Override
public BlockState getBlockStateIfLoaded(BlockPos position) {
return isReadable(position) ? delegate.getBlockStateIfLoaded(position) : null;
}
@Override
public FluidState getFluidState(BlockPos position) {
return isReadable(position) ? delegate.getFluidState(position) : terrainState(position).getFluidState();
}
@Override
public FluidState getFluidIfLoaded(BlockPos position) {
return isReadable(position) ? delegate.getFluidIfLoaded(position) : null;
}
@Override
public List<Entity> getEntities(Entity source, AABB area, Predicate<? super Entity> predicate) {
AABB boundedArea = boundedArea(area);
return boundedArea == null ? List.of() : delegate.getEntities(source, boundedArea, predicate);
}
@Override
public <T extends Entity> List<T> getEntities(
EntityTypeTest<Entity, T> type,
AABB area, Predicate<? super T> predicate) {
AABB boundedArea = boundedArea(area);
return boundedArea == null ? List.of() : delegate.getEntities(type, boundedArea, predicate);
}
@Override
public List<? extends Player> players() {
return delegate.players();
}
@Override
public boolean isStateAtPosition(BlockPos position, Predicate<BlockState> predicate) {
return predicate.test(getBlockState(position));
}
@Override
public boolean isFluidAtPosition(BlockPos position, Predicate<FluidState> predicate) {
return predicate.test(getFluidState(position));
}
@Override
public BlockPos getHeightmapPos(Heightmap.Types type, BlockPos position) {
return position.atY(getHeight(type, position.getX(), position.getZ()));
}
@Override
public boolean setBlock(BlockPos position, BlockState state, int updateFlags, int updateLimit) {
return isWritable(position)
&& (POI_UPDATES != null
? POI_UPDATES.setBlock(delegate, position, state, updateFlags, updateLimit)
: delegate.setBlock(position, state, updateFlags, updateLimit));
}
@Override
public boolean removeBlock(BlockPos position, boolean move) {
if (!isWritable(position)) {
return false;
}
return POI_UPDATES != null && delegate instanceof WorldGenRegion
? setBlock(position, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL, Block.UPDATE_LIMIT)
: delegate.removeBlock(position, move);
}
@Override
public boolean destroyBlock(BlockPos position, boolean drop, Entity source, int updateLimit) {
if (!isWritable(position)) {
return false;
}
return POI_UPDATES != null && delegate instanceof WorldGenRegion
? !delegate.getBlockState(position).isAir()
&& setBlock(position, Blocks.AIR.defaultBlockState(), Block.UPDATE_ALL, updateLimit)
: delegate.destroyBlock(position, drop, source, updateLimit);
}
@Override
public boolean addFreshEntity(Entity entity) {
return isWritable(entity.blockPosition()) && delegate.addFreshEntity(entity);
}
@Override
public boolean addFreshEntity(Entity entity, CreatureSpawnEvent.SpawnReason reason) {
return isWritable(entity.blockPosition()) && delegate.addFreshEntity(entity, reason);
}
private boolean isReadable(BlockPos position) {
return isInsideGenerationRegion(position.getX() >> 4, position.getZ() >> 4)
&& !isOutsideBuildHeight(position);
}
private boolean isWritable(BlockPos position) {
return isReadable(position) && !protectedPosition.test(position);
}
private boolean isWritableNeighbourhood(BlockPos position) {
return isWritable(position)
&& isWritable(position.north())
&& isWritable(position.south())
&& isWritable(position.east())
&& isWritable(position.west())
&& isWritable(position.above())
&& isWritable(position.below());
}
private AABB boundedArea(AABB area) {
double minX = Math.max(area.minX, generationBounds.minX);
double minY = Math.max(area.minY, generationBounds.minY);
double minZ = Math.max(area.minZ, generationBounds.minZ);
double maxX = Math.min(area.maxX, generationBounds.maxX);
double maxY = Math.min(area.maxY, generationBounds.maxY);
double maxZ = Math.min(area.maxZ, generationBounds.maxZ);
if (minX >= maxX || minY >= maxY || minZ >= maxZ) {
return null;
}
if (minX == area.minX && minY == area.minY && minZ == area.minZ
&& maxX == area.maxX && maxY == area.maxY && maxZ == area.maxZ) {
return area;
}
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
boolean isInsideGenerationRegion(int chunkX, int chunkZ) {
return Math.abs(chunkX - generationCenter.x()) <= WRITE_RADIUS
&& Math.abs(chunkZ - generationCenter.z()) <= WRITE_RADIUS;
}
@Override
public boolean isOutsideBuildHeight(BlockPos position) {
return position.getY() < getMinY() || position.getY() >= getMaxY();
}
private BlockState terrainState(BlockPos position) {
if (isOutsideBuildHeight(position)) {
return Blocks.VOID_AIR.defaultBlockState();
}
long heights = heights(position.getX(), position.getZ());
int surface = (int) (heights >> 32);
int floor = (int) heights;
if (position.getY() < floor) {
return Blocks.STONE.defaultBlockState();
}
if (position.getY() < surface) {
return Blocks.WATER.defaultBlockState();
}
return Blocks.AIR.defaultBlockState();
}
private int height(Heightmap.Types type, int x, int z) {
long heights = heights(x, z);
if (type == Heightmap.Types.OCEAN_FLOOR || type == Heightmap.Types.OCEAN_FLOOR_WG) {
return (int) heights;
}
return (int) (heights >> 32);
}
private long heights(int x, int z) {
long key = ((long) x << 32) ^ (z & 0xffffffffL);
long cached = terrainHeights.get(key);
if (cached != Long.MIN_VALUE) {
return cached;
}
int floor = Mth.clamp(floorFirstFreeY.applyAsInt(x, z), getMinY(), getMaxY());
int surface = Mth.clamp(surfaceFirstFreeY.applyAsInt(x, z), floor, getMaxY());
long heights = ((long) surface << 32) | (floor & 0xffffffffL);
terrainHeights.put(key, heights);
return heights;
}
private ChunkAccess outsideChunk(int chunkX, int chunkZ) {
long key = ChunkPos.pack(chunkX, chunkZ);
ChunkAccess cached = outsideChunks.get(key);
if (cached != null) {
return cached;
}
EmptyLevelChunk chunk = new EmptyLevelChunk(getLevel(), new ChunkPos(chunkX, chunkZ), fallbackBiome);
WorldgenTerrainHeightmaps.primeTerrain(chunk, surfaceFirstFreeY, floorFirstFreeY);
outsideChunks.put(key, chunk);
return chunk;
}
private record Boundary(ChunkPos generationCenter,
IntBinaryOperator surfaceFirstFreeY,
IntBinaryOperator floorFirstFreeY,
boolean virtualizeHeights,
Predicate<BlockPos> protectedPosition) {
}
private static final class BoundedTickAccess<T> implements LevelTickAccess<T> {
private final LevelTickAccess<T> delegate;
private final Predicate<BlockPos> writable;
private BoundedTickAccess(LevelTickAccess<T> delegate, Predicate<BlockPos> writable) {
this.delegate = delegate;
this.writable = writable;
}
@Override
public void schedule(ScheduledTick<T> tick) {
if (writable.test(tick.pos())) {
delegate.schedule(tick);
}
}
@Override
public boolean hasScheduledTick(BlockPos position, T type) {
return writable.test(position) && delegate.hasScheduledTick(position, type);
}
@Override
public int count() {
return delegate.count();
}
@Override
public boolean willTickThisTick(BlockPos position, T type) {
return writable.test(position) && delegate.willTickThisTick(position, type);
}
}
}
@@ -1,61 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_3_R1;
import art.arcane.iris.platform.agent.Agent;
import it.unimi.dsi.fastutil.shorts.ShortList;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.ChunkPos;
import java.util.function.LongPredicate;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import java.util.List;
final class NmsGenerationHooks {
private NmsGenerationHooks() {
}
static void install() {
ByteBuddy buddy = new ByteBuddy();
for (Class<?> clazz : List.of(ChunkAccess.class, ProtoChunk.class)) {
buddy.redefine(clazz)
.visit(Advice.to(ChunkAccessAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(ShortList.class, int.class))))
.make()
.load(clazz.getClassLoader(), Agent.installed());
}
buddy.redefine(WorldGenRegion.class)
.visit(Advice.to(WorldGenerationWriteAdvice.class).on(ElementMatchers.named("ensureCanWrite")
.and(ElementMatchers.takesArguments(BlockPos.class))))
.make()
.load(WorldGenRegion.class.getClassLoader(), Agent.installed());
}
private static class WorldGenerationWriteAdvice {
@Advice.OnMethodExit
static void exit(@Advice.This WorldGenRegion region, @Advice.Argument(0) BlockPos position,
@Advice.Return(readOnly = false) boolean allowed) {
if (!allowed) {
return;
}
ChunkGenerator generator = region.getLevel().getChunkSource().getGenerator();
if (generator instanceof LongPredicate predicate
&& generator.getClass().getName().equals("art.arcane.iris.platform.bukkit.nms.v26_3_R1.IrisChunkGenerator")
&& region.getChunk(position.getX() >> 4, position.getZ() >> 4)
.getPersistedStatus().isOrAfter(ChunkStatus.FULL)) {
allowed = predicate.test(ChunkPos.pack(position.getX() >> 4, position.getZ() >> 4));
}
}
}
private static class ChunkAccessAdvice {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean enter(@Advice.This ChunkAccess access, @Advice.Argument(1) int index) {
return index >= access.getPostProcessing().length;
}
}
}
@@ -1,182 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_3_R1;
import art.arcane.iris.spi.PlatformGenerationRegistry;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.dimension.DimensionType;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Supplier;
final class NmsGenerationRegistry implements PlatformGenerationRegistry {
private static final String BIOME_REGISTRY = "minecraft:worldgen/biome";
private static final String STRUCTURE_REGISTRY = "minecraft:worldgen/structure";
private static final String DIMENSION_TYPE_REGISTRY = "minecraft:dimension_type";
private static final String BLOCK_REGISTRY = "minecraft:block";
private static final String ENTITY_TYPE_REGISTRY = "minecraft:entity_type";
private final Supplier<RegistryAccess> registryAccess;
private final String runtimeIdentity;
private final String rendererIdentity;
NmsGenerationRegistry(
Supplier<RegistryAccess> registryAccess,
String runtimeIdentity,
String rendererIdentity
) {
this.registryAccess = Objects.requireNonNull(registryAccess, "registryAccess");
this.runtimeIdentity = requireText(runtimeIdentity, "runtimeIdentity");
this.rendererIdentity = requireText(rendererIdentity, "rendererIdentity");
}
@Override
public String runtimeIdentity() {
return runtimeIdentity;
}
@Override
public String generatedDefinitionRendererIdentity() {
return rendererIdentity;
}
@Override
public String customBiomeResourceKey(String identitySha256) {
return PlatformGenerationRegistry.contentAddressedCustomBiomeResourceKey(identitySha256);
}
@Override
public List<String> legacyCustomBiomeResourceKeys(
String packName,
String dimensionKey,
String customBiomeId
) {
return List.of(requireText(dimensionKey, "dimensionKey").toLowerCase(Locale.ROOT)
+ ":" + requireText(customBiomeId, "customBiomeId").toLowerCase(Locale.ROOT));
}
@Override
public String dimensionTypeResourceKey(String packName, String dimensionKey, String dimensionTypeKey) {
return "iris:" + requireText(dimensionTypeKey, "dimensionTypeKey").toLowerCase(Locale.ROOT);
}
@Override
public Definition definition(String registryKey, String resourceKey) {
RegistryAccess access = Objects.requireNonNull(registryAccess.get(), "Minecraft registry access");
return switch (registryKey) {
case BIOME_REGISTRY -> presence(access, Registries.BIOME, registryKey, resourceKey);
case STRUCTURE_REGISTRY -> presence(access, Registries.STRUCTURE, registryKey, resourceKey);
case DIMENSION_TYPE_REGISTRY -> presence(
access,
Registries.DIMENSION_TYPE,
registryKey,
resourceKey
);
case BLOCK_REGISTRY -> presence(access, Registries.BLOCK, registryKey, resourceKey);
case ENTITY_TYPE_REGISTRY -> presence(access, Registries.ENTITY_TYPE, registryKey, resourceKey);
default -> throw new IllegalArgumentException("Unsupported generation registry: " + registryKey);
};
}
@Override
public Definition canonicalDefinition(String registryKey, String resourceKey, String sourceJson) {
RegistryAccess access = Objects.requireNonNull(registryAccess.get(), "Minecraft registry access");
return switch (registryKey) {
case BIOME_REGISTRY -> canonicalize(access, Biome.DIRECT_CODEC, sourceJson);
case DIMENSION_TYPE_REGISTRY -> canonicalize(access, DimensionType.DIRECT_CODEC, sourceJson);
default -> throw new IllegalArgumentException(
"Registry does not accept generated JSON definitions: " + registryKey
);
};
}
@Override
public Definition generatedDefinition(String registryKey, String resourceKey) {
RegistryAccess access = Objects.requireNonNull(registryAccess.get(), "Minecraft registry access");
return switch (registryKey) {
case BIOME_REGISTRY -> exactRegistered(
access,
Registries.BIOME,
Biome.DIRECT_CODEC,
resourceKey
);
case DIMENSION_TYPE_REGISTRY -> exactRegistered(
access,
Registries.DIMENSION_TYPE,
DimensionType.DIRECT_CODEC,
resourceKey
);
default -> throw new IllegalArgumentException(
"Registry does not contain Iris-generated definitions: " + registryKey
);
};
}
private static <T> Definition presence(
RegistryAccess access,
ResourceKey<? extends Registry<T>> registryKey,
String registryResourceKey,
String resourceKey
) {
Registry<T> registry = access.lookupOrThrow(registryKey);
Identifier identifier = requireIdentifier(resourceKey);
T value = registry.getValue(identifier);
if (value == null) {
throw new IllegalStateException("Missing registry definition " + registryKey.identifier()
+ " / " + resourceKey + ".");
}
return Definition.resourceIdentity(registryResourceKey, resourceKey);
}
private static <T> Definition canonicalize(RegistryAccess access, Codec<T> codec, String sourceJson) {
RegistryOps<JsonElement> ops = RegistryOps.create(JsonOps.INSTANCE, access);
JsonElement source = JsonParser.parseString(Objects.requireNonNull(sourceJson, "sourceJson"));
T decoded = codec.parse(ops, source).getOrThrow(IllegalArgumentException::new);
JsonElement encoded = codec.encodeStart(ops, decoded).getOrThrow(IllegalStateException::new);
return Definition.exactJson(encoded.toString());
}
private static <T> Definition exactRegistered(
RegistryAccess access,
ResourceKey<? extends Registry<T>> registryKey,
Codec<T> codec,
String resourceKey
) {
Registry<T> registry = access.lookupOrThrow(registryKey);
Identifier identifier = requireIdentifier(resourceKey);
T value = registry.getValue(identifier);
if (value == null) {
throw new IllegalStateException("Missing generated registry definition "
+ registryKey.identifier() + " / " + resourceKey + ".");
}
RegistryOps<JsonElement> ops = RegistryOps.create(JsonOps.INSTANCE, access);
JsonElement encoded = codec.encodeStart(ops, value).getOrThrow(IllegalStateException::new);
return Definition.exactJson(encoded.toString());
}
private static Identifier requireIdentifier(String resourceKey) {
Identifier identifier = Identifier.tryParse(resourceKey);
if (identifier == null) {
throw new IllegalArgumentException("Invalid registry resource key: " + resourceKey);
}
return identifier;
}
private static String requireText(String value, String label) {
String required = Objects.requireNonNull(value, label).trim();
if (required.isEmpty()) {
throw new IllegalArgumentException(label + " must not be blank.");
}
return required;
}
}
@@ -1,266 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_3_R1;
import art.arcane.iris.world.history.NativeTerrainReceipt;
import art.arcane.iris.world.history.SavedTerrainChunk;
import art.arcane.iris.world.task.J;
import ca.spottedleaf.concurrentutil.completable.Completable;
import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
import ca.spottedleaf.concurrentutil.util.Priority;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import org.bukkit.World;
import org.bukkit.craftbukkit.CraftWorld;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.LockSupport;
final class NmsSavedTerrainCapture {
private static final long WRITE_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(30L);
private static final int SNAPSHOT_BATCH_SIZE = 64;
private NmsSavedTerrainCapture() {
}
static CompletableFuture<SavedTerrainChunk> capture(World world, int chunkX, int chunkZ, int minimumY, int height) {
ServerLevel level = ((CraftWorld) world).getHandle();
CompletableFuture<CapturedChunk> snapshot = new CompletableFuture<>();
J.runRegionFuture(world, chunkX, chunkZ, () -> {
try {
NewChunkHolder holder = level.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunkX, chunkZ);
ChunkAccess chunk = holder == null ? null : holder.getCurrentChunk();
if (chunk == null) {
snapshot.complete(new CapturedChunk(new ChunkPos(chunkX, chunkZ), CompletableFuture.completedFuture(null)));
return;
}
String status = BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getPersistedStatus()).toString();
if (!SavedTerrainChunk.hasTerrain(status)) {
throw new IOException("Native chunk " + chunkX + "," + chunkZ + " has no completed terrain at " + status);
}
snapshot.complete(scheduleSnapshot(level, chunk));
} catch (Throwable failure) {
snapshot.completeExceptionally(failure);
}
}).whenComplete((ignored, failure) -> {
if (failure != null) {
snapshot.completeExceptionally(failure);
}
});
return snapshot.thenApplyAsync(captured -> {
try {
long deadline = System.nanoTime() + WRITE_TIMEOUT_NANOS;
CompoundTag data = awaitSnapshot(level, captured, deadline);
Checkpoint checkpoint = checkpoint(chunkX, chunkZ, data);
awaitWrite(level, checkpoint.chunk(), deadline);
if (data == null) {
return SavedTerrainChunk.read(world.getWorldFolder().toPath(), chunkX, chunkZ, minimumY, height);
}
verifyCheckpoint(world.getWorldFolder().toPath(), checkpoint);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (DataOutputStream output = new DataOutputStream(bytes)) {
NbtIo.write(data, output);
}
return SavedTerrainChunk.readNbt(bytes.toByteArray(), chunkX, chunkZ, minimumY, height);
} catch (IOException failure) {
throw new CompletionException(failure);
}
});
}
static CompletableFuture<Void> flush(World world) {
ServerLevel level = ((CraftWorld) world).getHandle();
List<NewChunkHolder> holders = level.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolders();
return CompletableFuture.runAsync(() -> {
try {
flushSnapshots(world, level, holders);
} catch (IOException failure) {
throw new CompletionException(failure);
}
});
}
static void flushSnapshots(World world, ServerLevel level, List<NewChunkHolder> holders) throws IOException {
long deadline = System.nanoTime() + WRITE_TIMEOUT_NANOS;
List<Checkpoint> checkpoints = new ArrayList<>(holders.size());
for (int offset = 0; offset < holders.size(); offset += SNAPSHOT_BATCH_SIZE) {
int end = Math.min(holders.size(), offset + SNAPSHOT_BATCH_SIZE);
List<CompletableFuture<CapturedChunk>> snapshots = requestSnapshots(world, level, holders.subList(offset, end));
awaitSnapshots(snapshots, deadline);
for (CompletableFuture<CapturedChunk> future : snapshots) {
CapturedChunk captured = future.join();
CompoundTag data = awaitSnapshot(level, captured, deadline);
checkpoints.add(checkpoint(captured.chunk().x(), captured.chunk().z(), data));
}
for (int index = offset; index < end; index++) {
awaitWrite(level, checkpoints.get(index).chunk(), deadline);
}
}
finishCheckpoint(level, world.getWorldFolder().toPath(), checkpoints);
}
private static List<CompletableFuture<CapturedChunk>> requestSnapshots(
World world, ServerLevel level, List<NewChunkHolder> holders) {
List<CompletableFuture<CapturedChunk>> snapshots = new ArrayList<>(holders.size());
for (NewChunkHolder original : holders) {
ChunkPos position = new ChunkPos(original.chunkX, original.chunkZ);
CompletableFuture<CapturedChunk> snapshot = new CompletableFuture<>();
snapshots.add(snapshot);
J.runRegionFuture(world, position.x(), position.z(), () -> {
try {
NewChunkHolder holder = level.moonrise$getChunkTaskScheduler().chunkHolderManager
.getChunkHolder(position.x(), position.z());
ChunkAccess chunk = holder == null ? null : holder.getCurrentChunk();
snapshot.complete(chunk != null && chunk.isUnsaved() ? scheduleSnapshot(level, chunk)
: new CapturedChunk(position, CompletableFuture.completedFuture(null)));
} catch (Throwable failure) {
snapshot.completeExceptionally(failure);
}
}).whenComplete((ignored, failure) -> {
if (failure != null) {
snapshot.completeExceptionally(failure);
}
});
}
return snapshots;
}
private static void awaitSnapshots(List<CompletableFuture<CapturedChunk>> snapshots, long deadlineNanos)
throws IOException {
try {
CompletableFuture.allOf(snapshots.toArray(CompletableFuture[]::new))
.get(Math.max(0L, deadlineNanos - System.nanoTime()), TimeUnit.NANOSECONDS);
} catch (InterruptedException failure) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted while copying terrain checkpoint chunks.", failure);
} catch (ExecutionException | TimeoutException failure) {
throw new IOException("Unable to copy terrain checkpoint chunks.", failure);
}
}
static void finishCheckpoint(ServerLevel level, Path worldFolder, List<Checkpoint> checkpoints) throws IOException {
long deadline = System.nanoTime() + WRITE_TIMEOUT_NANOS;
for (Checkpoint checkpoint : checkpoints) {
awaitWrite(level, checkpoint.chunk(), deadline);
}
MoonriseRegionFileIO.flushRegionStorages(level, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA);
for (Checkpoint checkpoint : checkpoints) {
verifyCheckpoint(worldFolder, checkpoint);
}
}
static Checkpoint checkpoint(int chunkX, int chunkZ, CompoundTag data) {
if (data == null) {
return new Checkpoint(new ChunkPos(chunkX, chunkZ), null);
}
byte[] receipt = data.getCompound("ChunkBukkitValues")
.flatMap(values -> values.getByteArray(NativeTerrainReceipt.NBT_KEY)).orElse(null);
long structureActivation = data.getCompound("ChunkBukkitValues").map(values -> values.getLongOr(
NativeTerrainReceipt.STRUCTURE_ACTIVATION_KEY, 0)).orElse(0L);
return new Checkpoint(new ChunkPos(chunkX, chunkZ), new Verification(
data.getStringOr("Status", ""), receipt, structureActivation));
}
private static void verifyCheckpoint(Path worldFolder, Checkpoint checkpoint) throws IOException {
Verification expected = checkpoint.verification();
if (expected == null) {
return;
}
SavedTerrainChunk.verifyCheckpoint(worldFolder, checkpoint.chunk().x(), checkpoint.chunk().z(),
expected.status(), expected.receipt(), expected.structureActivation());
}
static void awaitWrite(ServerLevel level, ChunkPos chunk, long deadlineNanos) throws IOException {
ChunkTaskScheduler scheduler = level.moonrise$getChunkTaskScheduler();
while (true) {
checkWriteWait(chunk, deadlineNanos);
if (MoonriseRegionFileIO.getPriority(level, chunk.x(), chunk.z(),
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA) == Priority.COMPLETING) {
return;
}
helpSaveTasks(scheduler);
}
}
static CapturedChunk scheduleSnapshot(ServerLevel level, ChunkAccess chunk) {
SerializableChunkData snapshot = SerializableChunkData.copyOf(level, chunk);
PlatformHooks.get().chunkSyncSave(level, chunk, snapshot);
Completable<CompoundTag> nativeData = new Completable<>();
CompletableFuture<CompoundTag> data = new CompletableFuture<>();
PrioritisedExecutor.PrioritisedTask task = level.moonrise$getChunkTaskScheduler().saveExecutor
.createTask(() -> serializeSnapshot(snapshot, nativeData, data));
task.queue();
ChunkPos position = chunk.getPos();
MoonriseRegionFileIO.scheduleSave(level, position.x(), position.z(), nativeData, task,
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, Priority.NORMAL);
return new CapturedChunk(position, data);
}
static CompoundTag awaitSnapshot(ServerLevel level, CapturedChunk captured, long deadlineNanos) throws IOException {
ChunkTaskScheduler scheduler = level.moonrise$getChunkTaskScheduler();
while (!captured.data().isDone()) {
checkWriteWait(captured.chunk(), deadlineNanos);
helpSaveTasks(scheduler);
}
try {
return captured.data().join();
} catch (CompletionException failure) {
throw new IOException("Unable to serialize terrain checkpoint chunk " + captured.chunk(), failure.getCause());
}
}
private static void serializeSnapshot(SerializableChunkData snapshot, Completable<CompoundTag> nativeData,
CompletableFuture<CompoundTag> data) {
try {
CompoundTag serialized = snapshot.write();
nativeData.complete(serialized);
data.complete(serialized);
} catch (Throwable failure) {
nativeData.completeExceptionally(failure);
data.completeExceptionally(failure);
}
}
private static void checkWriteWait(ChunkPos chunk, long deadlineNanos) throws IOException {
if (Thread.currentThread().isInterrupted()) {
throw new IOException("Interrupted while saving terrain checkpoint chunk " + chunk);
}
if (System.nanoTime() - deadlineNanos >= 0L) {
throw new IOException("Timed out saving terrain checkpoint chunk " + chunk);
}
}
private static void helpSaveTasks(ChunkTaskScheduler scheduler) {
boolean helped = scheduler.saveExecutor.executeTask();
helped |= scheduler.compressionExecutor.executeTask();
if (!helped) {
LockSupport.parkNanos(1_000_000L);
}
}
record CapturedChunk(ChunkPos chunk, CompletableFuture<CompoundTag> data) {
}
record Checkpoint(ChunkPos chunk, Verification verification) {
}
record Verification(String status, byte[] receipt, long structureActivation) {
}
}
@@ -1,403 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_3_R1;
import art.arcane.iris.platform.bukkit.nms.ServerShutdownBoundary;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.localization.C;
import art.arcane.iris.platform.agent.Agent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.ResettableClassFileTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicBoolean;
final class NmsWorldLifecycle {
private final AtomicBoolean injected = new AtomicBoolean();
private volatile ResettableClassFileTransformer levelStorageAccessTransformer;
private volatile ResettableClassFileTransformer serverLevelTransformer;
private volatile ResettableClassFileTransformer pluginClassLoaderTransformer;
private boolean pluginClassLoaderCloseDeferred;
public boolean injectBukkit() {
synchronized (injected) {
if (injected.get()) {
return true;
}
try {
IrisLogging.info("Injecting Bukkit");
Agent.requireClassLoaderCloseDeferral();
Class<?> loaderCloseType = getClass().getClassLoader().getClass()
.getMethod("close").getDeclaringClass();
PluginClassLoaderInjectionListener loaderListener = new PluginClassLoaderInjectionListener();
pluginClassLoaderTransformer = new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(AgentBuilder.RedefinitionStrategy.Listener.ErrorEscalating.FAIL_FAST)
.with(loaderListener)
.type(ElementMatchers.is(loaderCloseType))
.transform((builder, typeDescription, classLoader, module, protectionDomain) ->
builder.visit(Advice.to(PluginClassLoaderCloseAdvice.class)
.on(ElementMatchers.named("close")
.and(ElementMatchers.takesArguments(0))
.and(ElementMatchers.returns(void.class)))))
.installOn(Agent.getInstrumentation());
loaderListener.requireInstalled();
levelStorageAccessTransformer = new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.type(ElementMatchers.is(LevelStorageSource.LevelStorageAccess.class))
.transform((builder, typeDescription, classLoader, module, protectionDomain) ->
builder.visit(Advice.to(LevelStorageAccessAdvice.class).on(ElementMatchers.isConstructor()
.and(ElementMatchers.takesArguments(4))
.and(ElementMatchers.takesArgument(0, LevelStorageSource.class))
.and(ElementMatchers.takesArgument(1, String.class))
.and(ElementMatchers.takesArgument(2, Path.class))
.and(ElementMatchers.takesArgument(3, ResourceKey.class)))))
.installOn(Agent.getInstrumentation());
serverLevelTransformer = new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.type(ElementMatchers.is(ServerLevel.class))
.transform((builder, typeDescription, classLoader, module, protectionDomain) ->
builder.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor()
.and(ElementMatchers.takesArgument(0, MinecraftServer.class))
.and(ElementMatchers.takesArgument(5, LevelStem.class)))))
.installOn(Agent.getInstrumentation());
NmsGenerationHooks.install();
injected.set(true);
return true;
} catch (Throwable e) {
IrisLogging.reportError(C.RED + "Failed to inject Bukkit", e);
ResettableClassFileTransformer partialServerLevel = serverLevelTransformer;
ResettableClassFileTransformer partialStorageAccess = levelStorageAccessTransformer;
ResettableClassFileTransformer partialPluginClassLoader = pluginClassLoaderTransformer;
serverLevelTransformer = null;
levelStorageAccessTransformer = null;
pluginClassLoaderTransformer = null;
for (ResettableClassFileTransformer partial : new ResettableClassFileTransformer[]{
partialServerLevel,
partialStorageAccess,
partialPluginClassLoader
}) {
if (partial == null) {
continue;
}
try {
partial.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);
} catch (Throwable cleanupFailure) {
IrisLogging.reportError("Failed to remove partial Bukkit lifecycle injection", cleanupFailure);
}
}
return false;
}
}
}
public void ensureServerLevelInjection() {
if (!injected.get()) {
throw new IllegalStateException("Iris world lifecycle injection is unavailable. Fix the Java Agent or Code Injection startup failure and restart before creating worlds.");
}
try {
Agent.getInstrumentation().retransformClasses(
LevelStorageSource.LevelStorageAccess.class,
ServerLevel.class
);
} catch (Throwable e) {
throw new IllegalStateException("Failed to re-apply Bukkit world lifecycle injection. World creation was stopped before loading the world.", e);
}
}
public void uninjectBukkit() {
synchronized (injected) {
ResettableClassFileTransformer activeServerLevel = serverLevelTransformer;
ResettableClassFileTransformer activeStorageAccess = levelStorageAccessTransformer;
serverLevelTransformer = null;
levelStorageAccessTransformer = null;
injected.set(false);
for (ResettableClassFileTransformer transformer : new ResettableClassFileTransformer[]{
activeServerLevel,
activeStorageAccess
}) {
if (transformer == null) {
continue;
}
try {
transformer.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);
} catch (Throwable e) {
IrisLogging.reportError(C.RED + "Failed to remove Bukkit world lifecycle injection", e);
}
}
}
}
public boolean isServerStopping() {
return ((CraftServer) Bukkit.getServer()).getServer().hasStopped();
}
public ServerShutdownBoundary createServerShutdownBoundary() {
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
return new ServerShutdownBoundary(
() -> server.hasFullyShutdown,
server.getRunningThread()
);
}
public void deferPluginClassLoaderClose() {
synchronized (injected) {
if (pluginClassLoaderTransformer == null || pluginClassLoaderCloseDeferred) {
return;
}
Agent.retainClassLoader(getClass().getClassLoader());
pluginClassLoaderCloseDeferred = true;
}
}
public void releasePluginClassLoaderClose() {
ResettableClassFileTransformer transformer;
boolean releaseLoader;
synchronized (injected) {
transformer = pluginClassLoaderTransformer;
pluginClassLoaderTransformer = null;
releaseLoader = pluginClassLoaderCloseDeferred;
pluginClassLoaderCloseDeferred = false;
}
try {
if (transformer != null
&& !transformer.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)) {
throw new IllegalStateException("Failed to remove Iris plugin class loader lifecycle injection");
}
} finally {
if (releaseLoader) {
Agent.releaseClassLoader(getClass().getClassLoader());
}
}
}
private static final class PluginClassLoaderInjectionListener extends AgentBuilder.Listener.Adapter {
private volatile boolean transformed;
private volatile Throwable failure;
@Override
public void onTransformation(TypeDescription type, ClassLoader loader, JavaModule module,
boolean loaded, DynamicType dynamicType) {
transformed = true;
}
@Override
public void onError(String typeName, ClassLoader loader, JavaModule module,
boolean loaded, Throwable throwable) {
failure = throwable;
}
private void requireInstalled() {
if (!transformed || failure != null) {
throw new IllegalStateException("Iris plugin class loader lifecycle injection failed", failure);
}
}
}
private static class PluginClassLoaderCloseAdvice {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean enter(@Advice.This ClassLoader loader) throws ReflectiveOperationException {
Class<?> installer = Class.forName(
"art.arcane.iris.platform.agent.Installer", true, ClassLoader.getSystemClassLoader());
return Boolean.TRUE.equals(installer.getMethod("deferClassLoaderClose", ClassLoader.class)
.invoke(null, loader));
}
}
private static class LevelStorageAccessAdvice {
@Advice.OnMethodEnter
static void enter(
@Advice.Argument(1) String levelId,
@Advice.Argument(value = 3, readOnly = false) ResourceKey<LevelStem> dimensionType
) {
if (levelId == null || levelId.isBlank()) {
return;
}
ClassLoader pluginClassLoader;
Class<?> generatorType;
Class<?> stagingType;
try {
Plugin irisPlugin = Bukkit.getPluginManager().getPlugin("Iris");
if (irisPlugin == null) {
return;
}
pluginClassLoader = irisPlugin.getClass().getClassLoader();
generatorType = Class.forName("art.arcane.iris.platform.generation.PlatformChunkGenerator", true, pluginClassLoader);
stagingType = Class.forName("art.arcane.iris.world.lifecycle.WorldLifecycleStaging", true, pluginClassLoader);
} catch (Throwable ignored) {
return;
}
Object generator;
try {
generator = stagingType
.getDeclaredMethod("peekStemGenerator", String.class)
.invoke(null, levelId);
} catch (Throwable e) {
throw new RuntimeException("Iris failed to inspect the staged world generator",
e instanceof InvocationTargetException ex ? ex.getCause() : e);
}
if (generator == null || !generatorType.isInstance(generator)) {
return;
}
try {
Object target = generatorType.getMethod("getTarget").invoke(generator);
if (target == null) {
throw new IllegalStateException("Iris generator has no engine target.");
}
Object world = target.getClass().getMethod("getWorld").invoke(target);
if (world == null) {
throw new IllegalStateException("Iris generator target has no world identity.");
}
Object rawIdentity = world.getClass().getMethod("identity").invoke(world);
String worldIdentity = rawIdentity == null ? "" : rawIdentity.toString().trim();
Identifier worldIdentifier = Identifier.parse(worldIdentity);
if (!"iris".equals(worldIdentifier.getNamespace())
&& !"minecraft".equals(worldIdentifier.getNamespace())) {
throw new IllegalStateException(
"Iris generator has an unmanaged world identity: " + worldIdentity);
}
dimensionType = ResourceKey.create(Registries.LEVEL_STEM, worldIdentifier);
} catch (Throwable e) {
throw new RuntimeException("Iris failed to bind the staged world storage identity",
e instanceof InvocationTargetException ex ? ex.getCause() : e);
}
}
}
private static class ServerLevelAdvice {
@Advice.OnMethodEnter
static void enter(
@Advice.Argument(0) MinecraftServer server,
@Advice.Argument(value = 4, readOnly = false) ResourceKey<Level> dimensionKey,
@Advice.Argument(value = 5, readOnly = false) LevelStem levelStem,
@Advice.AllArguments Object[] constructorArguments
) {
if (dimensionKey == null)
return;
// This advice is inlined into every ServerLevel construction on the server. Until a
// world is proven Iris-owned, every failure must fail OPEN (keep the vanilla stem):
// Iris being unloaded or half-loaded must never break other plugins' world creation.
String levelId;
ClassLoader pluginClassLoader;
Class<?> generatorType;
Class<?> stagingType;
try {
levelId = dimensionKey.identifier().getPath();
if (levelId == null || levelId.isBlank()) {
return;
}
Plugin irisPlugin = Bukkit.getPluginManager().getPlugin("Iris");
if (irisPlugin == null) {
return;
}
pluginClassLoader = irisPlugin.getClass().getClassLoader();
generatorType = Class.forName("art.arcane.iris.platform.generation.PlatformChunkGenerator", true, pluginClassLoader);
stagingType = Class.forName("art.arcane.iris.world.lifecycle.WorldLifecycleStaging", true, pluginClassLoader);
} catch (Throwable ignored) {
// Iris absent or half-loaded: fail OPEN for a world we cannot prove ours.
return;
}
// From here Iris is present and its classes resolve; a failure resolving the
// staged generator must fail LOUD — silently handing a possibly-staged Iris world
// the vanilla stem corrupts generation.
ChunkGenerator gen = null;
try {
ChunkGenerator constructorGenerator = null;
for (Object argument : constructorArguments) {
if (argument instanceof ChunkGenerator candidate) {
constructorGenerator = candidate;
break;
}
}
Object generator = generatorType.isInstance(constructorGenerator) ? constructorGenerator : null;
if (generator == null) {
generator = stagingType
.getDeclaredMethod("consumeStemGenerator", String.class)
.invoke(null, levelId);
}
if (generator instanceof ChunkGenerator owned && owned.getClass().getPackageName().startsWith("art.arcane.iris")) {
gen = owned;
}
} catch (Throwable e) {
throw new RuntimeException("Iris failed to resolve the staged world generator",
e instanceof InvocationTargetException ex ? ex.getCause() : e);
}
if (gen == null) {
return;
}
// Past the ownership gate the world is Iris-owned; silently handing back the
// vanilla stem would corrupt generation, so failures from here rethrow.
try {
Object bindings = Class.forName("art.arcane.iris.platform.bukkit.nms.INMS", true, pluginClassLoader)
.getDeclaredMethod("get")
.invoke(null);
if (bindings == null) {
throw new IllegalStateException("Iris failed to resolve an INMSBinding instance.");
}
Method stemMethod = null;
for (Method candidate : bindings.getClass().getMethods()) {
if (candidate.getName().equals("createRuntimeLevelStem") && candidate.getParameterCount() == 2) {
stemMethod = candidate;
break;
}
}
if (stemMethod == null) {
throw new IllegalStateException("Iris binding is missing createRuntimeLevelStem.");
}
Object target = generatorType.getMethod("getTarget").invoke(gen);
if (target == null) {
throw new IllegalStateException("Iris generator has no engine target.");
}
Object world = target.getClass().getMethod("getWorld").invoke(target);
if (world == null) {
throw new IllegalStateException("Iris generator target has no world identity.");
}
Object rawIdentity = world.getClass().getMethod("identity").invoke(world);
String worldIdentity = rawIdentity == null ? "" : rawIdentity.toString().trim();
Identifier worldIdentifier = Identifier.parse(worldIdentity);
if (!"iris".equals(worldIdentifier.getNamespace())
&& !"minecraft".equals(worldIdentifier.getNamespace())) {
throw new IllegalStateException(
"Iris generator has an unmanaged world identity: " + worldIdentity);
}
Object resolvedStem = stemMethod.invoke(bindings, server.registryAccess(), gen);
if (!(resolvedStem instanceof LevelStem runtimeStem)) {
throw new IllegalStateException("Iris runtime LevelStem binding returned " + (resolvedStem == null ? "null" : resolvedStem.getClass().getName()) + ".");
}
dimensionKey = ResourceKey.create(Registries.DIMENSION, worldIdentifier);
levelStem = runtimeStem;
} catch (Throwable e) {
throw new RuntimeException("Iris failed to replace the levelStem", e instanceof InvocationTargetException ex ? ex.getCause() : e);
}
}
}
}
@@ -1,80 +0,0 @@
package art.arcane.iris.platform.bukkit.nms.v26_3_R1;
import art.arcane.iris.nativegen.v26_3_R1.NativeStructureGenerationKeys;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.levelgen.structure.Structure;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
final class VanillaStructureBiomes {
private VanillaStructureBiomes() {
}
static Set<String> possibleBiomeKeys(BiomeSource source) {
if (source == null) {
throw new IllegalStateException("Minecraft chunk generator has no biome source");
}
Set<String> keys = new LinkedHashSet<>();
Set<Holder<Biome>> possibleBiomes = source instanceof CustomBiomeSource customBiomeSource
? customBiomeSource.possibleStructureBiomes()
: source.possibleBiomes();
for (Holder<Biome> holder : possibleBiomes) {
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
if (key.isPresent()) {
keys.add(key.get().identifier().toString());
}
}
if (keys.isEmpty()) {
throw new IllegalStateException("Minecraft biome source exposes no registered possible biomes");
}
return keys;
}
static Set<String> structureBiomeKeys(RegistryAccess access, String structureKey) {
if (access == null) {
throw new IllegalStateException("Minecraft registry access is unavailable");
}
if (structureKey == null || structureKey.isBlank()) {
throw new IllegalArgumentException("Registered structure key must not be blank");
}
Set<String> keys = new LinkedHashSet<>();
Registry<Structure> registry = access.lookupOrThrow(Registries.STRUCTURE);
Structure structure = registry.getValue(Identifier.parse(structureKey));
if (structure == null) {
throw new IllegalArgumentException("Registered structure does not exist: " + structureKey);
}
boolean hasFilterEntries = false;
for (Holder<Biome> holder : structure.biomes()) {
hasFilterEntries = true;
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
if (key.isPresent()) {
keys.add(key.get().identifier().toString());
}
}
// An empty biome filter is legal datapack content (opt-in structures whose tags only
// reference absent modded biomes); the structure is unreachable, not an error state.
if (keys.isEmpty() && hasFilterEntries) {
throw new IllegalStateException("Registered structure '" + structureKey
+ "' has biome filter entries but none resolve to registered biome keys");
}
return keys;
}
static Set<String> reachableStructureKeys(ServerLevel level, BiomeSource source) {
if (level == null) {
throw new IllegalStateException("Minecraft server level is unavailable");
}
Set<String> possible = possibleBiomeKeys(source);
return NativeStructureGenerationKeys.reachable(level, possible);
}
}
@@ -1,6 +1,9 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.platform.bukkit.nms.v26_3_R1.IrisChunkGenerator;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeChunkGenerator;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeTerrainColumns;
import art.arcane.iris.platform.bukkit.nms.BukkitTerrainColumnPolicy;
import art.arcane.iris.generation.runtime.IrisComplex;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.GenerationSessionLease;
@@ -101,14 +104,13 @@ public final class IrisChunkGeneratorHeightCacheTest {
when(engine.getCacheID()).thenReturn(1);
when(engine.acquireGenerationLease(anyString())).thenReturn(GenerationSessionLease.noop());
when(complex.resolvedTerrainColumn(anyInt(), anyInt())).thenReturn(Optional.of(signature()));
IrisChunkGenerator generator = mock(IrisChunkGenerator.class, CALLS_REAL_METHODS);
set(generator, "engine", engine);
set(generator, "terrainHeights", new NativeTerrainHeightCache());
NativeChunkGenerator<?, ?, ?, ?, ?> generator = mock(NativeChunkGenerator.class, CALLS_REAL_METHODS);
set(generator, "terrainColumns", new NativeTerrainColumns(new BukkitTerrainColumnPolicy(engine, null)));
return new Fixture(generator, engine, complex);
}
private static void set(IrisChunkGenerator generator, String name, Object value) throws Exception {
Field field = IrisChunkGenerator.class.getDeclaredField(name);
private static void set(NativeChunkGenerator<?, ?, ?, ?, ?> generator, String name, Object value) throws Exception {
Field field = NativeChunkGenerator.class.getDeclaredField(name);
field.setAccessible(true);
field.set(generator, value);
}
@@ -126,6 +128,6 @@ public final class IrisChunkGeneratorHeightCacheTest {
new TerrainBoundarySignature.BiomeEncoding(List.of("minecraft:plains"), new short[]{0})), geometry);
}
private record Fixture(IrisChunkGenerator generator, Engine engine, IrisComplex complex) {
private record Fixture(NativeChunkGenerator<?, ?, ?, ?, ?> generator, Engine engine, IrisComplex complex) {
}
}
@@ -1,5 +1,9 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.structure.nativegen.GenerationWritePolicy;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeGenerationWriteGuard;
import art.arcane.iris.generation.runtime.DimensionStackContext;
import art.arcane.iris.generation.runtime.DimensionStackLayout;
import art.arcane.iris.generation.runtime.IrisComplex;
@@ -45,11 +49,11 @@ public final class NativeGenerationWriteGuardTest {
DimensionStackContext stack = mock(DimensionStackContext.class);
when(objects.contains(-3, 74, -7)).thenReturn(true);
Predicate<BlockPos> protectedPositions = NativeGenerationWriteGuard.protectedPositions(objects, stack, -64);
Predicate<BlockPos> protectedPositions = NativeGenerationWriteGuard.protectedPositions(GenerationWritePolicy.protectedPositions(objects, stack, -64));
assertTrue(protectedPositions.test(new BlockPos(-3, 10, -7)));
verifyNoInteractions(stack);
assertFalse(NativeGenerationWriteGuard.protectedPositions(objects, null, -64)
assertFalse(NativeGenerationWriteGuard.protectedPositions(GenerationWritePolicy.protectedPositions(objects, null, -64))
.test(new BlockPos(-3, 11, -7)));
}
@@ -65,7 +69,7 @@ public final class NativeGenerationWriteGuardTest {
when(negative.isHostFeatureProtectedY(64)).thenReturn(true);
when(positive.isHostFeatureProtectedY(65)).thenReturn(true);
Predicate<BlockPos> protectedPositions = NativeGenerationWriteGuard.protectedPositions(objects, stack, -64);
Predicate<BlockPos> protectedPositions = NativeGenerationWriteGuard.protectedPositions(GenerationWritePolicy.protectedPositions(objects, stack, -64));
assertTrue(protectedPositions.test(new BlockPos(-1, 0, -1)));
assertFalse(protectedPositions.test(new BlockPos(-1, 1, -1)));
@@ -85,10 +89,10 @@ public final class NativeGenerationWriteGuardTest {
when(router.history()).thenReturn(history);
when(history.activeActivation()).thenReturn(active);
when(active.activationId()).thenReturn(4L);
assertTrue(NativeGenerationWriteGuard.isHistoricalStructure(engine, 3));
assertFalse(NativeGenerationWriteGuard.isHistoricalStructure(engine, 0));
assertFalse(NativeGenerationWriteGuard.isHistoricalStructure(engine, 4));
assertFalse(NativeGenerationWriteGuard.isHistoricalStructure(engine, 5));
assertTrue(GenerationWritePolicy.isHistoricalStructure(engine, 3));
assertFalse(GenerationWritePolicy.isHistoricalStructure(engine, 0));
assertFalse(GenerationWritePolicy.isHistoricalStructure(engine, 4));
assertFalse(GenerationWritePolicy.isHistoricalStructure(engine, 5));
}
@Test
@@ -100,11 +104,11 @@ public final class NativeGenerationWriteGuardTest {
ChunkAccess partial = mock(ChunkAccess.class);
when(partial.getPersistedStatus()).thenReturn(ChunkStatus.TERRAIN);
when(region.getChunk(anyInt(), anyInt())).thenReturn(partial);
assertTrue(NativeGenerationWriteGuard.allowsDecoration(engine, region, new ChunkPos(0, 0)));
assertTrue(NativeGenerationWriteGuard.allowsDecoration(engine.getComplex()::allowsMantleChunkWrite, region, new ChunkPos(0, 0)));
ChunkAccess complete = mock(ChunkAccess.class);
when(complete.getPersistedStatus()).thenReturn(ChunkStatus.FULL);
when(region.getChunk(1, -1)).thenReturn(complete);
assertFalse(NativeGenerationWriteGuard.allowsDecoration(engine, region, new ChunkPos(0, 0)));
assertFalse(NativeGenerationWriteGuard.allowsDecoration(engine.getComplex()::allowsMantleChunkWrite, region, new ChunkPos(0, 0)));
}
@Test
@@ -115,11 +119,11 @@ public final class NativeGenerationWriteGuardTest {
ChunkAccess chunk = mock(ChunkAccess.class);
when(chunk.getPos()).thenReturn(new ChunkPos(-2, 3));
when(chunk.getPersistedStatus()).thenReturn(ChunkStatus.TERRAIN);
assertTrue(NativeGenerationWriteGuard.allowsPendingStage(engine, chunk, ChunkStatus.FEATURES));
assertFalse(NativeGenerationWriteGuard.allowsPendingStage(engine, chunk, ChunkStatus.TERRAIN));
assertTrue(NativeGenerationWriteGuard.allowsPendingStage(engine.getComplex()::allowsMantleChunkWrite, chunk, ChunkStatus.FEATURES));
assertFalse(NativeGenerationWriteGuard.allowsPendingStage(engine.getComplex()::allowsMantleChunkWrite, chunk, ChunkStatus.TERRAIN));
when(chunk.getPersistedStatus()).thenReturn(ChunkStatus.FEATURES);
assertFalse(NativeGenerationWriteGuard.allowsPendingStage(engine, chunk, ChunkStatus.FEATURES));
assertFalse(NativeGenerationWriteGuard.allowsPendingStage(engine.getComplex()::allowsMantleChunkWrite, chunk, ChunkStatus.FEATURES));
when(chunk.getPersistedStatus()).thenReturn(ChunkStatus.FULL);
assertFalse(NativeGenerationWriteGuard.allowsPendingStage(engine, chunk, ChunkStatus.SPAWN));
assertFalse(NativeGenerationWriteGuard.allowsPendingStage(engine.getComplex()::allowsMantleChunkWrite, chunk, ChunkStatus.SPAWN));
}
}
@@ -1,5 +1,19 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureStartInjector;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureFactory;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureReferenceEnvelope;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeJigsawMetadata;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.ForcedStructureChunkGenerator;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureTemplatePoolBounds;
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
import java.util.Set;
import art.arcane.iris.structure.jigsaw.IrisJigsawConfiguration;
@@ -110,10 +124,10 @@ public class NativeStructureFactoryTest {
distance.putInt("vertical", 64);
axisSpecific.put("max_distance_from_center", distance);
assertEquals(80, NativeStructureFactory.sourceDistance(scalar, "horizontal"));
assertEquals(80, NativeStructureFactory.sourceDistance(scalar, "vertical"));
assertEquals(96, NativeStructureFactory.sourceDistance(axisSpecific, "horizontal"));
assertEquals(64, NativeStructureFactory.sourceDistance(axisSpecific, "vertical"));
assertEquals(80, NativeJigsawMetadata.sourceDistance(scalar, "horizontal"));
assertEquals(80, NativeJigsawMetadata.sourceDistance(scalar, "vertical"));
assertEquals(96, NativeJigsawMetadata.sourceDistance(axisSpecific, "horizontal"));
assertEquals(64, NativeJigsawMetadata.sourceDistance(axisSpecific, "vertical"));
}
@Test
@@ -123,8 +137,8 @@ public class NativeStructureFactoryTest {
.terrainAdapation(TerrainAdjustment.BEARD_BOX)
.build());
assertEquals(0, NativeStructureFactory.horizontalReferenceExpansion(none));
assertEquals(12, NativeStructureFactory.horizontalReferenceExpansion(beardBox));
assertEquals(0, NativeJigsawMetadata.horizontalReferenceExpansion(none));
assertEquals(12, NativeJigsawMetadata.horizontalReferenceExpansion(beardBox));
}
@Test
@@ -1,5 +1,7 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureGenerationKeys;
import net.minecraft.world.level.levelgen.structure.placement.AbstractSpreadingStructurePlacement;
import net.minecraft.SharedConstants;
import net.minecraft.core.Vec3i;
@@ -1,5 +1,7 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureLocatePersistence;
import net.minecraft.SharedConstants;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.Bootstrap;
@@ -1,11 +1,19 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureReferenceRepair;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureOwnershipFingerprint;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureReferenceEnvelope;
import art.arcane.iris.structure.nativegen.NativeStructureOwnershipRecord;
import art.arcane.iris.structure.nativegen.NativeStructureStartPlan;
import art.arcane.iris.structure.nativegen.IrisNativeStructure;
import art.arcane.iris.structure.placement.IrisStructurePlacement;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.volmlib.util.structure.StructureTerrainMode;
import art.arcane.volmlib.util.collection.KList;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
@@ -65,7 +73,7 @@ public class NativeStructureOwnershipFingerprintTest {
generated, 0, 80, -64, 320);
int alignedLocatorY = NativeStructureOwnershipFingerprint.locatorY(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(24);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated,
@@ -85,11 +93,9 @@ public class NativeStructureOwnershipFingerprintTest {
StructureStart reloaded = new StructureStart(structure, origin, 0, regeneratedPieces);
String afterReload = NativeStructureOwnershipFingerprint.fingerprint(
"minecraft:monument", reloaded);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
wrapped,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(wrapped).minY()),
NativeStructureReferenceEnvelope.referenceBounds(wrapped, structure, terrain));
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
"minecraft:monument", wrapped, NativeStructureReferenceEnvelope.referenceBounds(wrapped, structure, terrain)), plan(origin, NativeStructureReferenceEnvelope.contentBounds(wrapped).minY()));
assertEquals(beforeReload, afterReload);
assertTrue(NativeStructureOwnershipFingerprint.matches(ownership, reloaded));
@@ -110,11 +116,9 @@ public class NativeStructureOwnershipFingerprintTest {
for (StructurePiece piece : moved.getPieces()) {
piece.move(1, 0, 0);
}
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
expected,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(expected).minY()),
expected.getBoundingBox());
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
"minecraft:monument", expected, expected.getBoundingBox()), plan(origin, NativeStructureReferenceEnvelope.contentBounds(expected).minY()));
assertFalse(NativeStructureOwnershipFingerprint.matches(ownership, moved));
}
@@ -129,18 +133,16 @@ public class NativeStructureOwnershipFingerprintTest {
NativeStructureVerticalPlacer.alignOceanMonumentToSeaLevel(
generated, 0, 80, -64, 320);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(24);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
generated,
structure,
0,
terrain);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
wrapped,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(wrapped).minY()),
NativeStructureReferenceEnvelope.referenceBounds(wrapped, structure, terrain));
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
"minecraft:monument", wrapped, NativeStructureReferenceEnvelope.referenceBounds(wrapped, structure, terrain)), plan(origin, NativeStructureReferenceEnvelope.contentBounds(wrapped).minY()));
PiecesContainer regeneratedPieces = OceanMonumentStructure.regeneratePiecesAfterLoad(
origin, seed, new PiecesContainer(wrapped.getPieces()));
StructureStart reloaded = new StructureStart(structure, origin, 0, regeneratedPieces);
@@ -263,13 +265,10 @@ public class NativeStructureOwnershipFingerprintTest {
private static NativeStructureOwnershipRecord ownership(
ChunkPos origin, StructureStart start, OceanMonumentStructure structure) {
return NativeStructureOwnershipFingerprint.capture(
"minecraft:monument",
start,
plan(origin, NativeStructureReferenceEnvelope.contentBounds(start).minY()),
NativeStructureReferenceEnvelope.referenceBounds(
start, structure, new IrisStructureTerrain())
);
return NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
"minecraft:monument", start, NativeStructureReferenceEnvelope.referenceBounds(
start, structure, new IrisStructureTerrain())), plan(origin, NativeStructureReferenceEnvelope.contentBounds(start).minY()));
}
private static StructureStart poolStart(
@@ -1,12 +1,22 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.volmlib.nativelib.terrain.structure.StructureOwnershipRecordView;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureOwnershipRecovery;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureOwnershipFingerprint;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureReferenceEnvelope;
import art.arcane.iris.structure.nativegen.NativeStructureOwnershipRecord;
import art.arcane.iris.structure.nativegen.NativeStructureStartPlan;
import art.arcane.iris.structure.placement.StructurePlacementGrid;
import art.arcane.iris.structure.nativegen.IrisNativeStructure;
import art.arcane.iris.structure.placement.IrisStructurePlacement;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.volmlib.util.structure.StructureTerrainMode;
import art.arcane.volmlib.util.collection.KList;
import net.minecraft.SharedConstants;
import net.minecraft.core.Direction;
@@ -63,7 +73,7 @@ public class NativeStructureOwnershipRecoveryTest {
NativeStructureOwnershipRecord recovered =
NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, reloaded,
plan, expected, false);
plan, expected, false, NativeStructureOwnershipRecord::capture);
assertNotNull(recovered);
assertEquals(StructurePlacementGrid.placementIdentity(plan.placement()),
@@ -71,7 +81,7 @@ public class NativeStructureOwnershipRecoveryTest {
assertEquals(expectedLocatorY, recovered.locatorY());
assertNotEquals(NativeStructureOwnershipFingerprint.locatorY(reloaded),
recovered.locatorY());
assertEquals(IrisStructureTerrainMode.FORCE_CARVE,
assertEquals(StructureTerrainMode.FORCE_CARVE,
recovered.restoredDecision().terrain().resolvedMode());
assertEquals(24, recovered.restoredDecision().terrain().getHorizontalPadding());
assertTrue(recovered.referenceMinChunkX()
@@ -89,7 +99,7 @@ public class NativeStructureOwnershipRecoveryTest {
NativeStructureOwnershipRecord recovered =
NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, natural,
plan, natural, true);
plan, natural, true, NativeStructureOwnershipRecord::capture);
assertNull(recovered);
}
@@ -114,10 +124,10 @@ public class NativeStructureOwnershipRecoveryTest {
StructurePlacementGrid.placementIdentity(changedIdentity.placement()));
assertNull(NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, expected,
changedIdentity, expected, false));
changedIdentity, expected, false, NativeStructureOwnershipRecord::capture));
assertNull(NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, changedGeometry,
plan(origin, "geometry-check", 0), expected, false));
plan(origin, "geometry-check", 0), expected, false, NativeStructureOwnershipRecord::capture));
}
@Test
@@ -129,7 +139,7 @@ public class NativeStructureOwnershipRecoveryTest {
NativeStructureOwnershipRecord recovered =
NativeStructureOwnershipRecovery.proveCandidate(
"minecraft:monument", structure, expected,
plan(origin, "fingerprint-check", 16), expected, false);
plan(origin, "fingerprint-check", 16), expected, false, NativeStructureOwnershipRecord::capture);
assertNotNull(recovered);
StructureStart moved = monumentStart(structure, origin, seed);
@@ -148,10 +158,11 @@ public class NativeStructureOwnershipRecoveryTest {
OceanMonumentStructure structure = structure();
StructureStart start = monumentStart(structure, origin, seed);
NativeStructureStartPlan plan = plan(
origin, "vacuum-envelope-refresh", IrisStructureTerrainMode.VACUUM, 0);
origin, "vacuum-envelope-refresh", StructureTerrainMode.VACUUM, 0);
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(start);
NativeStructureOwnershipRecord stale = NativeStructureOwnershipFingerprint.capture(
structureKey, start, plan, content);
NativeStructureOwnershipRecord stale = NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
structureKey, start, content), plan);
NativeStructureOwnershipRecord refreshed =
NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
@@ -166,7 +177,7 @@ public class NativeStructureOwnershipRecoveryTest {
assertEquals(stale.locatorY(), refreshed.locatorY());
assertEquals(stale.contentFingerprint(), refreshed.contentFingerprint());
assertEquals(stale.decision(), refreshed.decision());
assertEquals(IrisStructureTerrainMode.VACUUM,
assertEquals(StructureTerrainMode.VACUUM,
refreshed.restoredDecision().terrain().resolvedMode());
BoundingBox expected = NativeStructureReferenceEnvelope.referenceBounds(
start, structure, plan.placement().resolvedTerrain(), structureKey);
@@ -187,10 +198,10 @@ public class NativeStructureOwnershipRecoveryTest {
OceanMonumentStructure structure = structure();
StructureStart expected = monumentStart(structure, origin, seed);
NativeStructureStartPlan plan = plan(
origin, "vacuum-content-check", IrisStructureTerrainMode.VACUUM, 0);
NativeStructureOwnershipRecord stale = NativeStructureOwnershipFingerprint.capture(
structureKey, expected, plan,
NativeStructureReferenceEnvelope.contentBounds(expected));
origin, "vacuum-content-check", StructureTerrainMode.VACUUM, 0);
NativeStructureOwnershipRecord stale = NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
structureKey, expected, NativeStructureReferenceEnvelope.contentBounds(expected)), plan);
StructureStart moved = monumentStart(structure, origin, seed);
for (StructurePiece piece : moved.getPieces()) {
piece.move(1, 0, 0);
@@ -210,8 +221,9 @@ public class NativeStructureOwnershipRecoveryTest {
NativeStructureStartPlan plan = plan(origin, "current-force-carve", 24);
BoundingBox envelope = NativeStructureReferenceEnvelope.referenceBounds(
start, structure, plan.placement().resolvedTerrain(), structureKey);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
structureKey, start, plan, envelope);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
structureKey, start, envelope), plan);
assertSame(ownership, NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
structureKey, structure, start, ownership));
@@ -226,19 +238,20 @@ public class NativeStructureOwnershipRecoveryTest {
StructureStart start = monumentStart(structure, origin, seed);
BoundingBox initial = NativeStructureReferenceEnvelope.contentBounds(start);
int maximumReferenceBlockX = ((origin.x()
+ NativeStructureOwnershipRecord.MAX_REFERENCE_DISTANCE_CHUNKS) << 4) + 15;
+ StructureOwnershipRecordView.MAX_REFERENCE_DISTANCE_CHUNKS) << 4) + 15;
int shiftX = maximumReferenceBlockX - initial.maxX();
for (StructurePiece piece : start.getPieces()) {
piece.move(shiftX, 0, 0);
}
NativeStructureStartPlan plan = plan(
origin, "clipped-vacuum", IrisStructureTerrainMode.VACUUM, 0);
origin, "clipped-vacuum", StructureTerrainMode.VACUUM, 0);
BoundingBox envelope = NativeStructureReferenceEnvelope.referenceBounds(
start, structure, plan.placement().resolvedTerrain(), structureKey);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipFingerprint.capture(
structureKey, start, plan, envelope);
NativeStructureOwnershipRecord ownership = NativeStructureOwnershipRecord.capture(
NativeStructureOwnershipFingerprint.capture(
structureKey, start, envelope), plan);
assertEquals(origin.x() + NativeStructureOwnershipRecord.MAX_REFERENCE_DISTANCE_CHUNKS,
assertEquals(origin.x() + StructureOwnershipRecordView.MAX_REFERENCE_DISTANCE_CHUNKS,
ownership.referenceMaxChunkX());
assertSame(ownership, NativeStructureOwnershipRecovery.refreshReferenceEnvelope(
structureKey, structure, start, ownership));
@@ -270,12 +283,12 @@ public class NativeStructureOwnershipRecoveryTest {
String placementId,
int horizontalPadding) {
return plan(origin, placementId,
IrisStructureTerrainMode.FORCE_CARVE, horizontalPadding);
StructureTerrainMode.FORCE_CARVE, horizontalPadding);
}
private static NativeStructureStartPlan plan(ChunkPos origin,
String placementId,
IrisStructureTerrainMode terrainMode,
StructureTerrainMode terrainMode,
int horizontalPadding) {
IrisNativeStructure source = new IrisNativeStructure()
.setStructure("minecraft:monument")
@@ -1,8 +1,18 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.volmlib.nativelib.terrain.structure.StructurePalette;
import art.arcane.volmlib.nativelib.terrain.structure.StructureTerrainSettings;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureReferenceEnvelope;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureTerrainIntegrator;
import art.arcane.iris.generation.terrain.IrisMaterialPalette;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.volmlib.util.structure.StructureTerrainMode;
import art.arcane.iris.structure.placement.IrisStructureYBand;
import art.arcane.volmlib.util.math.RNG;
import net.minecraft.SharedConstants;
@@ -76,7 +86,7 @@ public class NativeStructurePostProcessorEncaseTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "minecraft:stronghold", start,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setMode(StructureTerrainMode.ENCASE)
.setHorizontalPadding(1)
.setCeilingPadding(1)
.setFloorPadding(1),
@@ -100,7 +110,7 @@ public class NativeStructurePostProcessorEncaseTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), bounds, "minecraft:stronghold", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.ENCASE),
new IrisStructureTerrain().setMode(StructureTerrainMode.ENCASE),
null);
assertEquals(-1, bounds.minY());
@@ -181,7 +191,7 @@ public class NativeStructurePostProcessorEncaseTest {
world(new HashMap<>()), area,
List.of(new NativeStructureTerrainIntegrator.TerrainTarget(
"test:bounded-snapshot", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))));
new IrisStructureTerrain().setMode(StructureTerrainMode.SOURCE))));
assertNotNull(sourceTerrain);
assertEquals(23 * 16 * 16, sourceTerrain.sampledCells());
@@ -210,7 +220,7 @@ public class NativeStructurePostProcessorEncaseTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), bounds, "minecraft:stronghold", start,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setMode(StructureTerrainMode.ENCASE)
.setEncasePalette(palette),
NativeStructurePostProcessorEncaseTest::tuffBlock);
@@ -221,10 +231,10 @@ public class NativeStructurePostProcessorEncaseTest {
@Test
public void omittedTerrainConfigurationRetainsSourceSemantics() {
for (TerrainAdjustment adjustment : TerrainAdjustment.values()) {
IrisStructureTerrain resolved = NativeStructureTerrainIntegrator.resolveNativeTerrain(
StructureTerrainSettings resolved = NativeStructureTerrainIntegrator.resolveNativeTerrain(
start(adjustment, 64), null);
assertEquals(IrisStructureTerrainMode.SOURCE, resolved.resolvedMode());
assertEquals(StructureTerrainMode.SOURCE, resolved.resolvedMode());
}
}
@@ -240,7 +250,7 @@ public class NativeStructurePostProcessorEncaseTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "test:bury", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE), null);
new IrisStructureTerrain().setMode(StructureTerrainMode.SOURCE), null);
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX() - 5, groundY, bounds.minZ()));
@@ -267,7 +277,7 @@ public class NativeStructurePostProcessorEncaseTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "test:encapsulate", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE), null);
new IrisStructureTerrain().setMode(StructureTerrainMode.SOURCE), null);
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX() - 11, bounds.minY(), bounds.minZ()));
@@ -304,7 +314,7 @@ public class NativeStructurePostProcessorEncaseTest {
@Test
public void explicitTerrainConfigurationWinsOverAutoEncase() {
IrisStructureTerrain configured = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.PRESERVE);
.setMode(StructureTerrainMode.PRESERVE);
assertSame(configured, NativeStructureTerrainIntegrator.resolveNativeTerrain(
start(TerrainAdjustment.BURY, 64), configured));
@@ -313,11 +323,11 @@ public class NativeStructurePostProcessorEncaseTest {
@Test
public void legacyTemplateAirIsClearedAfterEveryTerrainFillPath() {
IrisStructureTerrain source = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.SOURCE);
.setMode(StructureTerrainMode.SOURCE);
IrisStructureTerrain preserve = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.PRESERVE);
.setMode(StructureTerrainMode.PRESERVE);
IrisStructureTerrain encase = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE);
.setMode(StructureTerrainMode.ENCASE);
assertTrue(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
start(TerrainAdjustment.BURY, 64), source));
@@ -337,7 +347,7 @@ public class NativeStructurePostProcessorEncaseTest {
BoundingBox sourceBounds = generated.getBoundingBox();
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setMode(StructureTerrainMode.ENCASE)
.setHorizontalPadding(4);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
@@ -373,7 +383,7 @@ public class NativeStructurePostProcessorEncaseTest {
StructureStart generated = start(TerrainAdjustment.BURY, 64);
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.ENCASE)
.setMode(StructureTerrainMode.ENCASE)
.setHorizontalPadding(112);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
@@ -446,7 +456,7 @@ public class NativeStructurePostProcessorEncaseTest {
assertEquals(minY, start.getBoundingBox().minY());
}
private static BlockState tuffBlock(IrisMaterialPalette palette, RNG rng, int x, int y, int z) {
private static BlockState tuffBlock(StructurePalette palette, RNG rng, int x, int y, int z) {
assertNotNull(palette);
assertNotNull(rng);
return Blocks.TUFF.defaultBlockState();
@@ -1,7 +1,11 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureReferenceEnvelope;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.volmlib.util.structure.StructureTerrainMode;
import net.minecraft.SharedConstants;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderSet;
@@ -95,7 +99,7 @@ public class NativeStructurePostProcessorMonumentTest {
start.getStructure(),
0,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(4));
int offset = NativeStructureVerticalPlacer.applyVerticalPlacement(
@@ -1,5 +1,11 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureSurfaceFitter;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureReflection;
import net.minecraft.SharedConstants;
import net.minecraft.core.HolderSet;
import net.minecraft.server.Bootstrap;
@@ -1,9 +1,29 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.generation.mantle.StructureCarvingFootprint;
import art.arcane.iris.structure.placement.IrisStructureCarveShape;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureReferenceEnvelope;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureTerrainIntegrator;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureFoundationBuilder;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureSurfaceSupportBuilder;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureSurfaceFitter;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureVegetationClearer;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureTemplateOccupancy;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureReflection;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.WorldgenTerrainHeightmaps;
import art.arcane.volmlib.util.structure.StructureCarvingFootprint;
import art.arcane.volmlib.util.structure.StructureCarveShape;
import art.arcane.iris.structure.placement.IrisStructureTerrain;
import art.arcane.iris.structure.placement.IrisStructureTerrainMode;
import art.arcane.volmlib.util.structure.StructureTerrainMode;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import net.minecraft.SharedConstants;
@@ -214,10 +234,10 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
put(blocks, x, sourceY, 0, Blocks.GRASS_BLOCK.defaultBlockState());
}
assertTrue(NativeStructureSurfaceFitter.requiresFlattenTerrain(
surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)));
surfaceTarget(start, StructureTerrainMode.FLATTEN)));
NativeStructureSurfaceFitter.prepareSurfaceStructures(world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)),
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)),
(x, z) -> x < 2 ? groundY - 32 : groundY + 32);
for (int x = 0; x <= 3; x++) {
@@ -240,7 +260,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
put(blocks, 0, groundY - 19, 0, Blocks.DIRT.defaultBlockState());
NativeStructureSurfaceFitter.prepareSurfaceStructures(world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)), (x, z) -> groundY);
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)), (x, z) -> groundY);
for (int y = groundY - 19; y <= groundY; y++) {
assertEquals(Blocks.DIRT.defaultBlockState(), state(blocks, 0, y, 0));
@@ -260,9 +280,9 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
Map<BlockPos, BlockState> originalSubmerged = new HashMap<>(submerged);
NativeStructureSurfaceFitter.prepareSurfaceStructures(world(buried), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)), (x, z) -> 100);
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)), (x, z) -> 100);
NativeStructureSurfaceFitter.prepareSurfaceStructures(world(submerged), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)), (x, z) -> groundY - 8);
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)), (x, z) -> groundY - 8);
assertEquals(originalBuried, buried);
assertEquals(originalSubmerged, submerged);
@@ -280,12 +300,12 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
StructureStart start = new StructureStart(structure, new ChunkPos(0, 0), 0,
new PiecesContainer(desertStart().getPieces()));
assertFalse(NativeStructureSurfaceFitter.requiresFlattenTerrain(
surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)));
surfaceTarget(start, StructureTerrainMode.FLATTEN)));
}
assertFalse(NativeStructureSurfaceFitter.requiresFlattenTerrain(
surfaceTarget(desertStart(TerrainAdjustment.BURY), IrisStructureTerrainMode.FLATTEN)));
surfaceTarget(desertStart(TerrainAdjustment.BURY), StructureTerrainMode.FLATTEN)));
assertFalse(NativeStructureSurfaceFitter.requiresFlattenTerrain(
surfaceTarget(desertStart(TerrainAdjustment.ENCAPSULATE), IrisStructureTerrainMode.FLATTEN)));
surfaceTarget(desertStart(TerrainAdjustment.ENCAPSULATE), StructureTerrainMode.FLATTEN)));
}
@Test
@@ -294,8 +314,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(0, 128, 0, 0, 136, 0), 0, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece));
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, IrisStructureTerrainMode.FLATTEN);
target.terrain().setFlattenRange(32);
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, StructureTerrainMode.FLATTEN);
((IrisStructureTerrain) target.terrain()).setFlattenRange(32);
BoundingBox area = new BoundingBox(0, 0, 0, 0, 255, 0);
Map<BlockPos, BlockState> blocks = new HashMap<>();
put(blocks, 0, 94, 0, Blocks.GOLD_BLOCK.defaultBlockState());
@@ -323,7 +343,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(0, 64, 0, 0, 70, 0), 0, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece));
IrisStructureTerrain terrain = new IrisStructureTerrain().setMode(IrisStructureTerrainMode.FLATTEN)
IrisStructureTerrain terrain = new IrisStructureTerrain().setMode(StructureTerrainMode.FLATTEN)
.setHorizontalPadding(128).setFlattenRange(128);
BoundingBox references = NativeStructureReferenceEnvelope.referenceBounds(start, start.getStructure(), terrain);
@@ -351,7 +371,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
Map<BlockPos, BlockState> blocks = supportTerrain(0, 0);
Set<Long> written = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)),
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertEquals(Set.of(BlockPos.asLong(0, 63, 0)), written);
@@ -372,7 +392,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
put(blocks, 0, 80, 0, Blocks.STONE.defaultBlockState());
Set<Long> written = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)),
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertTrue(written.isEmpty());
@@ -399,7 +419,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
NativeStructureSurfaceFitter.prepareSurfaceStructures(world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.FLATTEN)),
List.of(surfaceTarget(start, StructureTerrainMode.FLATTEN)),
(x, z) -> x == footprint.minX() ? 80 : 112);
assertEquals(81, piece.getBoundingBox().minY());
@@ -421,8 +441,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
List.of(), StructureTemplatePool.Projection.TERRAIN_MATCHING),
new BoundingBox(4, 65, 0, 4, 75, 0), 0, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(left, path, right));
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, IrisStructureTerrainMode.FLATTEN);
target.terrain().setHorizontalPadding(24);
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, StructureTerrainMode.FLATTEN);
((IrisStructureTerrain) target.terrain()).setHorizontalPadding(24);
BoundingBox area = new BoundingBox(0, 0, 0, 8, 128, 0);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 96);
for (int x : List.of(0, 8)) {
@@ -472,8 +492,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
List.of(), StructureTemplatePool.Projection.TERRAIN_MATCHING),
new BoundingBox(0, 65, 0, 8, 75, 0), 0, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(left, path, right));
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, IrisStructureTerrainMode.FLATTEN);
target.terrain().setHorizontalPadding(24);
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, StructureTerrainMode.FLATTEN);
((IrisStructureTerrain) target.terrain()).setHorizontalPadding(24);
BoundingBox area = new BoundingBox(0, 0, 0, 8, 127, 0);
WorldGenLevel world = world(chunk);
@@ -532,8 +552,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
List.of(), StructureTemplatePool.Projection.TERRAIN_MATCHING),
new BoundingBox(4, 65, 0, 6, 75, 0), 0, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(left, path, right));
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, IrisStructureTerrainMode.FLATTEN);
target.terrain().setHorizontalPadding(24).setFlattenRange(32);
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, StructureTerrainMode.FLATTEN);
((IrisStructureTerrain) target.terrain()).setHorizontalPadding(24).setFlattenRange(32);
BoundingBox area = new BoundingBox(0, 0, 0, 8, 127, 0);
WorldGenLevel world = world(chunk);
@@ -579,8 +599,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
}
StructureStart start = rigidSurfaceStart(pieces);
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, IrisStructureTerrainMode.FLATTEN);
target.terrain().setHorizontalPadding(24).setFlattenRange(64);
NativeStructureTerrainIntegrator.TerrainTarget target = surfaceTarget(start, StructureTerrainMode.FLATTEN);
((IrisStructureTerrain) target.terrain()).setHorizontalPadding(24).setFlattenRange(64);
BoundingBox area = new BoundingBox(0, 0, 0, 8, 127, 8);
Map<BlockPos, BlockState> blocks = new HashMap<>();
for (int x = 0; x <= 8; x++) {
@@ -667,11 +687,11 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
assertTrue(NativeStructureSurfaceFitter.requiresSurfaceTerrain(
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:beard_box", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE))));
new IrisStructureTerrain().setMode(StructureTerrainMode.SOURCE))));
assertFalse(NativeStructureSurfaceFitter.requiresSurfaceTerrain(
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:beard_box", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.PRESERVE))));
new IrisStructureTerrain().setMode(StructureTerrainMode.PRESERVE))));
}
@Test
@@ -681,15 +701,15 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureTerrainIntegrator.TerrainTarget sourceNone =
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:none", none,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE));
new IrisStructureTerrain().setMode(StructureTerrainMode.SOURCE));
NativeStructureTerrainIntegrator.TerrainTarget vacuumNone =
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:none", none,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.VACUUM));
new IrisStructureTerrain().setMode(StructureTerrainMode.VACUUM));
NativeStructureTerrainIntegrator.TerrainTarget vacuumBox =
new NativeStructureTerrainIntegrator.TerrainTarget(
"test:box", box,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.VACUUM));
new IrisStructureTerrain().setMode(StructureTerrainMode.VACUUM));
assertFalse(NativeStructureSurfaceFitter.requiresSurfaceTerrain(sourceNone));
assertTrue(NativeStructureSurfaceFitter.requiresSurfaceTerrain(vacuumNone));
@@ -778,7 +798,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
List<NativeStructureTerrainIntegrator.TerrainTarget> targets = List.of(
new NativeStructureTerrainIntegrator.TerrainTarget(
"nova_structures:tavern_oak", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.SOURCE)));
new IrisStructureTerrain().setMode(StructureTerrainMode.SOURCE)));
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area, targets,
(x, z) -> 65);
@@ -886,7 +906,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 0, 64, 0));
@@ -911,7 +931,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 65, 0));
@@ -932,7 +952,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 80);
for (int y = 61; y <= 74; y++) {
@@ -957,7 +977,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 79);
for (int y = 61; y <= 74; y++) {
@@ -989,7 +1009,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.SurfaceTerrainPlan plan =
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
for (int y = 61; y <= 64; y++) {
put(blocks, 0, y, 0, Blocks.CAVE_AIR.defaultBlockState());
@@ -1029,7 +1049,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.SurfaceTerrainPlan plan =
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
put(blocks, 0, 65, 0, Blocks.COBBLESTONE.defaultBlockState());
@@ -1051,7 +1071,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
new BoundingBox(15, 65, 0, 16, 70, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
NativeStructureTerrainIntegrator.TerrainTarget target =
surfaceTarget(start, IrisStructureTerrainMode.VACUUM);
surfaceTarget(start, StructureTerrainMode.VACUUM);
BoundingBox wideArea = new BoundingBox(0, 54, 0, 31, 72, 15);
BoundingBox westArea = new BoundingBox(0, 54, 0, 15, 72, 15);
BoundingBox eastArea = new BoundingBox(16, 54, 0, 31, 72, 15);
@@ -1100,7 +1120,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(originalBlocks, blocks);
@@ -1121,7 +1141,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(originalBlocks, blocks);
@@ -1145,7 +1165,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 1, 66, 0));
@@ -1170,7 +1190,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(originalBlocks, blocks);
@@ -1190,7 +1210,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 65, 0));
@@ -1215,7 +1235,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(originalBlocks, blocks);
@@ -1236,7 +1256,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 4, 61, 4));
@@ -1254,7 +1274,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 50);
assertEquals(originalBlocks, blocks);
@@ -1273,7 +1293,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60, 1);
assertEquals(originalBlocks, blocks);
@@ -1297,7 +1317,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(start, StructureTerrainMode.VACUUM)),
(x, z) -> 60);
for (int y = 61; y <= 65; y++) {
@@ -1317,7 +1337,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
new BoundingBox(15, 66, 0, 16, 72, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
NativeStructureTerrainIntegrator.TerrainTarget target =
surfaceTarget(start, IrisStructureTerrainMode.VACUUM);
surfaceTarget(start, StructureTerrainMode.VACUUM);
BoundingBox wideArea = new BoundingBox(0, 54, 0, 31, 74, 15);
BoundingBox westArea = new BoundingBox(0, 54, 0, 15, 74, 15);
BoundingBox eastArea = new BoundingBox(16, 54, 0, 31, 74, 15);
@@ -1573,13 +1593,13 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
Set<Long> vacuumWritten = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(vacuumBlocks), area,
List.of(surfaceTarget(vacuumStart, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(vacuumStart, StructureTerrainMode.VACUUM)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertEquals(Set.of(BlockPos.asLong(0, 63, 0)), vacuumWritten);
assertTrue(NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(supportTerrain(0, 0)), area,
List.of(surfaceTarget(vacuumStart, IrisStructureTerrainMode.SOURCE)),
List.of(surfaceTarget(vacuumStart, StructureTerrainMode.SOURCE)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager).isEmpty());
PoolElementStructurePiece distantUpper = rigidTemplatePiece(
@@ -1589,7 +1609,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
List.of(lower, distantUpper), TerrainAdjustment.NONE);
assertTrue(NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(supportTerrain(0, 0)), area,
List.of(surfaceTarget(distantStart, IrisStructureTerrainMode.VACUUM)),
List.of(surfaceTarget(distantStart, StructureTerrainMode.VACUUM)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager).isEmpty());
}
@@ -1676,8 +1696,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
Set<Long> written = NativeStructureSurfaceSupportBuilder.bridgeRigidPieceSupport(
world(blocks), area, List.of(
surfaceTarget(start, IrisStructureTerrainMode.PRESERVE),
surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
surfaceTarget(start, StructureTerrainMode.PRESERVE),
surfaceTarget(start, StructureTerrainMode.VACUUM)),
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager);
assertEquals(Set.of(BlockPos.asLong(0, 63, 0)), written);
@@ -2033,7 +2053,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), bounds, "minecraft:desert_pyramid", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.VACUUM), null);
new IrisStructureTerrain().setMode(StructureTerrainMode.VACUUM), null);
assertEquals(Blocks.STONE.defaultBlockState(),
state(blocks, bounds.minX(), bounds.minY(), bounds.minZ()));
@@ -2055,7 +2075,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "minecraft:desert_pyramid", start,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(1)
.setCeilingPadding(1), null);
@@ -2089,7 +2109,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "minecraft:ancient_city", start,
new IrisStructureTerrain().setMode(IrisStructureTerrainMode.FORCE_CARVE), null);
new IrisStructureTerrain().setMode(StructureTerrainMode.FORCE_CARVE), null);
assertEquals(Blocks.AIR.defaultBlockState(),
state(blocks, firstBounds.minX(), y, z));
@@ -2338,8 +2358,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureTerrainIntegrator.integrateTerrain(
world(blocks), area, "minecraft:ancient_city", start,
new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setShape(IrisStructureCarveShape.ERODED)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setShape(StructureCarveShape.ERODED)
.setHorizontalPadding(6)
.setCeilingPadding(8)
.setFloorPadding(0)
@@ -2378,7 +2398,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
StructureStart generated = desertStart();
BoundingBox content = generated.getBoundingBox();
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(24);
StructureStart wrapped = NativeStructureReferenceEnvelope.wrap(
@@ -2403,7 +2423,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
StructureStart generated = desertStart(TerrainAdjustment.NONE);
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.VACUUM)
.setMode(StructureTerrainMode.VACUUM)
.setHorizontalPadding(64);
BoundingBox references = NativeStructureReferenceEnvelope.referenceBounds(
@@ -2425,7 +2445,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
ChunkPos origin = generated.getChunkPos();
BoundingBox content = NativeStructureReferenceEnvelope.contentBounds(generated);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setHorizontalPadding(124);
BoundingBox references = NativeStructureReferenceEnvelope.referenceBounds(
@@ -2575,11 +2595,11 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
private static NativeStructureTerrainIntegrator.TerrainTarget surfaceTarget(
StructureStart start) {
return surfaceTarget(start, IrisStructureTerrainMode.SOURCE);
return surfaceTarget(start, StructureTerrainMode.SOURCE);
}
private static NativeStructureTerrainIntegrator.TerrainTarget surfaceTarget(
StructureStart start, IrisStructureTerrainMode mode) {
StructureStart start, StructureTerrainMode mode) {
return new NativeStructureTerrainIntegrator.TerrainTarget(
"nova_structures:tavern_oak", start,
new IrisStructureTerrain().setMode(mode));
@@ -2619,8 +2639,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
private static NativeStructureTerrainIntegrator.OrganicCarve organicCarve(
StructureStart start, int horizontalPadding, double lobeStrength) {
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setShape(IrisStructureCarveShape.ERODED)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setShape(StructureCarveShape.ERODED)
.setHorizontalPadding(horizontalPadding)
.setCeilingPadding(8)
.setFloorPadding(0)
@@ -2630,7 +2650,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
return NativeStructureTerrainIntegrator.organicCarve(
NativeStructureTerrainIntegrator.carveFootprint(start, horizontalPadding,
NativeStructurePostProcessorSurfaceTerrainTest::forbiddenTemplateManager),
terrain, IrisStructureCarveShape.ERODED, TEST_SEED);
terrain, StructureCarveShape.ERODED, TEST_SEED);
}
/**
@@ -2648,8 +2668,8 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
return true;
}, SLAB_PADDING, 1_000_000);
IrisStructureTerrain terrain = new IrisStructureTerrain()
.setMode(IrisStructureTerrainMode.FORCE_CARVE)
.setShape(IrisStructureCarveShape.ERODED)
.setMode(StructureTerrainMode.FORCE_CARVE)
.setShape(StructureCarveShape.ERODED)
.setHorizontalPadding(SLAB_PADDING)
.setCeilingPadding(8)
.setFloorPadding(0)
@@ -2664,7 +2684,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
NativeStructureTerrainIntegrator.carveOrganicColumns(world(blocks), area,
NativeStructureTerrainIntegrator.organicCarve(
footprint, terrain, IrisStructureCarveShape.ERODED, TEST_SEED));
footprint, terrain, StructureCarveShape.ERODED, TEST_SEED));
int[] depths = new int[SLAB_DEPTH];
for (int z = 0; z < SLAB_DEPTH; z++) {
@@ -1,5 +1,11 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureVerticalPlacer;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureSurfaceFitter;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureVegetationClearer;
import com.mojang.serialization.Codec;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
@@ -1,5 +1,9 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureTemplateOccupancy;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeStructureReflection;
import com.mojang.datafixers.util.Either;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
@@ -1,5 +1,7 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeTerrainHeightCache;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.levelgen.Heightmap;
@@ -1,10 +1,14 @@
package art.arcane.iris.nativegen.v26_3_R1;
import art.arcane.iris.world.history.TerrainNativeBlockKeys;
import art.arcane.volmlib.nativelib.v26_3_R1.terrain.NativeTransitionColumn;
import art.arcane.iris.world.history.BoundaryColumnGeometry;
import art.arcane.iris.world.history.TerrainBoundarySignature;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import art.arcane.iris.spi.PlatformRegistries;
import art.arcane.iris.testsupport.PlatformLeakGuard;
import net.minecraft.SharedConstants;
@@ -45,8 +49,8 @@ public final class NativeTransitionColumnTest {
@Test
public void customKeysUseCarrierForNativeColumnsAndHeightmaps() {
String key = "itemsadder:rocks/ruby_ore";
PlatformBlockState custom = mock(PlatformBlockState.class);
PlatformBlockState carrier = mock(PlatformBlockState.class);
NativeBlockState custom = mock(NativeBlockState.class);
NativeBlockState carrier = mock(NativeBlockState.class);
when(custom.placementBaseState()).thenReturn(carrier);
when(carrier.key()).thenReturn("minecraft:oak_log[axis=z]");
IrisPlatform platform = mock(IrisPlatform.class);
@@ -59,17 +63,17 @@ public final class NativeTransitionColumnTest {
try (MockedStatic<IrisPlatforms> platforms = mockStatic(IrisPlatforms.class)) {
platforms.when(IrisPlatforms::isBound).thenReturn(true);
platforms.when(IrisPlatforms::get).thenReturn(platform);
NoiseColumn column = NativeTransitionColumn.column(signature, height);
NoiseColumn column = NativeTransitionColumn.column(signature.geometry(), height, TerrainNativeBlockKeys::placementKey);
assertEquals(Direction.Axis.Z, column.getBlock(-15).getValue(RotatedPillarBlock.AXIS));
assertEquals(-14, NativeTransitionColumn.height(signature, Heightmap.Types.OCEAN_FLOOR_WG, height));
assertEquals(-14, NativeTransitionColumn.height(signature.geometry(), Heightmap.Types.OCEAN_FLOOR_WG, height, TerrainNativeBlockKeys::placementKey));
}
}
@Test
public void preservesNativePropertiesCavesAndFluids() {
NoiseColumn column = NativeTransitionColumn.column(signature("minecraft:oak_log[axis=x]"),
LevelHeightAccessor.create(-16, 32));
NoiseColumn column = NativeTransitionColumn.column(signature("minecraft:oak_log[axis=x]").geometry(),
LevelHeightAccessor.create(-16, 32), TerrainNativeBlockKeys::placementKey);
assertEquals(Direction.Axis.X, column.getBlock(-15).getValue(RotatedPillarBlock.AXIS));
assertTrue(column.getBlock(-14).is(Blocks.CAVE_AIR));
assertEquals(Integer.valueOf(3), column.getBlock(-13).getValue(LiquidBlock.LEVEL));
@@ -80,14 +84,13 @@ public final class NativeTransitionColumnTest {
public void usesNativeHeightmapPredicatesForWaterAndOpenSpace() {
TerrainBoundarySignature signature = signature("minecraft:oak_log[axis=x]");
LevelHeightAccessor height = LevelHeightAccessor.create(-16, 32);
assertEquals(-12, NativeTransitionColumn.height(signature, Heightmap.Types.WORLD_SURFACE_WG, height));
assertEquals(-14, NativeTransitionColumn.height(signature, Heightmap.Types.OCEAN_FLOOR_WG, height));
assertEquals(-12, NativeTransitionColumn.height(signature.geometry(), Heightmap.Types.WORLD_SURFACE_WG, height, TerrainNativeBlockKeys::placementKey));
assertEquals(-14, NativeTransitionColumn.height(signature.geometry(), Heightmap.Types.OCEAN_FLOOR_WG, height, TerrainNativeBlockKeys::placementKey));
}
@Test
public void rejectsUnknownSavedStatesInsteadOfReplacingTerrain() {
assertThrows(IllegalArgumentException.class, () -> NativeTransitionColumn.column(
signature("missing:removed_block"), LevelHeightAccessor.create(-16, 32)));
assertThrows(IllegalArgumentException.class, () -> NativeTransitionColumn.column(signature("missing:removed_block").geometry(), LevelHeightAccessor.create(-16, 32), TerrainNativeBlockKeys::placementKey));
}
private static TerrainBoundarySignature signature(String solidState) {
@@ -3,7 +3,7 @@ package art.arcane.iris.platform.bukkit.nms.v26_3_R1;
import art.arcane.iris.generation.biome.IrisBiomeCustom;
import art.arcane.iris.generation.biome.IrisBiomeCustomSpawn;
import art.arcane.iris.generation.biome.IrisBiomeCustomSpawnType;
import art.arcane.iris.platform.bukkit.nms.datapack.v263.DataFixerV263;
import art.arcane.iris.pack.datapack.v263.DataFixerV263;
import art.arcane.volmlib.util.collection.KList;
import com.google.gson.JsonParser;
import com.mojang.serialization.JsonOps;
@@ -1,5 +1,7 @@
package art.arcane.iris.platform.bukkit.nms.v26_3_R1;
import art.arcane.iris.platform.bukkit.nms.BukkitBiomePolicy;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.terrain.IrisDimension;
import net.minecraft.SharedConstants;
@@ -28,10 +30,10 @@ public class CustomBiomeSourceStructureContractTest {
IrisDimension upper = new IrisDimension();
upper.setLoadKey("Layers/Upper");
assertEquals("layers:upper/aurora", CustomBiomeSource.customBiomeKey(upper, "Aurora"));
assertEquals("layers:upper/aurora", BukkitBiomePolicy.customBiomeKey(upper, "Aurora"));
assertNotEquals(
CustomBiomeSource.customBiomeKey(host, "Aurora"),
CustomBiomeSource.customBiomeKey(upper, "Aurora")
BukkitBiomePolicy.customBiomeKey(host, "Aurora"),
BukkitBiomePolicy.customBiomeKey(upper, "Aurora")
);
}
@@ -39,8 +41,8 @@ public class CustomBiomeSourceStructureContractTest {
public void temporaryNaturalBiomeAnswersAreNotMemoized() {
Engine temporary = engineAnsweringNaturalFallback(true, 12, -8);
Engine stable = engineAnsweringNaturalFallback(false, 12, -8);
assertFalse(CustomBiomeSource.isBiomeCacheable(temporary, 12, -8));
assertTrue(CustomBiomeSource.isBiomeCacheable(stable, 12, -8));
assertFalse(BukkitBiomePolicy.isBiomeCacheable(temporary, 12, -8));
assertTrue(BukkitBiomePolicy.isBiomeCacheable(stable, 12, -8));
}
private static Engine engineAnsweringNaturalFallback(

Some files were not shown because too many files have changed in this diff Show More