mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-30 05:50:48 +00:00
Vwoop
This commit is contained in:
+28
-25
@@ -66,34 +66,33 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
this.cacheDimension = engine.getDimension();
|
||||
}
|
||||
|
||||
private static List<Holder<Biome>> getAllBiomes(Registry<Biome> customRegistry, Registry<Biome> registry, Engine engine, Holder<Biome> fallback) {
|
||||
private static List<Holder<Biome>> getAllBiomes(Registry<Biome> customRegistry, Registry<Biome> registry, Engine engine) {
|
||||
LinkedHashSet<Holder<Biome>> biomes = new LinkedHashSet<>();
|
||||
boolean resolutionFailed = false;
|
||||
|
||||
for (IrisBiome i : engine.getAllBiomes()) {
|
||||
Holder<Biome> vanillaHolder = NMSBinding.biomeToBiomeBase(registry, i.getVanillaDerivative());
|
||||
if (vanillaHolder != null) {
|
||||
biomes.add(vanillaHolder);
|
||||
} else if (!i.isCustom()) {
|
||||
resolutionFailed = true;
|
||||
if (vanillaHolder == null) {
|
||||
throw new IllegalStateException("Iris structure biome derivative '"
|
||||
+ i.getVanillaDerivativeKey() + "' is not registered for biome '" + i.getLoadKey() + "'");
|
||||
}
|
||||
biomes.add(vanillaHolder);
|
||||
|
||||
if (i.isCustom()) {
|
||||
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
|
||||
Holder<Biome> customHolder = resolveCustomBiomeHolder(customRegistry, engine, j.getId());
|
||||
if (customHolder != null) {
|
||||
biomes.add(customHolder);
|
||||
} else {
|
||||
resolutionFailed = true;
|
||||
if (customHolder == null) {
|
||||
throw new IllegalStateException("Iris custom structure biome '"
|
||||
+ engine.getDimension().getLoadKey() + ":" + j.getId() + "' is not registered");
|
||||
}
|
||||
biomes.add(customHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((resolutionFailed || biomes.isEmpty()) && fallback != null) {
|
||||
biomes.add(fallback);
|
||||
if (biomes.isEmpty()) {
|
||||
throw new IllegalStateException("Iris pack '" + engine.getName()
|
||||
+ "' has no registered structure biomes");
|
||||
}
|
||||
|
||||
return new ArrayList<>(biomes);
|
||||
}
|
||||
|
||||
@@ -159,7 +158,7 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
.lookup(Registries.BIOME).orElse(null);
|
||||
Registry<Biome> worldRegistry = ((CraftWorld) world).getHandle().registryAccess()
|
||||
.lookup(Registries.BIOME).orElse(null);
|
||||
return Set.copyOf(getAllBiomes(customRegistry, worldRegistry, engine, fallbackBiome));
|
||||
return Set.copyOf(getAllBiomes(customRegistry, worldRegistry, engine));
|
||||
}
|
||||
|
||||
private KMap<String, Holder<Biome>> fillCustomBiomes(Registry<Biome> customRegistry, Engine engine, Holder<Biome> fallback) {
|
||||
@@ -310,10 +309,16 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
int blockZ = z << 2;
|
||||
IrisBiome irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
|
||||
if (irisBiome == null) {
|
||||
return getFallbackBiome();
|
||||
throw new IllegalStateException("Iris returned no surface structure biome at block "
|
||||
+ blockX + "," + blockZ);
|
||||
}
|
||||
Holder<Biome> holder = NMSBinding.biomeToBiomeBase(biomeRegistry, irisBiome.getVanillaDerivative());
|
||||
return holder == null ? getFallbackBiome() : holder;
|
||||
if (holder == null) {
|
||||
throw new IllegalStateException("Iris structure biome derivative '"
|
||||
+ irisBiome.getVanillaDerivativeKey() + "' is not registered at block "
|
||||
+ blockX + "," + blockZ);
|
||||
}
|
||||
return holder;
|
||||
}
|
||||
|
||||
public Holder<Biome> getVisibleNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
|
||||
@@ -362,19 +367,17 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
private Holder<Biome> resolveStructureBiomeHolder(int x, int y, int z) {
|
||||
BiomeResolution resolution = resolveBiomeResolution(x, y, z);
|
||||
if (resolution == null) {
|
||||
return getFallbackBiome();
|
||||
throw new IllegalStateException("Iris returned no structure biome at quart "
|
||||
+ x + "," + y + "," + z);
|
||||
}
|
||||
|
||||
Holder<Biome> holder = NMSBinding.biomeToBiomeBase(biomeRegistry, resolution.irisBiome.getVanillaDerivative());
|
||||
if (holder != null) {
|
||||
return holder;
|
||||
if (holder == null) {
|
||||
throw new IllegalStateException("Iris structure biome derivative '"
|
||||
+ resolution.irisBiome.getVanillaDerivativeKey() + "' is not registered at block "
|
||||
+ resolution.blockX + "," + resolution.blockY + "," + resolution.blockZ);
|
||||
}
|
||||
|
||||
if (resolution.irisBiome.isCustom()) {
|
||||
return resolveCustomHolder(resolution);
|
||||
}
|
||||
|
||||
return getFallbackBiome();
|
||||
return holder;
|
||||
}
|
||||
|
||||
private Holder<Biome> resolveVisibleBiomeHolder(int x, int y, int z) {
|
||||
|
||||
+157
-108
@@ -2,12 +2,15 @@ package art.arcane.iris.core.nms.v26_2_R1;
|
||||
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
||||
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
|
||||
import art.arcane.iris.core.structure.NativeStructureLocateCapability;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisImportedStructureControl;
|
||||
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
|
||||
import art.arcane.iris.engine.object.IrisVanillaStructureStiltSettings;
|
||||
import art.arcane.iris.engine.object.IrisStructureStiltSettings;
|
||||
import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
|
||||
import art.arcane.iris.nativegen.NativeStructureGenerationException;
|
||||
import art.arcane.iris.nativegen.NativeStructureLocateResults;
|
||||
import art.arcane.iris.nativegen.NativeStructurePostProcessor;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.common.data.IrisCustomData;
|
||||
import art.arcane.iris.util.common.reflect.WrappedField;
|
||||
@@ -81,7 +84,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
private static final String NATIVE_MONUMENT_KEY = "minecraft:monument";
|
||||
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
|
||||
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
|
||||
private final ChunkGenerator delegate;
|
||||
@@ -109,74 +111,83 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
@Override
|
||||
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
|
||||
try {
|
||||
Registry<Structure> registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||
BlockPos best = null;
|
||||
Holder<Structure> bestHolder = null;
|
||||
long bestDist = Long.MAX_VALUE;
|
||||
for (Holder<Structure> holder : holders) {
|
||||
Object id = registry.getKey(holder.value());
|
||||
if (id == null) {
|
||||
continue;
|
||||
}
|
||||
IrisStructureLocator.LocateResult result = IrisStructureLocator.locate(
|
||||
engine, id.toString(), pos.getX(), pos.getZ(), Math.max(1, radius));
|
||||
if (result.status() == IrisStructureLocator.LocateStatus.SEARCH_LIMIT_REACHED) {
|
||||
continue;
|
||||
}
|
||||
if (!result.found()) {
|
||||
continue;
|
||||
}
|
||||
long dx = (long) result.originX() - pos.getX();
|
||||
long dz = (long) result.originZ() - pos.getZ();
|
||||
long d = dx * dx + dz * dz;
|
||||
if (d < bestDist) {
|
||||
bestDist = d;
|
||||
best = new BlockPos(result.originX(), result.baseY(), result.originZ());
|
||||
bestHolder = holder;
|
||||
}
|
||||
}
|
||||
if (best != null) {
|
||||
return Pair.of(best, bestHolder);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError("Iris-placed structure lookup failed near "
|
||||
+ pos.getX() + ", " + pos.getZ() + ".", e);
|
||||
}
|
||||
if (!importedControl().active()) {
|
||||
return null;
|
||||
}
|
||||
Pair<BlockPos, Holder<Structure>> irisPlaced = findNearestIrisStructure(
|
||||
level, holders, pos, Math.max(1, radius), findUnexplored);
|
||||
HolderSet<Structure> reachable = filterReachableStructures(level, holders);
|
||||
if (reachable == null || reachable.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return delegate.findNearestMapStructure(level, reachable, pos, radius, findUnexplored);
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError("Vanilla structure locate failed near "
|
||||
+ pos.getX() + ", " + pos.getZ() + ".", e);
|
||||
return null;
|
||||
}
|
||||
Pair<BlockPos, Holder<Structure>> nativeLocated = reachable == null || reachable.size() == 0
|
||||
? null
|
||||
: delegate.findNearestMapStructure(level, reachable, pos, radius, findUnexplored);
|
||||
return NativeStructureLocateResults.nearest(pos, irisPlaced, nativeLocated);
|
||||
}
|
||||
|
||||
private HolderSet<Structure> filterReachableStructures(ServerLevel level, HolderSet<Structure> holders) {
|
||||
Set<String> reachable = reachableStructureKeys(level);
|
||||
IrisImportedStructureControl control = importedControl();
|
||||
private Pair<BlockPos, Holder<Structure>> findNearestIrisStructure(ServerLevel level,
|
||||
HolderSet<Structure> holders,
|
||||
BlockPos pos, int radius,
|
||||
boolean findUnexplored) {
|
||||
if (findUnexplored) {
|
||||
return null;
|
||||
}
|
||||
Registry<Structure> registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||
List<Holder<Structure>> kept = new ArrayList<>(holders.size());
|
||||
BlockPos best = null;
|
||||
Holder<Structure> bestHolder = null;
|
||||
long bestDist = Long.MAX_VALUE;
|
||||
for (Holder<Structure> holder : holders) {
|
||||
Object id = registry.getKey(holder.value());
|
||||
if (id == null) {
|
||||
throw new IllegalStateException("Native structure locate received an unregistered structure holder");
|
||||
}
|
||||
String structureId = id.toString();
|
||||
IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(engine,
|
||||
structureId, NativeStructurePostProcessor.isUndergroundStep(holder.value().step()));
|
||||
if (decision.status() != NativeStructureGenerationStatus.REPLACED_BY_IRIS) {
|
||||
continue;
|
||||
}
|
||||
IrisStructureLocator.LocateResult result = IrisStructureLocator.locate(
|
||||
engine, structureId, pos.getX(), pos.getZ(), radius);
|
||||
if (result.status() == IrisStructureLocator.LocateStatus.SEARCH_LIMIT_REACHED) {
|
||||
throw new IllegalStateException("Iris structure locate reached its safety limit for "
|
||||
+ structureId + " within " + radius + " chunks");
|
||||
}
|
||||
if (!result.found()) {
|
||||
continue;
|
||||
}
|
||||
long dx = (long) result.originX() - pos.getX();
|
||||
long dz = (long) result.originZ() - pos.getZ();
|
||||
long d = dx * dx + dz * dz;
|
||||
if (d < bestDist) {
|
||||
bestDist = d;
|
||||
best = new BlockPos(result.originX(), result.baseY(), result.originZ());
|
||||
bestHolder = holder;
|
||||
}
|
||||
}
|
||||
return best == null ? null : Pair.of(best, bestHolder);
|
||||
}
|
||||
|
||||
private HolderSet<Structure> filterReachableStructures(ServerLevel level, HolderSet<Structure> holders) {
|
||||
Registry<Structure> registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||
List<NativeLocateCandidate> candidates = new ArrayList<>(holders.size());
|
||||
for (Holder<Structure> holder : holders) {
|
||||
Object id = registry.getKey(holder.value());
|
||||
if (id == null) {
|
||||
throw new IllegalStateException("Native structure filtering received an unregistered structure holder");
|
||||
}
|
||||
String key = id.toString();
|
||||
IrisNativeStructureDecision decision = control.resolve(
|
||||
IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(engine,
|
||||
key, NativeStructurePostProcessor.isUndergroundStep(holder.value().step()));
|
||||
if (NATIVE_MONUMENT_KEY.equals(key) || !decision.generate()
|
||||
|| IrisStructureLocator.suppressesVanilla(engine, key) || !reachable.contains(key)) {
|
||||
if (NativeStructureLocateCapability.isPaperUnavailable(key) || !decision.generate()) {
|
||||
continue;
|
||||
}
|
||||
kept.add(holder);
|
||||
candidates.add(new NativeLocateCandidate(holder, key));
|
||||
}
|
||||
if (candidates.isEmpty()) {
|
||||
return HolderSet.direct(List.of());
|
||||
}
|
||||
Set<String> reachable = reachableStructureKeys(level);
|
||||
List<Holder<Structure>> kept = new ArrayList<>(candidates.size());
|
||||
for (NativeLocateCandidate candidate : candidates) {
|
||||
if (reachable.contains(candidate.key())) {
|
||||
kept.add(candidate.holder());
|
||||
}
|
||||
}
|
||||
if (kept.size() == holders.size()) {
|
||||
return holders;
|
||||
@@ -195,14 +206,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
if (cached != null && cached.dimension() == dimension) {
|
||||
return cached.keys();
|
||||
}
|
||||
Set<String> reachable;
|
||||
try {
|
||||
reachable = Set.copyOf(VanillaStructureBiomes.reachableStructureKeys(level, customBiomeSource));
|
||||
} catch (Throwable error) {
|
||||
IrisLogging.reportError("Iris could not resolve native structure biome reachability; "
|
||||
+ "native locate is disabled until the next hotload.", error);
|
||||
reachable = Set.of();
|
||||
}
|
||||
Set<String> reachable = Set.copyOf(
|
||||
VanillaStructureBiomes.reachableStructureKeys(level, customBiomeSource));
|
||||
reachableStructureCache = new ReachableStructureCache(dimension, reachable);
|
||||
return reachable;
|
||||
}
|
||||
@@ -232,9 +237,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
@Override
|
||||
public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager, ResourceKey<Level> levelKey) {
|
||||
if (!importedControl().active()) {
|
||||
return;
|
||||
}
|
||||
Map<Structure, StructureStart> previousStarts = new HashMap<>(access.getAllStarts());
|
||||
super.createStructures(registryAccess, structureState, structureManager, access, templateManager, levelKey);
|
||||
adjustGeneratedStructures(registryAccess, access, previousStarts);
|
||||
@@ -242,7 +244,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
|
||||
private void adjustGeneratedStructures(RegistryAccess registryAccess, ChunkAccess access, Map<Structure, StructureStart> previousStarts) {
|
||||
Registry<Structure> registry = registryAccess.lookupOrThrow(Registries.STRUCTURE);
|
||||
IrisImportedStructureControl control = importedControl();
|
||||
ChunkPos chunkPos = access.getPos();
|
||||
for (Map.Entry<Structure, StructureStart> entry : access.getAllStarts().entrySet()) {
|
||||
Structure structure = entry.getKey();
|
||||
StructureStart start = entry.getValue();
|
||||
@@ -251,32 +253,40 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
}
|
||||
Identifier id = registry.getKey(structure);
|
||||
String structureId = id == null ? null : id.toString();
|
||||
IrisNativeStructureDecision decision = control.resolve(
|
||||
structureId, NativeStructurePostProcessor.isUndergroundStep(structure.step()));
|
||||
if (!decision.generate() || IrisStructureLocator.suppressesVanilla(engine, structureId)) {
|
||||
if (structureId == null) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"resolution", null, chunkPos.x(), chunkPos.z());
|
||||
}
|
||||
boolean undergroundStep = NativeStructurePostProcessor.isUndergroundStep(structure.step());
|
||||
IrisNativeStructureDecision decision;
|
||||
try {
|
||||
decision = NativeStructureGenerationPolicy.resolve(engine,
|
||||
structureId, undergroundStep);
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"policy resolution", structureId, chunkPos.x(), chunkPos.z(), error);
|
||||
}
|
||||
if (!decision.generate()) {
|
||||
access.setStartForStructure(structure, StructureStart.INVALID_START);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
NativeStructurePostProcessor.applyVerticalShift(
|
||||
NativeStructurePostProcessor.applyVerticalPlacement(
|
||||
start,
|
||||
structureId,
|
||||
decision.yShift(),
|
||||
getSeaLevel(),
|
||||
access.getMinY(),
|
||||
access.getMinY() + access.getHeight());
|
||||
} catch (RuntimeException error) {
|
||||
access.setStartForStructure(structure, StructureStart.INVALID_START);
|
||||
IrisLogging.reportError("Iris rejected native structure " + structureId + " in chunk "
|
||||
+ access.getPos().x() + "," + access.getPos().z()
|
||||
+ " because its vertical bounds are invalid.", error);
|
||||
continue;
|
||||
access.getMinY() + access.getHeight(),
|
||||
undergroundStep,
|
||||
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"vertical adjustment", structureId, chunkPos.x(), chunkPos.z(), error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IrisImportedStructureControl importedControl() {
|
||||
return engine.getDimension().getImportedStructures();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> holderlookup, RandomState randomstate, long i, SpigotWorldConfig conf) {
|
||||
return delegate.createState(holderlookup, randomstate, i, conf);
|
||||
@@ -347,15 +357,17 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
@Override
|
||||
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
|
||||
addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
|
||||
if (importedControl().active()) {
|
||||
placeVanillaStructures(generatoraccessseed, ichunkaccess, structuremanager);
|
||||
}
|
||||
placeVanillaStructures(generatoraccessseed, ichunkaccess, structuremanager);
|
||||
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false);
|
||||
}
|
||||
|
||||
private void placeVanillaStructures(WorldGenLevel world, ChunkAccess chunk, StructureManager structureManager) {
|
||||
if (!structureManager.shouldGenerateStructures()) {
|
||||
return;
|
||||
ChunkPos disabledChunk = chunk.getPos();
|
||||
throw new IllegalStateException("Iris cannot generate native structures in chunk "
|
||||
+ disabledChunk.x() + "," + disabledChunk.z()
|
||||
+ " because structure generation is disabled outside the pack; enable native structure generation "
|
||||
+ "and deny individual structures through importedStructures.disabled");
|
||||
}
|
||||
ChunkPos chunkPos = chunk.getPos();
|
||||
SectionPos sectionPos = SectionPos.of(chunkPos, world.getMinSectionY());
|
||||
@@ -366,42 +378,60 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
long decoSeed = random.setDecorationSeed(world.getSeed(), origin.getX(), origin.getZ());
|
||||
BoundingBox area = writableArea(chunk);
|
||||
int steps = GenerationStep.Decoration.values().length;
|
||||
IrisImportedStructureControl control = importedControl();
|
||||
List<NativePlacementGroup> placementGroups = new ArrayList<>();
|
||||
List<StructureStart> nativeStarts = new ArrayList<>();
|
||||
List<NativeStructurePostProcessor.VegetationTarget> vegetationTargets = new ArrayList<>();
|
||||
for (int step = 0; step < steps; step++) {
|
||||
int index = 0;
|
||||
for (Structure structure : byStep.get(step)) {
|
||||
Object id = registry.getKey(structure);
|
||||
String structureId = id == null ? null : id.toString();
|
||||
IrisNativeStructureDecision decision = control.resolve(
|
||||
structureId, NativeStructurePostProcessor.isUndergroundStep(structure.step()));
|
||||
if (decision.generate() && !IrisStructureLocator.suppressesVanilla(engine, structureId)) {
|
||||
try {
|
||||
if (structureId == null) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"resolution", null, chunkPos.x(), chunkPos.z());
|
||||
}
|
||||
try {
|
||||
IrisNativeStructureDecision decision = NativeStructureGenerationPolicy.resolve(engine,
|
||||
structureId, NativeStructurePostProcessor.isUndergroundStep(structure.step()));
|
||||
if (decision.generate()) {
|
||||
List<StructureStart> starts = structureManager.startsForStructure(sectionPos, structure);
|
||||
if (!starts.isEmpty()) {
|
||||
List<StructureStart> resolvedStarts = List.copyOf(starts);
|
||||
placementGroups.add(new NativePlacementGroup(
|
||||
structureId, decision, index, step, resolvedStarts));
|
||||
nativeStarts.addAll(resolvedStarts);
|
||||
boolean clearEntireFootprint = NativeStructurePostProcessor
|
||||
.shouldClearEntireVegetationFootprint(
|
||||
structure.step(), decision.clearVegetation());
|
||||
for (StructureStart start : resolvedStarts) {
|
||||
vegetationTargets.add(new NativeStructurePostProcessor.VegetationTarget(
|
||||
start, decision.clearVegetation()));
|
||||
start, clearEntireFootprint));
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError("Iris failed to resolve native structure " + structureId
|
||||
+ " in chunk " + chunkPos.x() + "," + chunkPos.z() + ".", e);
|
||||
}
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"resolution", structureId, chunkPos.x(), chunkPos.z(), error);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
try {
|
||||
NativeStructurePostProcessor.prepareSurfaceStructures(
|
||||
world, area, nativeStarts,
|
||||
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"terrain integration", nativeStructureBatchContext(placementGroups),
|
||||
chunkPos.x(), chunkPos.z(), error);
|
||||
}
|
||||
try {
|
||||
NativeStructurePostProcessor.clearIntersectingVegetation(
|
||||
world, chunk, area, vegetationTargets);
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError("Iris failed to clear vegetation from native structures in chunk "
|
||||
+ chunkPos.x() + "," + chunkPos.z() + ".", e);
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"vegetation cleanup", nativeStructureBatchContext(placementGroups),
|
||||
chunkPos.x(), chunkPos.z(), error);
|
||||
}
|
||||
for (NativePlacementGroup group : placementGroups) {
|
||||
random.setFeatureSeed(decoSeed, group.featureIndex(), group.step());
|
||||
@@ -410,13 +440,27 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
placeVanillaStructure(world, structureManager, random, area, chunkPos,
|
||||
group.structureId(), start, group.decision());
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError("Iris failed to place native structure " + group.structureId()
|
||||
+ " in chunk " + chunkPos.x() + "," + chunkPos.z() + ".", e);
|
||||
} catch (Throwable error) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"placement", group.structureId(), chunkPos.x(), chunkPos.z(), error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String nativeStructureBatchContext(List<NativePlacementGroup> placementGroups) {
|
||||
if (placementGroups.isEmpty()) {
|
||||
return "<no resolved native structures>";
|
||||
}
|
||||
StringBuilder context = new StringBuilder("[");
|
||||
for (int i = 0; i < placementGroups.size(); i++) {
|
||||
if (i > 0) {
|
||||
context.append(", ");
|
||||
}
|
||||
context.append(placementGroups.get(i).structureId());
|
||||
}
|
||||
return context.append(']').toString();
|
||||
}
|
||||
|
||||
private void placeVanillaStructure(WorldGenLevel world, StructureManager structureManager, WorldgenRandom random,
|
||||
BoundingBox area, ChunkPos chunkPos, String structureId, StructureStart start,
|
||||
IrisNativeStructureDecision decision) {
|
||||
@@ -451,13 +495,14 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private BlockState resolveStiltBlock(IrisVanillaStructureStiltSettings settings, RNG rng, int x, int y, int z) {
|
||||
private BlockState resolveStiltBlock(IrisStructureStiltSettings settings, RNG rng, int x, int y, int z) {
|
||||
if (settings.getPalette() == null) {
|
||||
return Blocks.COBBLESTONE.defaultBlockState();
|
||||
}
|
||||
PlatformBlockState platformState = settings.getPalette().get(rng, x, y, z, engine.getData());
|
||||
if (platformState == null || !(platformState.nativeHandle() instanceof BlockData blockData)) {
|
||||
return Blocks.COBBLESTONE.defaultBlockState();
|
||||
throw new IllegalStateException("Configured native structure stilt palette did not resolve a Bukkit block at "
|
||||
+ x + "," + y + "," + z);
|
||||
}
|
||||
if (blockData instanceof IrisCustomData customData) {
|
||||
blockData = customData.getBase();
|
||||
@@ -465,7 +510,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
if (blockData instanceof CraftBlockData craftBlockData) {
|
||||
return craftBlockData.getState();
|
||||
}
|
||||
return Blocks.COBBLESTONE.defaultBlockState();
|
||||
throw new IllegalStateException("Configured native structure stilt palette resolved unsupported Bukkit block data "
|
||||
+ blockData.getClass().getName() + " at " + x + "," + y + "," + z);
|
||||
}
|
||||
|
||||
private BoundingBox writableArea(ChunkAccess chunk) {
|
||||
@@ -616,4 +662,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
|
||||
private record NativePlacementGroup(String structureId, IrisNativeStructureDecision decision,
|
||||
int featureIndex, int step, List<StructureStart> starts) {
|
||||
}
|
||||
|
||||
private record NativeLocateCandidate(Holder<Structure> holder, String key) {
|
||||
}
|
||||
}
|
||||
|
||||
+27
-14
@@ -15,6 +15,7 @@ import art.arcane.iris.engine.data.chunk.TerrainChunk;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisDimensionRuntimeContract;
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
import art.arcane.iris.nativegen.NativeStructureGenerationException;
|
||||
import art.arcane.iris.util.project.agent.Agent;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
@@ -396,6 +397,11 @@ public class NMSBinding implements INMSBinding {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsIrisWorldGeneration() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTrueBiomeBaseId(Object biomeBase) {
|
||||
return getCustomBiomeRegistry().getId(((Holder<net.minecraft.world.level.biome.Biome>) biomeBase).value());
|
||||
@@ -470,8 +476,8 @@ public class NMSBinding implements INMSBinding {
|
||||
KList<String> keys = new KList<>();
|
||||
try {
|
||||
registry().lookupOrThrow(Registries.STRUCTURE).keySet().forEach(k -> keys.add(k.toString()));
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
} catch (RuntimeException e) {
|
||||
throw new IllegalStateException("Iris failed to read registered structure keys from the Minecraft registry", e);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
@@ -481,8 +487,8 @@ public class NMSBinding implements INMSBinding {
|
||||
KList<String> keys = new KList<>();
|
||||
try {
|
||||
registry().lookupOrThrow(Registries.STRUCTURE_SET).keySet().forEach(k -> keys.add(k.toString()));
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
} catch (RuntimeException e) {
|
||||
throw new IllegalStateException("Iris failed to read registered structure-set keys from the Minecraft registry", e);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
@@ -494,8 +500,9 @@ public class NMSBinding implements INMSBinding {
|
||||
ServerLevel level = ((CraftWorld) world).getHandle();
|
||||
BiomeSource source = level.getChunkSource().getGenerator().getBiomeSource();
|
||||
keys.addAll(VanillaStructureBiomes.reachableStructureKeys(level, source));
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
} catch (RuntimeException e) {
|
||||
throw new IllegalStateException("Iris failed to resolve reachable structures for Bukkit world '"
|
||||
+ (world == null ? "<null>" : world.getName()) + "'", e);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
@@ -504,9 +511,14 @@ public class NMSBinding implements INMSBinding {
|
||||
public KList<String> getStructureBiomeKeys(String structureKey) {
|
||||
KList<String> keys = new KList<>();
|
||||
try {
|
||||
keys.addAll(VanillaStructureBiomes.structureBiomeKeys(registry(), structureKey));
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
RegistryAccess access = registry();
|
||||
if (access == null) {
|
||||
throw new IllegalStateException("Minecraft registry access is unavailable");
|
||||
}
|
||||
keys.addAll(VanillaStructureBiomes.structureBiomeKeys(access, structureKey));
|
||||
} catch (RuntimeException e) {
|
||||
throw new IllegalStateException("Iris failed to resolve biome keys for registered structure '"
|
||||
+ structureKey + "'", e);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
@@ -518,8 +530,9 @@ public class NMSBinding implements INMSBinding {
|
||||
ServerLevel level = ((CraftWorld) world).getHandle();
|
||||
BiomeSource source = level.getChunkSource().getGenerator().getBiomeSource();
|
||||
keys.addAll(VanillaStructureBiomes.possibleBiomeKeys(source));
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
} catch (RuntimeException e) {
|
||||
throw new IllegalStateException("Iris failed to resolve possible structure biome keys for Bukkit world '"
|
||||
+ (world == null ? "<null>" : world.getName()) + "'", e);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
@@ -636,9 +649,9 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
}
|
||||
return new int[]{box.minX(), box.minY(), box.minZ(), box.maxX(), box.maxY(), box.maxZ()};
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
return null;
|
||||
} catch (RuntimeException e) {
|
||||
throw NativeStructureGenerationException.failure(
|
||||
"capture placement", structureKey, chunkX, chunkZ, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-12
@@ -21,10 +21,10 @@ final class VanillaStructureBiomes {
|
||||
}
|
||||
|
||||
static Set<String> possibleBiomeKeys(BiomeSource source) {
|
||||
Set<String> keys = new LinkedHashSet<>();
|
||||
if (source == null) {
|
||||
return keys;
|
||||
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();
|
||||
@@ -34,18 +34,24 @@ final class VanillaStructureBiomes {
|
||||
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) {
|
||||
Set<String> keys = new LinkedHashSet<>();
|
||||
if (access == null || structureKey == null || structureKey.isEmpty()) {
|
||||
return keys;
|
||||
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) {
|
||||
return keys;
|
||||
throw new IllegalArgumentException("Registered structure does not exist: " + structureKey);
|
||||
}
|
||||
for (Holder<Biome> holder : structure.biomes()) {
|
||||
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
|
||||
@@ -53,18 +59,19 @@ final class VanillaStructureBiomes {
|
||||
keys.add(key.get().identifier().toString());
|
||||
}
|
||||
}
|
||||
if (keys.isEmpty()) {
|
||||
throw new IllegalStateException("Registered structure '" + structureKey
|
||||
+ "' exposes no 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> reachable = new LinkedHashSet<>();
|
||||
if (level == null || source == null) {
|
||||
return reachable;
|
||||
}
|
||||
Set<String> possible = possibleBiomeKeys(source);
|
||||
if (possible.isEmpty()) {
|
||||
return reachable;
|
||||
}
|
||||
Registry<Structure> registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
|
||||
for (Map.Entry<ResourceKey<Structure>, Structure> entry : registry.entrySet()) {
|
||||
for (Holder<Biome> holder : entry.getValue().biomes()) {
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package art.arcane.iris.core.nms.v26_2_R1;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisChunkGeneratorFailureContractTest {
|
||||
@Test
|
||||
public void structureLocateDoesNotCatchAndFallThroughToAnotherImplementation() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
|
||||
int locateStart = source.indexOf("public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure");
|
||||
int locateEnd = source.indexOf("private HolderSet<Structure> filterReachableStructures", locateStart);
|
||||
String locate = source.substring(locateStart, locateEnd);
|
||||
int reachabilityStart = source.indexOf("private Set<String> reachableStructureKeys");
|
||||
int reachabilityEnd = source.indexOf("protected MapCodec", reachabilityStart);
|
||||
String reachability = source.substring(reachabilityStart, reachabilityEnd);
|
||||
|
||||
assertTrue(locate.contains("reached its safety limit"));
|
||||
assertTrue(locate.contains("unregistered structure holder"));
|
||||
assertFalse(locate.contains("catch (Throwable"));
|
||||
assertFalse(locate.contains("IrisLogging.reportError"));
|
||||
assertFalse(reachability.contains("catch (Throwable"));
|
||||
assertFalse(reachability.contains("reachable = Set.of()"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void structureGenerationAbortsInsteadOfLoggingAndContinuing() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
|
||||
int adjustmentStart = source.indexOf("private void adjustGeneratedStructures");
|
||||
int adjustmentEnd = source.indexOf("public ChunkGeneratorStructureState createState", adjustmentStart);
|
||||
String adjustment = source.substring(adjustmentStart, adjustmentEnd);
|
||||
int placementStart = source.indexOf("private void placeVanillaStructures");
|
||||
int placementEnd = source.indexOf("private void placeVanillaStructure(", placementStart + 1);
|
||||
String placement = source.substring(placementStart, placementEnd);
|
||||
|
||||
assertTrue(adjustment.contains("NativeStructureGenerationException.failure("));
|
||||
assertTrue(adjustment.contains("\"vertical adjustment\""));
|
||||
assertFalse(adjustment.contains("IrisLogging.reportError"));
|
||||
assertTrue(placement.contains("\"resolution\""));
|
||||
assertTrue(placement.contains("\"terrain integration\""));
|
||||
assertTrue(placement.contains("\"vegetation cleanup\""));
|
||||
assertTrue(placement.contains("\"placement\""));
|
||||
assertTrue(placement.contains("because structure generation is disabled outside the pack"));
|
||||
assertTrue(placement.contains("prepareSurfaceStructures"));
|
||||
assertTrue(placement.contains("clearIntersectingVegetation"));
|
||||
assertTrue(placement.indexOf("prepareSurfaceStructures")
|
||||
< placement.indexOf("clearIntersectingVegetation"));
|
||||
assertTrue(placement.indexOf("clearIntersectingVegetation")
|
||||
< placement.indexOf("for (NativePlacementGroup group"));
|
||||
assertFalse(placement.contains("IrisLogging.reportError"));
|
||||
}
|
||||
}
|
||||
+80
-24
@@ -1,5 +1,10 @@
|
||||
package art.arcane.iris.core.nms.v26_2_R1;
|
||||
|
||||
import art.arcane.iris.nativegen.NativeStructureLocateResults;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -8,35 +13,70 @@ import java.nio.file.Path;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
public class IrisChunkGeneratorMonumentLocateContractTest {
|
||||
@Test
|
||||
public void irisPlacementRunsBeforeExactNativeMonumentIsRemovedFromDelegateLookup() throws IOException {
|
||||
public void nativeLocateAllowsExplicitReplacementBeforeNativeCapabilityGate() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.nmsChunkGeneratorSource")));
|
||||
int findStart = source.indexOf("findNearestMapStructure(ServerLevel level");
|
||||
assertTrue(findStart >= 0);
|
||||
int filterStart = source.indexOf("private HolderSet<Structure> filterReachableStructures", findStart);
|
||||
assertTrue(filterStart > findStart);
|
||||
String findMethod = source.substring(findStart, filterStart);
|
||||
int irisLocate = findMethod.indexOf("IrisStructureLocator.locate(");
|
||||
int searchLimit = findMethod.indexOf("LocateStatus.SEARCH_LIMIT_REACHED", irisLocate);
|
||||
int limitSkip = findMethod.indexOf("continue;", searchLimit);
|
||||
int nativeFilter = findMethod.indexOf("filterReachableStructures(level, holders)");
|
||||
int delegateLocate = findMethod.indexOf("delegate.findNearestMapStructure(level, reachable");
|
||||
int irisHelperStart = source.indexOf("private Pair<BlockPos, Holder<Structure>> findNearestIrisStructure(", findStart);
|
||||
int filterStart = source.indexOf("private HolderSet<Structure> filterReachableStructures", irisHelperStart);
|
||||
assertTrue(irisHelperStart > findStart);
|
||||
assertTrue(filterStart > irisHelperStart);
|
||||
String outerMethod = source.substring(findStart, irisHelperStart);
|
||||
String irisHelper = source.substring(irisHelperStart, filterStart);
|
||||
int policyResolution = irisHelper.indexOf("NativeStructureGenerationPolicy.resolve(engine,");
|
||||
int unexploredGuard = irisHelper.indexOf("if (findUnexplored)");
|
||||
int registryLookup = irisHelper.indexOf("level.registryAccess().lookupOrThrow(Registries.STRUCTURE)");
|
||||
int replacementCheck = irisHelper.indexOf(
|
||||
"decision.status() != NativeStructureGenerationStatus.REPLACED_BY_IRIS");
|
||||
int irisLocate = irisHelper.indexOf("IrisStructureLocator.locate(", replacementCheck);
|
||||
int searchLimit = irisHelper.indexOf("LocateStatus.SEARCH_LIMIT_REACHED", irisLocate);
|
||||
int limitSkip = irisHelper.indexOf("continue;", searchLimit);
|
||||
int nativeFilter = outerMethod.indexOf("filterReachableStructures(level, holders)");
|
||||
int delegateLocate = outerMethod.indexOf("delegate.findNearestMapStructure(level, reachable");
|
||||
int nearestSelection = outerMethod.indexOf(
|
||||
"NativeStructureLocateResults.nearest(pos, irisPlaced, nativeLocated)");
|
||||
int reachabilityStart = source.indexOf("private Set<String> reachableStructureKeys", filterStart);
|
||||
assertTrue(reachabilityStart > filterStart);
|
||||
String filterMethod = source.substring(filterStart, reachabilityStart);
|
||||
int monumentReject = filterMethod.indexOf("if (NATIVE_MONUMENT_KEY.equals(key)");
|
||||
int monumentReject = filterMethod.indexOf("if (NativeStructureLocateCapability.isPaperUnavailable(key)");
|
||||
int rejectContinue = filterMethod.indexOf("continue;", monumentReject);
|
||||
int emptyNativePartition = filterMethod.indexOf("if (candidates.isEmpty())", rejectContinue);
|
||||
int reachabilityLookup = filterMethod.indexOf("reachableStructureKeys(level)", emptyNativePartition);
|
||||
|
||||
assertTrue(irisLocate >= 0);
|
||||
assertTrue(policyResolution >= 0);
|
||||
assertTrue(unexploredGuard >= 0);
|
||||
assertTrue(registryLookup > unexploredGuard);
|
||||
assertTrue(policyResolution > registryLookup);
|
||||
assertTrue(replacementCheck > policyResolution);
|
||||
assertTrue(irisLocate > replacementCheck);
|
||||
assertTrue(searchLimit > irisLocate);
|
||||
assertTrue(limitSkip > searchLimit);
|
||||
assertTrue(nativeFilter > limitSkip);
|
||||
assertTrue(nativeFilter >= 0);
|
||||
assertTrue(delegateLocate > nativeFilter);
|
||||
assertTrue(nearestSelection > delegateLocate);
|
||||
assertTrue(monumentReject >= 0);
|
||||
assertTrue(rejectContinue > monumentReject);
|
||||
assertTrue(findMethod.contains("new BlockPos(result.originX(), result.baseY(), result.originZ())"));
|
||||
assertTrue(emptyNativePartition > rejectContinue);
|
||||
assertTrue(reachabilityLookup > emptyNativePartition);
|
||||
assertFalse(irisHelper.contains("NativeStructureLocateCapability.isPaperUnavailable(structureId)"));
|
||||
assertTrue(irisHelper.contains("new BlockPos(result.originX(), result.baseY(), result.originZ())"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mixedLocateSelectsNearestProviderAndPrefersNativeOnTie() {
|
||||
BlockPos origin = BlockPos.ZERO;
|
||||
Pair<BlockPos, Holder<Structure>> irisNear = Pair.of(new BlockPos(4, 70, 0), null);
|
||||
Pair<BlockPos, Holder<Structure>> nativeFar = Pair.of(new BlockPos(8, 70, 0), null);
|
||||
Pair<BlockPos, Holder<Structure>> nativeNear = Pair.of(new BlockPos(2, 70, 0), null);
|
||||
Pair<BlockPos, Holder<Structure>> nativeTie = Pair.of(new BlockPos(0, 70, 4), null);
|
||||
|
||||
assertSame(irisNear, NativeStructureLocateResults.nearest(origin, irisNear, nativeFar));
|
||||
assertSame(nativeNear, NativeStructureLocateResults.nearest(origin, irisNear, nativeNear));
|
||||
assertSame(nativeTie, NativeStructureLocateResults.nearest(origin, irisNear, nativeTie));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -54,18 +94,34 @@ public class IrisChunkGeneratorMonumentLocateContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verticalShiftMovesPiecesJigsawJunctionsAndCachedBoundsTogether() throws IOException {
|
||||
public void verticalPlacementMovesPiecesMonumentChildrenJigsawJunctionsAndCachedBoundsTogether() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.nativeStructurePostProcessorSource")));
|
||||
int method = source.indexOf("public static int applyVerticalShift");
|
||||
int clamp = source.indexOf("StructureVerticalBounds.clampOffset", method);
|
||||
int pieceMove = source.indexOf("piece.move(0, offsetY, 0)", clamp);
|
||||
int junctionMove = source.indexOf("junction.getSourceGroundY() + offsetY", pieceMove);
|
||||
int boundsMove = source.indexOf("bounds.move(0, offsetY, 0)", junctionMove);
|
||||
int placementStart = source.indexOf("public static int applyVerticalPlacement");
|
||||
int shiftStart = source.indexOf("public static int applyVerticalShift", placementStart);
|
||||
int alignmentStart = source.indexOf("static int alignOceanMonumentToSeaLevel", shiftStart);
|
||||
int moveStart = source.indexOf("private static void moveStructureStart", alignmentStart);
|
||||
int movePieceStart = source.indexOf("private static void moveStructurePiece", moveStart);
|
||||
int monumentChildrenStart = source.indexOf("static List<StructurePiece> monumentChildPieces", movePieceStart);
|
||||
|
||||
assertTrue(method >= 0);
|
||||
assertTrue(clamp > method);
|
||||
assertTrue(pieceMove > clamp);
|
||||
assertTrue(junctionMove > pieceMove);
|
||||
assertTrue(boundsMove > junctionMove);
|
||||
assertTrue(placementStart >= 0);
|
||||
assertTrue(shiftStart > placementStart);
|
||||
assertTrue(alignmentStart > shiftStart);
|
||||
assertTrue(moveStart > alignmentStart);
|
||||
assertTrue(movePieceStart > moveStart);
|
||||
assertTrue(monumentChildrenStart > movePieceStart);
|
||||
String placementMethod = source.substring(placementStart, shiftStart);
|
||||
String shiftMethod = source.substring(shiftStart, alignmentStart);
|
||||
String moveMethod = source.substring(moveStart, movePieceStart);
|
||||
String movePieceMethod = source.substring(movePieceStart, monumentChildrenStart);
|
||||
|
||||
assertTrue(placementMethod.contains("return alignOceanMonumentToSeaLevel("));
|
||||
assertTrue(placementMethod.contains("return applyVerticalShift("));
|
||||
assertTrue(shiftMethod.contains("StructureVerticalBounds.clampOffset"));
|
||||
assertTrue(shiftMethod.contains("moveStructureStart(start, bounds, offsetY)"));
|
||||
assertTrue(moveMethod.contains("moveStructurePiece(piece, offsetY)"));
|
||||
assertTrue(moveMethod.contains("cachedBounds.move(0, offsetY, 0)"));
|
||||
assertTrue(movePieceMethod.contains("piece.move(0, offsetY, 0)"));
|
||||
assertTrue(movePieceMethod.contains("child.move(0, offsetY, 0)"));
|
||||
assertTrue(movePieceMethod.contains("junction.getSourceGroundY() + offsetY"));
|
||||
}
|
||||
}
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package art.arcane.iris.core.nms.v26_2_R1;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NMSBindingPlacementFailureContractTest {
|
||||
@Test
|
||||
public void structurePlacementPropagatesRuntimeFailuresWithContext() throws IOException {
|
||||
Path chunkGeneratorSource = Path.of(System.getProperty("iris.nmsChunkGeneratorSource"));
|
||||
String source = Files.readString(chunkGeneratorSource.resolveSibling("NMSBinding.java"));
|
||||
int methodStart = source.indexOf("public int[] placeStructure(");
|
||||
int methodEnd = source.indexOf("\n @Override", methodStart + 1);
|
||||
String method = source.substring(methodStart, methodEnd);
|
||||
int catchStart = method.indexOf("catch (RuntimeException e)");
|
||||
|
||||
assertTrue(catchStart >= 0);
|
||||
String failurePath = method.substring(catchStart);
|
||||
assertTrue(failurePath.contains("throw NativeStructureGenerationException.failure("));
|
||||
assertTrue(failurePath.contains("\"capture placement\", structureKey, chunkX, chunkZ, e"));
|
||||
assertFalse(failurePath.contains("return null;"));
|
||||
assertFalse(failurePath.contains("catch (Throwable"));
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package art.arcane.iris.core.nms.v26_2_R1;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NMSBindingStructureFailureContractTest {
|
||||
@Test
|
||||
public void structureRegistryHooksThrowInsteadOfReturningEmptyOnFailure() throws IOException {
|
||||
Path chunkGeneratorSource = Path.of(System.getProperty("iris.nmsChunkGeneratorSource"));
|
||||
String source = Files.readString(chunkGeneratorSource.resolveSibling("NMSBinding.java"));
|
||||
List<String> methodNames = List.of(
|
||||
"getStructureKeys",
|
||||
"getStructureSetKeys",
|
||||
"getReachableStructureKeys",
|
||||
"getStructureBiomeKeys",
|
||||
"getPossibleBiomeKeys");
|
||||
|
||||
for (String methodName : methodNames) {
|
||||
String method = methodSource(source, methodName);
|
||||
assertTrue(methodName + " must preserve failure context", method.contains("throw new IllegalStateException("));
|
||||
assertTrue(methodName + " must retain its root cause", method.contains(", e);"));
|
||||
assertFalse(methodName + " must not log and return an empty registry result",
|
||||
method.contains("IrisLogging.reportError"));
|
||||
assertFalse(methodName + " must not catch fatal JVM errors", method.contains("catch (Throwable"));
|
||||
}
|
||||
}
|
||||
|
||||
private static String methodSource(String source, String methodName) {
|
||||
int start = source.indexOf("public KList<String> " + methodName + "(");
|
||||
int end = source.indexOf("\n @Override", start + 1);
|
||||
if (start < 0 || end < 0) {
|
||||
throw new IllegalArgumentException("Unable to find NMS binding method " + methodName);
|
||||
}
|
||||
return source.substring(start, end);
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package art.arcane.iris.nativegen;
|
||||
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.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 net.minecraft.world.level.levelgen.structure.structures.OceanMonumentPieces;
|
||||
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentStructure;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NativeStructurePostProcessorMonumentTest {
|
||||
@BeforeClass
|
||||
public static void bootstrapMinecraftRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void vanillaSeaLevelKeepsTheVanillaMonumentHeight() {
|
||||
StructureStart start = monumentStart(1337L);
|
||||
|
||||
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
|
||||
start, "minecraft:monument", 0, 63, -64, 320, false, (x, z) -> 0);
|
||||
|
||||
assertEquals(0, offset);
|
||||
assertEquals(39, start.getBoundingBox().minY());
|
||||
assertEquals(61, start.getBoundingBox().maxY());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shiftedSeaLevelMovesTheShellAndEveryRoomTogether() {
|
||||
StructureStart start = monumentStart(1337L);
|
||||
OceanMonumentPieces.MonumentBuilding building = monumentBuilding(start);
|
||||
List<StructurePiece> children = NativeStructurePostProcessor.monumentChildPieces(building);
|
||||
int[] childMinY = minimumYs(children);
|
||||
BoundingBox cachedBounds = start.getBoundingBox();
|
||||
|
||||
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
|
||||
start, "minecraft:monument", 0, 50, -256, 512, false, (x, z) -> 0);
|
||||
|
||||
assertEquals(-13, offset);
|
||||
assertSame(cachedBounds, start.getBoundingBox());
|
||||
assertEquals(26, building.getBoundingBox().minY());
|
||||
assertEquals(48, building.getBoundingBox().maxY());
|
||||
assertEquals(26, start.getBoundingBox().minY());
|
||||
assertEquals(48, start.getBoundingBox().maxY());
|
||||
assertTrue(children.size() > 20);
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
assertEquals(childMinY[i] - 13, children.get(i).getBoundingBox().minY());
|
||||
}
|
||||
|
||||
int repeatedOffset = NativeStructurePostProcessor.applyVerticalPlacement(
|
||||
start, "minecraft:monument", 0, 50, -256, 512, false, (x, z) -> 0);
|
||||
assertEquals(0, repeatedOffset);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configuredOffsetIsRelativeToTheActualSeaLevel() {
|
||||
StructureStart start = monumentStart(1337L);
|
||||
|
||||
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
|
||||
start, "minecraft:monument", 3, 50, -256, 512, false, (x, z) -> 0);
|
||||
|
||||
assertEquals(-10, offset);
|
||||
assertEquals(29, start.getBoundingBox().minY());
|
||||
assertEquals(51, start.getBoundingBox().maxY());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void vanillaReloadRegenerationIsRealignedBeforePlacement() {
|
||||
long seed = 1337L;
|
||||
ChunkPos chunkPos = new ChunkPos(0, 0);
|
||||
StructureStart initial = monumentStart(seed);
|
||||
NativeStructurePostProcessor.applyVerticalPlacement(
|
||||
initial, "minecraft:monument", 0, 50, -256, 512, false, (x, z) -> 0);
|
||||
|
||||
PiecesContainer regenerated = OceanMonumentStructure.regeneratePiecesAfterLoad(
|
||||
chunkPos, seed, new PiecesContainer(initial.getPieces()));
|
||||
StructureStart reloaded = new StructureStart(
|
||||
monumentStructure(), chunkPos, 0, regenerated);
|
||||
|
||||
assertEquals(39, reloaded.getBoundingBox().minY());
|
||||
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
|
||||
reloaded, "minecraft:monument", 0, 50, -256, 512, false, (x, z) -> 0);
|
||||
assertEquals(-13, offset);
|
||||
assertEquals(26, reloaded.getBoundingBox().minY());
|
||||
assertEquals(48, reloaded.getBoundingBox().maxY());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void impossibleSeaLevelAlignmentFailsInsteadOfClippingTheMonument() {
|
||||
StructureStart start = monumentStart(1337L);
|
||||
try {
|
||||
NativeStructurePostProcessor.applyVerticalPlacement(
|
||||
start, "minecraft:monument", 0, -50, -64, 320, false, (x, z) -> 0);
|
||||
} catch (IllegalStateException error) {
|
||||
assertTrue(error.getMessage().contains("cannot align"));
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Expected an out-of-bounds monument alignment to fail");
|
||||
}
|
||||
|
||||
private static StructureStart monumentStart(long seed) {
|
||||
ChunkPos chunkPos = new ChunkPos(0, 0);
|
||||
OceanMonumentPieces.MonumentBuilding building = new OceanMonumentPieces.MonumentBuilding(
|
||||
RandomSource.create(seed), -29, -29, Direction.NORTH);
|
||||
return new StructureStart(
|
||||
monumentStructure(), chunkPos, 0, new PiecesContainer(List.of(building)));
|
||||
}
|
||||
|
||||
private static OceanMonumentStructure monumentStructure() {
|
||||
return new OceanMonumentStructure(new Structure.StructureSettings(HolderSet.empty()));
|
||||
}
|
||||
|
||||
private static OceanMonumentPieces.MonumentBuilding monumentBuilding(StructureStart start) {
|
||||
return (OceanMonumentPieces.MonumentBuilding) start.getPieces().get(0);
|
||||
}
|
||||
|
||||
private static int[] minimumYs(List<StructurePiece> pieces) {
|
||||
int[] ys = new int[pieces.size()];
|
||||
for (int i = 0; i < pieces.size(); i++) {
|
||||
ys[i] = pieces.get(i).getBoundingBox().minY();
|
||||
}
|
||||
return ys;
|
||||
}
|
||||
}
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
package art.arcane.iris.nativegen;
|
||||
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.Rotation;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NativeStructurePostProcessorSurfaceTerrainTest {
|
||||
@BeforeClass
|
||||
public static void bootstrapMinecraft() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onlySurfaceBeardThinStructuresPrepareTerrain() {
|
||||
assertTrue(NativeStructurePostProcessor.shouldPrepareSurfaceTerrain(
|
||||
TerrainAdjustment.BEARD_THIN, GenerationStep.Decoration.SURFACE_STRUCTURES));
|
||||
assertFalse(NativeStructurePostProcessor.shouldPrepareSurfaceTerrain(
|
||||
TerrainAdjustment.BURY, GenerationStep.Decoration.SURFACE_STRUCTURES));
|
||||
assertFalse(NativeStructurePostProcessor.shouldPrepareSurfaceTerrain(
|
||||
TerrainAdjustment.BEARD_BOX, GenerationStep.Decoration.SURFACE_STRUCTURES));
|
||||
assertFalse(NativeStructurePostProcessor.shouldPrepareSurfaceTerrain(
|
||||
TerrainAdjustment.ENCAPSULATE, GenerationStep.Decoration.SURFACE_STRUCTURES));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undergroundAdjustmentsNeverPrepareSurfaceTerrain() {
|
||||
for (TerrainAdjustment adjustment : List.of(
|
||||
TerrainAdjustment.BEARD_THIN,
|
||||
TerrainAdjustment.BURY,
|
||||
TerrainAdjustment.BEARD_BOX,
|
||||
TerrainAdjustment.ENCAPSULATE)) {
|
||||
assertFalse(NativeStructurePostProcessor.shouldPrepareSurfaceTerrain(
|
||||
adjustment, GenerationStep.Decoration.UNDERGROUND_STRUCTURES));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void surfaceAnchorIsFlushInsideAndUnchangedAtRadius() {
|
||||
NativeStructurePostProcessor.SurfaceAnchor anchor = anchor(80, 2);
|
||||
|
||||
assertEquals(80, NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(anchor), 2, 2, 64));
|
||||
assertEquals(64, NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(anchor), 16, 2, 64));
|
||||
assertEquals(64, NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(anchor), 17, 2, 64));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void surfaceAnchorRaisesAndLowersThroughTheTaper() {
|
||||
NativeStructurePostProcessor.SurfaceAnchor raised = anchor(80, 2);
|
||||
NativeStructurePostProcessor.SurfaceAnchor lowered = anchor(64, 2);
|
||||
|
||||
assertEquals(68, NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(raised), 10, 2, 64));
|
||||
assertEquals(76, NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(lowered), 10, 2, 80));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containingRigidFloorsHaveDeterministicPriority() {
|
||||
NativeStructurePostProcessor.SurfaceAnchor rigid = anchor(70, 2);
|
||||
NativeStructurePostProcessor.SurfaceAnchor junction = anchor(90, 1);
|
||||
|
||||
assertEquals(70, NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(rigid, junction), 2, 2, 64));
|
||||
assertEquals(70, NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(junction, rigid), 2, 2, 64));
|
||||
|
||||
NativeStructurePostProcessor.SurfaceAnchor weakTie = anchor(48, 1);
|
||||
NativeStructurePostProcessor.SurfaceAnchor strongTie = anchor(80, 2);
|
||||
assertEquals(80, NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(weakTie, strongTie), 2, 2, 64));
|
||||
assertEquals(80, NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(strongTie, weakTie), 2, 2, 64));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containingFootprintOverridesAnAdjacentPiecesFalloff() {
|
||||
NativeStructurePostProcessor.SurfaceAnchor local =
|
||||
new NativeStructurePostProcessor.SurfaceAnchor(0, 4, 0, 4, 65, 2);
|
||||
NativeStructurePostProcessor.SurfaceAnchor adjacent =
|
||||
new NativeStructurePostProcessor.SurfaceAnchor(5, 9, 0, 4, 80, 2);
|
||||
|
||||
assertEquals(77, NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(adjacent), 4, 2, 64));
|
||||
assertEquals(65, NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(local, adjacent), 4, 2, 64));
|
||||
assertEquals(65, NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(adjacent, local), 4, 2, 64));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void opposingFalloffsBlendWithoutAnAbruptMidpointSeam() {
|
||||
NativeStructurePostProcessor.SurfaceAnchor high =
|
||||
new NativeStructurePostProcessor.SurfaceAnchor(0, 0, 0, 0, 80, 2);
|
||||
NativeStructurePostProcessor.SurfaceAnchor low =
|
||||
new NativeStructurePostProcessor.SurfaceAnchor(12, 12, 0, 0, 48, 2);
|
||||
int previous = NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(high, low), 0, 0, 64);
|
||||
|
||||
for (int x = 1; x <= 12; x++) {
|
||||
int forward = NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(high, low), x, 0, 64);
|
||||
int reversed = NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(low, high), x, 0, 64);
|
||||
assertEquals(forward, reversed);
|
||||
assertTrue(Math.abs(forward - previous) <= 4);
|
||||
previous = forward;
|
||||
}
|
||||
assertEquals(64, NativeStructurePostProcessor.resolveSurfaceTarget(
|
||||
List.of(high, low), 6, 0, 64));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void surfaceColumnsMutateTerrainAndRemoveUnsupportedDecoration() {
|
||||
Map<BlockPos, BlockState> lowered = new HashMap<>();
|
||||
put(lowered, 0, 61, 0, Blocks.STONE.defaultBlockState());
|
||||
put(lowered, 0, 62, 0, Blocks.DIRT.defaultBlockState());
|
||||
put(lowered, 0, 63, 0, Blocks.DIRT.defaultBlockState());
|
||||
put(lowered, 0, 64, 0, Blocks.GRASS_BLOCK.defaultBlockState());
|
||||
put(lowered, 0, 65, 0, Blocks.DANDELION.defaultBlockState());
|
||||
|
||||
NativeStructurePostProcessor.applySurfaceColumn(
|
||||
world(lowered), new BlockPos.MutableBlockPos(),
|
||||
0, 0, 64, 62, -64, 319);
|
||||
|
||||
assertEquals(Blocks.STONE.defaultBlockState(), state(lowered, 0, 61, 0));
|
||||
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(lowered, 0, 62, 0));
|
||||
assertEquals(Blocks.AIR.defaultBlockState(), state(lowered, 0, 63, 0));
|
||||
assertEquals(Blocks.AIR.defaultBlockState(), state(lowered, 0, 64, 0));
|
||||
assertEquals(Blocks.AIR.defaultBlockState(), state(lowered, 0, 65, 0));
|
||||
|
||||
Map<BlockPos, BlockState> raised = new HashMap<>();
|
||||
put(raised, 0, 63, 0, Blocks.DIRT.defaultBlockState());
|
||||
put(raised, 0, 64, 0, Blocks.GRASS_BLOCK.defaultBlockState());
|
||||
put(raised, 0, 65, 0, Blocks.DANDELION.defaultBlockState());
|
||||
|
||||
NativeStructurePostProcessor.applySurfaceColumn(
|
||||
world(raised), new BlockPos.MutableBlockPos(),
|
||||
0, 0, 64, 68, -64, 319);
|
||||
|
||||
assertEquals(Blocks.DIRT.defaultBlockState(), state(raised, 0, 65, 0));
|
||||
assertEquals(Blocks.DIRT.defaultBlockState(), state(raised, 0, 66, 0));
|
||||
assertEquals(Blocks.DIRT.defaultBlockState(), state(raised, 0, 67, 0));
|
||||
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(raised, 0, 68, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loweredFluidColumnsRemainFluidFilled() {
|
||||
Map<BlockPos, BlockState> blocks = new HashMap<>();
|
||||
put(blocks, 0, 62, 0, Blocks.DIRT.defaultBlockState());
|
||||
put(blocks, 0, 63, 0, Blocks.DIRT.defaultBlockState());
|
||||
put(blocks, 0, 64, 0, Blocks.GRAVEL.defaultBlockState());
|
||||
put(blocks, 0, 65, 0, Blocks.WATER.defaultBlockState());
|
||||
|
||||
NativeStructurePostProcessor.applySurfaceColumn(
|
||||
world(blocks), new BlockPos.MutableBlockPos(),
|
||||
0, 0, 64, 62, -64, 319);
|
||||
|
||||
assertEquals(Blocks.WATER.defaultBlockState(), state(blocks, 0, 63, 0));
|
||||
assertEquals(Blocks.WATER.defaultBlockState(), state(blocks, 0, 64, 0));
|
||||
assertEquals(Blocks.WATER.defaultBlockState(), state(blocks, 0, 65, 0));
|
||||
assertEquals(Blocks.GRAVEL.defaultBlockState(), state(blocks, 0, 62, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singlePoolTemplateFieldMatchesTheRuntimeContract() {
|
||||
Field field = NativeStructurePostProcessor.resolveSinglePoolTemplateField();
|
||||
|
||||
assertEquals(SinglePoolElement.class, field.getDeclaringClass());
|
||||
assertEquals(Either.class, field.getType());
|
||||
assertTrue(Modifier.isProtected(field.getModifiers()));
|
||||
assertTrue(Modifier.isFinal(field.getModifiers()));
|
||||
assertFalse(Modifier.isStatic(field.getModifiers()));
|
||||
assertTrue(field.trySetAccessible());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runtimeTemplatesAndLegacyAirUseTheExactContract() {
|
||||
StructureTemplate runtimeTemplate = new StructureTemplate();
|
||||
|
||||
assertEquals(runtimeTemplate, NativeStructurePostProcessor.resolveTemplateReference(
|
||||
Either.right(runtimeTemplate), null));
|
||||
assertFalse(NativeStructurePostProcessor.shouldClearLegacyAir(79, 80, false));
|
||||
assertTrue(NativeStructurePostProcessor.shouldClearLegacyAir(80, 80, false));
|
||||
assertTrue(NativeStructurePostProcessor.shouldClearLegacyAir(96, 80, false));
|
||||
assertFalse(NativeStructurePostProcessor.shouldClearLegacyAir(96, 80, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rotatedTemplateAirClearsOnlyInsideTheChunkAndAboveTheFloor() throws Exception {
|
||||
StructureTemplate.StructureBlockInfo clear = new StructureTemplate.StructureBlockInfo(
|
||||
new BlockPos(1, 0, 0), Blocks.AIR.defaultBlockState(), null);
|
||||
StructureTemplate.StructureBlockInfo belowFloor = new StructureTemplate.StructureBlockInfo(
|
||||
new BlockPos(0, -1, 0), Blocks.AIR.defaultBlockState(), null);
|
||||
StructureTemplate.StructureBlockInfo outsideChunk = new StructureTemplate.StructureBlockInfo(
|
||||
new BlockPos(3, 0, 0), Blocks.AIR.defaultBlockState(), null);
|
||||
StructureTemplate template = template(List.of(clear, belowFloor, outsideChunk));
|
||||
BlockPos origin = new BlockPos(10, 80, 10);
|
||||
StructurePlaceSettings settings = new StructurePlaceSettings().setRotation(Rotation.CLOCKWISE_90);
|
||||
BlockPos clearPosition = origin.offset(StructureTemplate.calculateRelativePosition(settings, clear.pos()));
|
||||
BlockPos belowFloorPosition = origin.offset(
|
||||
StructureTemplate.calculateRelativePosition(settings, belowFloor.pos()));
|
||||
BlockPos outsidePosition = origin.offset(
|
||||
StructureTemplate.calculateRelativePosition(settings, outsideChunk.pos()));
|
||||
BoundingBox area = new BoundingBox(
|
||||
Math.min(clearPosition.getX(), belowFloorPosition.getX()),
|
||||
Math.min(clearPosition.getY(), belowFloorPosition.getY()),
|
||||
Math.min(clearPosition.getZ(), belowFloorPosition.getZ()),
|
||||
Math.max(clearPosition.getX(), belowFloorPosition.getX()),
|
||||
Math.max(clearPosition.getY(), belowFloorPosition.getY()),
|
||||
Math.max(clearPosition.getZ(), belowFloorPosition.getZ()));
|
||||
settings.setBoundingBox(area);
|
||||
assertFalse(area.isInside(outsidePosition));
|
||||
Map<BlockPos, BlockState> blocks = new HashMap<>();
|
||||
blocks.put(clearPosition, Blocks.DIRT.defaultBlockState());
|
||||
blocks.put(belowFloorPosition, Blocks.DIRT.defaultBlockState());
|
||||
blocks.put(outsidePosition, Blocks.DIRT.defaultBlockState());
|
||||
|
||||
NativeStructurePostProcessor.clearTemplateAir(
|
||||
world(blocks), template, origin, 80, settings);
|
||||
|
||||
assertEquals(Blocks.AIR.defaultBlockState(), blocks.get(clearPosition));
|
||||
assertEquals(Blocks.DIRT.defaultBlockState(), blocks.get(belowFloorPosition));
|
||||
assertEquals(Blocks.DIRT.defaultBlockState(), blocks.get(outsidePosition));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unrelatedPieceBoundsAreRejectedBeforeTemplateScanning() {
|
||||
BoundingBox area = new BoundingBox(0, -64, 0, 15, 319, 15);
|
||||
|
||||
assertTrue(NativeStructurePostProcessor.intersects(
|
||||
new BoundingBox(15, 60, 15, 30, 90, 30), area));
|
||||
assertFalse(NativeStructurePostProcessor.intersects(
|
||||
new BoundingBox(16, 60, 16, 30, 90, 30), area));
|
||||
assertFalse(NativeStructurePostProcessor.intersects(
|
||||
new BoundingBox(0, 320, 0, 15, 350, 15), area));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void vegetationCleanupUsesTheSameCircularTaperAsTerrain() {
|
||||
BoundingBox piece = new BoundingBox(0, 60, 0, 4, 80, 4);
|
||||
|
||||
assertTrue(NativeStructurePostProcessor.withinSurfaceTerrainRadius(16, 2, piece, 12));
|
||||
assertFalse(NativeStructurePostProcessor.withinSurfaceTerrainRadius(16, 16, piece, 12));
|
||||
}
|
||||
|
||||
private static NativeStructurePostProcessor.SurfaceAnchor anchor(int meetY, int strength) {
|
||||
return new NativeStructurePostProcessor.SurfaceAnchor(0, 4, 0, 4, meetY, strength);
|
||||
}
|
||||
|
||||
private static StructureTemplate template(
|
||||
List<StructureTemplate.StructureBlockInfo> blocks) throws Exception {
|
||||
Constructor<StructureTemplate.Palette> constructor =
|
||||
StructureTemplate.Palette.class.getDeclaredConstructor(List.class);
|
||||
assertTrue(constructor.trySetAccessible());
|
||||
StructureTemplate.Palette palette = constructor.newInstance(blocks);
|
||||
StructureTemplate template = new StructureTemplate();
|
||||
template.palettes.add(palette);
|
||||
return template;
|
||||
}
|
||||
|
||||
private static WorldGenLevel world(Map<BlockPos, BlockState> blocks) {
|
||||
InvocationHandler handler = (proxy, method, arguments) -> {
|
||||
String methodName = method.getName();
|
||||
if (methodName.equals("getBlockState")) {
|
||||
BlockPos position = (BlockPos) arguments[0];
|
||||
return state(blocks, position.getX(), position.getY(), position.getZ());
|
||||
}
|
||||
if (methodName.equals("setBlock")) {
|
||||
BlockPos position = (BlockPos) arguments[0];
|
||||
BlockState blockState = (BlockState) arguments[1];
|
||||
put(blocks, position.getX(), position.getY(), position.getZ(), blockState);
|
||||
return true;
|
||||
}
|
||||
if (methodName.equals("hashCode")) {
|
||||
return System.identityHashCode(proxy);
|
||||
}
|
||||
if (methodName.equals("equals")) {
|
||||
return proxy == arguments[0];
|
||||
}
|
||||
if (methodName.equals("toString")) {
|
||||
return "surface-test-world";
|
||||
}
|
||||
throw new UnsupportedOperationException(method.toString());
|
||||
};
|
||||
return (WorldGenLevel) Proxy.newProxyInstance(
|
||||
WorldGenLevel.class.getClassLoader(), new Class<?>[]{WorldGenLevel.class}, handler);
|
||||
}
|
||||
|
||||
private static void put(Map<BlockPos, BlockState> blocks,
|
||||
int x, int y, int z, BlockState state) {
|
||||
blocks.put(new BlockPos(x, y, z), state);
|
||||
}
|
||||
|
||||
private static BlockState state(Map<BlockPos, BlockState> blocks, int x, int y, int z) {
|
||||
return blocks.getOrDefault(new BlockPos(x, y, z), Blocks.AIR.defaultBlockState());
|
||||
}
|
||||
}
|
||||
+39
@@ -1,8 +1,10 @@
|
||||
package art.arcane.iris.nativegen;
|
||||
|
||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@@ -24,6 +26,22 @@ public class NativeStructurePostProcessorVegetationTest {
|
||||
assertTrue(NativeStructurePostProcessor.shouldClearVegetationColumn(20, 100, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void surfaceStructuresClearTheirEntireFootprintByDefault() {
|
||||
assertTrue(NativeStructurePostProcessor.shouldClearEntireVegetationFootprint(
|
||||
GenerationStep.Decoration.SURFACE_STRUCTURES, false));
|
||||
assertTrue(NativeStructurePostProcessor.shouldClearEntireVegetationFootprint(
|
||||
GenerationStep.Decoration.SURFACE_STRUCTURES, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undergroundStructuresPreserveSurfaceVegetationUnlessConfigured() {
|
||||
assertFalse(NativeStructurePostProcessor.shouldClearEntireVegetationFootprint(
|
||||
GenerationStep.Decoration.UNDERGROUND_STRUCTURES, false));
|
||||
assertTrue(NativeStructurePostProcessor.shouldClearEntireVegetationFootprint(
|
||||
GenerationStep.Decoration.UNDERGROUND_STRUCTURES, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allUndergroundGenerationStepsShareOneClassification() {
|
||||
assertTrue(NativeStructurePostProcessor.isUndergroundStep(
|
||||
@@ -35,4 +53,25 @@ public class NativeStructurePostProcessorVegetationTest {
|
||||
assertFalse(NativeStructurePostProcessor.isUndergroundStep(
|
||||
GenerationStep.Decoration.SURFACE_STRUCTURES));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undergroundStructuresUseTheLowestTerrainColumn() {
|
||||
BoundingBox bounds = new BoundingBox(0, 60, 0, 1, 80, 0);
|
||||
int offset = NativeStructurePostProcessor.resolveBuriedOffset(
|
||||
bounds, 0, -64, 320, (x, z) -> x == 1 ? 76 : 100);
|
||||
assertEquals(-5, offset);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undergroundStructuresFailWhenBurialWouldCrossWorldFloor() {
|
||||
BoundingBox bounds = new BoundingBox(0, 60, 0, 1, 80, 0);
|
||||
try {
|
||||
NativeStructurePostProcessor.resolveBuriedOffset(
|
||||
bounds, 0, 58, 320, (x, z) -> x == 1 ? 76 : 100);
|
||||
} catch (IllegalStateException e) {
|
||||
assertTrue(e.getMessage().contains("world minimum"));
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Expected underground burial to fail below the world floor");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user