mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-19 15:04:01 +00:00
[VERY VERY VERY BAD IDEA]
This commit is contained in:
-509
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-440
@@ -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) {
|
||||
}
|
||||
}
|
||||
-1643
File diff suppressed because it is too large
Load Diff
-1644
File diff suppressed because it is too large
Load Diff
-142
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-606
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-61
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
-182
@@ -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;
|
||||
}
|
||||
}
|
||||
-266
@@ -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) {
|
||||
}
|
||||
}
|
||||
-403
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-80
@@ -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);
|
||||
}
|
||||
}
|
||||
+9
-7
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
+17
-13
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+20
-6
@@ -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
|
||||
|
||||
+2
@@ -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;
|
||||
|
||||
+2
@@ -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;
|
||||
|
||||
+24
-25
@@ -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(
|
||||
|
||||
+37
-24
@@ -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")
|
||||
|
||||
+26
-16
@@ -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();
|
||||
|
||||
+6
-2
@@ -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(
|
||||
|
||||
+6
@@ -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;
|
||||
|
||||
+90
-70
@@ -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++) {
|
||||
|
||||
+6
@@ -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;
|
||||
|
||||
+4
@@ -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;
|
||||
|
||||
+2
@@ -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;
|
||||
|
||||
+14
-11
@@ -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) {
|
||||
|
||||
+7
-5
@@ -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
-1
@@ -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;
|
||||
|
||||
+10
-5
@@ -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);
|
||||
}
|
||||
|
||||
+25
-23
@@ -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(
|
||||
|
||||
+2
@@ -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;
|
||||
|
||||
+6
-4
@@ -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();
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
+4
-3
@@ -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);
|
||||
|
||||
+5
-4
@@ -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));
|
||||
|
||||
-114
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
-223
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
-135
@@ -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;
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -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));
|
||||
|
||||
+19
-19
@@ -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"));
|
||||
|
||||
+15
-8
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -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);
|
||||
}
|
||||
}
|
||||
-83
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
-31
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-78
@@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
-286
@@ -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
|
||||
) {
|
||||
}
|
||||
}
|
||||
-212
@@ -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) {
|
||||
}
|
||||
}
|
||||
-35
@@ -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) {
|
||||
}
|
||||
}
|
||||
-121
@@ -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;
|
||||
}
|
||||
}
|
||||
-351
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-58
@@ -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;
|
||||
}
|
||||
}
|
||||
-239
@@ -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) {
|
||||
}
|
||||
}
|
||||
-195
@@ -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);
|
||||
}
|
||||
}
|
||||
-66
@@ -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);
|
||||
}
|
||||
}
|
||||
-239
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-246
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
-386
@@ -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() {
|
||||
}
|
||||
}
|
||||
}
|
||||
-147
@@ -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
|
||||
) {
|
||||
}
|
||||
}
|
||||
-1221
File diff suppressed because it is too large
Load Diff
-305
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-165
@@ -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) {
|
||||
}
|
||||
}
|
||||
-228
@@ -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;
|
||||
}
|
||||
}
|
||||
-1096
File diff suppressed because it is too large
Load Diff
-224
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
-174
@@ -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) {
|
||||
}
|
||||
}
|
||||
-384
@@ -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;
|
||||
}
|
||||
}
|
||||
-643
@@ -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
|
||||
) {
|
||||
}
|
||||
}
|
||||
-42
@@ -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) {
|
||||
}
|
||||
}
|
||||
-63
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-113
@@ -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);
|
||||
}
|
||||
}
|
||||
-1287
File diff suppressed because it is too large
Load Diff
-513
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-441
@@ -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) {
|
||||
}
|
||||
}
|
||||
-132
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
-1621
File diff suppressed because it is too large
Load Diff
-1645
File diff suppressed because it is too large
Load Diff
-142
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-606
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-61
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
-182
@@ -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;
|
||||
}
|
||||
}
|
||||
-266
@@ -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) {
|
||||
}
|
||||
}
|
||||
-403
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-80
@@ -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);
|
||||
}
|
||||
}
|
||||
+9
-7
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
+17
-13
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+20
-6
@@ -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
|
||||
|
||||
+2
@@ -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;
|
||||
|
||||
+2
@@ -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;
|
||||
|
||||
+24
-25
@@ -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(
|
||||
|
||||
+37
-24
@@ -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")
|
||||
|
||||
+26
-16
@@ -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();
|
||||
|
||||
+6
-2
@@ -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(
|
||||
|
||||
+6
@@ -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;
|
||||
|
||||
+90
-70
@@ -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++) {
|
||||
|
||||
+6
@@ -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;
|
||||
|
||||
+4
@@ -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;
|
||||
|
||||
+2
@@ -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;
|
||||
|
||||
+14
-11
@@ -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) {
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+7
-5
@@ -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
Reference in New Issue
Block a user