This commit is contained in:
Brian Neumann-Fopiano committed 2026-08-05 23:39:54 -06:00
1 parent ce8c4ff578
commit 967c082372
48 files changed
+2351 -349

No files matched your search

@@ -303,15 +303,19 @@ public class ServerConfigurator {
return "";
}
Path root = packsDir.toPath().toAbsolutePath().normalize();
if (Files.isSymbolicLink(root)) {
throw new IllegalArgumentException("Iris packs root is a symbolic link: " + root);
if (!Files.exists(root, LinkOption.NOFOLLOW_LINKS)) {
return "";
}
if (!Files.isDirectory(root, LinkOption.NOFOLLOW_LINKS)) {
if (!Files.isDirectory(root)) {
if (Files.isSymbolicLink(root)) {
throw new IllegalArgumentException("Iris packs root target is missing or unsafe: " + root);
}
return "";
}
try {
Path resolvedRoot = root.toRealPath();
MessageDigest digest = MessageDigest.getInstance("SHA-256");
List<FingerprintEntry> entries = collectFingerprintEntries(root);
List<FingerprintEntry> entries = collectFingerprintEntries(resolvedRoot);
entries.sort(Comparator.comparing(FingerprintEntry::relativePath));
byte[] buffer = new byte[8192];
for (FingerprintEntry entry : entries) {
@@ -91,6 +91,7 @@ import java.util.stream.Stream;
public final class DatapackIngestService {
private static final String USER_AGENT = "VolmitSoftware/Iris (datapack-ingest)";
private static final String FINDER_METADATA = ".DS_Store";
private static final String OVERRIDES_STRIPPED_MARKER = ".iris-overrides-stripped";
private static final String OWNERSHIP_MARKER = ".iris-managed.json";
private static final String TRANSACTION_DIRECTORY = ".iris-datapack-transactions";
@@ -803,6 +804,7 @@ public final class DatapackIngestService {
if (!ownershipSourceMatches(ownership, entry)) {
throw new IOException("Ownership marker at " + directory.getPath() + " belongs to '" + ownership.id + "'");
}
removeFinderMetadata(directory);
if (!Objects.equals(ownership.contentHash, directoryHash(directory))) {
throw new IOException("Refusing to remove modified or corrupt Iris-managed datapack " + directory.getPath());
}
@@ -1479,6 +1481,9 @@ public final class DatapackIngestService {
}
validateInstallTree(target, worldFolder, "Existing datapack install");
Ownership ownership = readOwnershipOrNull(target);
if (ownership != null) {
removeFinderMetadata(target);
}
String currentHash = directoryHash(target);
originalHash = currentHash;
originalMarkerHash = ownershipMarkerFingerprint(target);
@@ -1705,6 +1710,9 @@ public final class DatapackIngestService {
throw new IOException("Missing or unsafe " + purpose + " at " + directory.getPath());
}
validateInstallTree(directory, storeAnchor, purpose);
if (Files.exists(new File(directory, OWNERSHIP_MARKER).toPath(), LinkOption.NOFOLLOW_LINKS)) {
removeFinderMetadata(directory);
}
if (!expectedIdentity.isEmpty()
&& !Objects.equals(directoryIdentity(directory), expectedIdentity)) {
throw new IOException("Datapack directory identity changed in " + purpose + " at " + directory.getPath());
@@ -1991,6 +1999,7 @@ public final class DatapackIngestService {
if (!id.equals(ownership.id)) {
throw new IOException("Datapack ownership mismatch at " + directory.getPath());
}
removeFinderMetadata(directory);
}
private static void rejectSymbolicLinks(File root) throws IOException {
@@ -2019,6 +2028,13 @@ public final class DatapackIngestService {
if (path.equals(rootMarker)) {
continue;
}
if (isFinderMetadata(path)) {
if (Files.isSymbolicLink(path)
|| !Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS)) {
throw new IOException("Suspicious Finder metadata in datapack: " + path);
}
continue;
}
pathCount++;
if (pathCount > MAX_MANAGED_PATHS) {
throw new IOException("Datapack contains more than " + MAX_MANAGED_PATHS + " paths");
@@ -2087,6 +2103,31 @@ public final class DatapackIngestService {
}
}
private static void removeFinderMetadata(File root) throws IOException {
List<Path> entries;
try (Stream<Path> paths = Files.walk(root.toPath())) {
entries = paths.limit(MAX_MANAGED_PATHS + 1L).toList();
}
if (entries.size() > MAX_MANAGED_PATHS) {
throw new IOException("Datapack contains more than " + MAX_MANAGED_PATHS + " paths");
}
for (Path path : entries) {
if (!isFinderMetadata(path)) {
continue;
}
if (Files.isSymbolicLink(path)
|| !Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS)) {
throw new IOException("Suspicious Finder metadata in datapack: " + path);
}
Files.delete(path);
}
}
private static boolean isFinderMetadata(Path path) {
Path fileName = path.getFileName();
return fileName != null && FINDER_METADATA.equals(fileName.toString());
}
private static PackResources scanPackResources(File root) throws IOException {
TreeSet<String> structureKeys = new TreeSet<>();
TreeSet<String> templateKeys = new TreeSet<>();
@@ -2133,6 +2174,7 @@ public final class DatapackIngestService {
if (!id.equals(ownership.id)) {
throw new IOException("Ownership marker belongs to '" + ownership.id + "'");
}
removeFinderMetadata(directory);
if (!Objects.equals(ownership.contentHash, directoryHash(directory))) {
throw new IOException("Refusing to delete modified or corrupt Iris-managed datapack " + directory.getPath());
}
@@ -3440,7 +3482,7 @@ public final class DatapackIngestService {
}
private static boolean isHarmlessRecoveryArtifact(Path path) throws IOException {
if (!".DS_Store".equals(path.getFileName().toString())) {
if (!isFinderMetadata(path)) {
return false;
}
if (Files.isSymbolicLink(path) || !Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS)) {
@@ -44,7 +44,7 @@ final class PackDimensionValidator {
continue;
}
validateImportedStructurePolicy(dimensionKey, dimJson, blockingErrors);
validateImportedStructurePolicy(dimensionKey, dimJson, blockingErrors, warnings);
JSONArray regionsArray = dimJson.optJSONArray("regions");
if (regionsArray == null || regionsArray.length() == 0) {
@@ -90,7 +90,7 @@ final class PackDimensionValidator {
}
static void validateImportedStructurePolicy(String dimensionKey, JSONObject dimension,
List<String> blockingErrors) {
List<String> blockingErrors, List<String> warnings) {
if (!dimension.has("importedStructures")) {
return;
}
@@ -127,6 +127,10 @@ final class PackDimensionValidator {
+ "' importedStructures.adjustments has a non-object entry at index " + index + ".");
continue;
}
if (adjustment.has("clearVegetation")) {
warnings.add("Dimension '" + dimensionKey + "' importedStructures.adjustments[" + index
+ "].clearVegetation was removed and is ignored. Vegetation is always cleared inside structure piece envelopes.");
}
validateStructureKeyList(dimensionKey, adjustment, "match", blockingErrors);
validateAdjustmentYBand(dimensionKey, adjustment, index, blockingErrors);
PackStructurePlacementValidator.validateNativeTerrain("Dimension '" + dimensionKey
@@ -47,7 +47,7 @@ public final class PackDirectoryResolver {
if (!Files.exists(root, LinkOption.NOFOLLOW_LINKS)) {
return List.of();
}
if (Files.isSymbolicLink(root) || !Files.isDirectory(root, LinkOption.NOFOLLOW_LINKS)) {
if (!Files.isDirectory(root)) {
throw new IOException("Pack workspace is missing or unsafe: " + root);
}
try (Stream<Path> stream = Files.list(root)) {
@@ -34,7 +34,7 @@ public record NativeStructureOwnershipRecord(
String contentFingerprint,
DecisionSnapshot decision
) {
public static final int CURRENT_SCHEMA = 1;
public static final int CURRENT_SCHEMA = 2;
public static final int MAX_REFERENCE_DISTANCE_CHUNKS = 8;
private static final int MAX_KEY_BYTES = 512;
private static final int MAX_FINGERPRINT_BYTES = 128;
@@ -251,7 +251,7 @@ public record NativeStructureOwnershipRecord(
}
}
public record DecisionSnapshot(boolean clearVegetation, String stiltJson, String terrainJson) {
public record DecisionSnapshot(String stiltJson, String terrainJson) {
private static final Gson GSON = new Gson();
private static final int MAX_JSON_BYTES = 65_536;
@@ -269,7 +269,6 @@ public record NativeStructureOwnershipRecord(
throw new IllegalArgumentException("Only generated native structure decisions can be persisted");
}
return new DecisionSnapshot(
resolved.clearVegetation(),
GSON.toJson(resolved.stilt()),
GSON.toJson(Objects.requireNonNullElseGet(
resolved.terrain(), IrisStructureTerrain::new))
@@ -288,21 +287,18 @@ public record NativeStructureOwnershipRecord(
0,
null,
false,
clearVegetation,
stilt,
terrain
);
}
void write(DataOutputStream output) throws IOException {
output.writeBoolean(clearVegetation);
writeString(output, stiltJson, MAX_JSON_BYTES, "stilt snapshot");
writeString(output, terrainJson, MAX_JSON_BYTES, "terrain snapshot");
}
static DecisionSnapshot read(DataInputStream input) throws IOException {
return new DecisionSnapshot(
input.readBoolean(),
readString(input, MAX_JSON_BYTES, "stilt snapshot"),
readString(input, MAX_JSON_BYTES, "terrain snapshot")
);
@@ -79,7 +79,6 @@ public final class NativeStructurePlacementPlanner {
0,
null,
false,
!placement.isUnderground(),
placement.getStilt(),
placement.resolvedTerrain()
);
@@ -0,0 +1,261 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 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.engine.mantle.components;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterCavern;
import art.arcane.volmlib.util.matter.MatterSlice;
import java.util.Arrays;
public final class CarveOrphanSweep {
private static final int CHUNK_SIZE = 16;
private static final int CHUNK_AREA = CHUNK_SIZE * CHUNK_SIZE;
private static final int BAND_FLOOR_MARGIN = 4;
private static final int MAX_ORPHAN_CELLS = 16;
private static final MatterCavern ORPHAN_CAVERN = new MatterCavern(true, "", (byte) 0);
private static final ThreadLocal<SweepScratch> SCRATCH = ThreadLocal.withInitial(SweepScratch::new);
public interface CarveAccess {
boolean isCarved(int localX, int y, int localZ);
void markCarved(int localX, int y, int localZ);
}
private CarveOrphanSweep() {
}
static int sweepChunk(MantleChunk<Matter> chunk, int[] surfaceHeights, int maxSurfaceBreakDepth, int worldCeilingY) {
if (chunk == null) {
return 0;
}
return sweep(surfaceHeights, maxSurfaceBreakDepth, 0, worldCeilingY, new MantleCarveAccess(chunk));
}
public static int sweep(int[] surfaceHeights, int maxSurfaceBreakDepth, int worldFloorY, int worldCeilingY, CarveAccess access) {
if (surfaceHeights == null || surfaceHeights.length < CHUNK_AREA || access == null) {
return 0;
}
int minSurfaceY = Integer.MAX_VALUE;
int maxSurfaceY = Integer.MIN_VALUE;
for (int columnIndex = 0; columnIndex < CHUNK_AREA; columnIndex++) {
int surfaceY = surfaceHeights[columnIndex];
if (surfaceY < minSurfaceY) {
minSurfaceY = surfaceY;
}
if (surfaceY > maxSurfaceY) {
maxSurfaceY = surfaceY;
}
}
int bandTop = Math.min(worldCeilingY, maxSurfaceY);
int bandFloor = Math.max(worldFloorY + 1, minSurfaceY - Math.max(0, maxSurfaceBreakDepth) - BAND_FLOOR_MARGIN);
if (bandTop < bandFloor) {
return 0;
}
int bandHeight = bandTop - bandFloor + 1;
int cellCount = bandHeight * CHUNK_AREA;
SweepScratch scratch = SCRATCH.get();
scratch.prepare(cellCount);
long[] solid = scratch.solid;
long[] visited = scratch.visited;
int[] stack = scratch.stack;
int[] component = scratch.component;
boolean carvedPresent = false;
for (int y = bandFloor; y <= bandTop; y++) {
int layer = (y - bandFloor) * CHUNK_AREA;
for (int localX = 0; localX < CHUNK_SIZE; localX++) {
for (int localZ = 0; localZ < CHUNK_SIZE; localZ++) {
int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
if (y > surfaceHeights[columnIndex]) {
continue;
}
if (access.isCarved(localX, y, localZ)) {
carvedPresent = true;
continue;
}
setBit(solid, layer + columnIndex);
}
}
}
if (!carvedPresent) {
return 0;
}
int marked = 0;
for (int rootIndex = 0; rootIndex < cellCount; rootIndex++) {
if (!getBit(solid, rootIndex) || getBit(visited, rootIndex)) {
continue;
}
setBit(visited, rootIndex);
stack[0] = rootIndex;
int stackSize = 1;
int componentSize = 0;
boolean anchored = false;
while (stackSize > 0) {
int current = stack[--stackSize];
int columnIndex = current & (CHUNK_AREA - 1);
int localX = columnIndex >>> 4;
int localZ = columnIndex & (CHUNK_SIZE - 1);
int y = bandFloor + (current / CHUNK_AREA);
if (!anchored) {
if (localX == 0 || localX == CHUNK_SIZE - 1 || localZ == 0 || localZ == CHUNK_SIZE - 1) {
anchored = true;
} else if (y == bandFloor && isSolidBelowBand(access, surfaceHeights, columnIndex, localX, localZ, bandFloor - 1, worldFloorY)) {
anchored = true;
} else if (componentSize >= MAX_ORPHAN_CELLS) {
anchored = true;
} else {
component[componentSize++] = current;
}
}
if (y > bandFloor) {
stackSize = push(solid, visited, stack, stackSize, current - CHUNK_AREA);
}
if (y < bandTop) {
stackSize = push(solid, visited, stack, stackSize, current + CHUNK_AREA);
}
if (localX > 0) {
stackSize = push(solid, visited, stack, stackSize, current - CHUNK_SIZE);
}
if (localX < CHUNK_SIZE - 1) {
stackSize = push(solid, visited, stack, stackSize, current + CHUNK_SIZE);
}
if (localZ > 0) {
stackSize = push(solid, visited, stack, stackSize, current - 1);
}
if (localZ < CHUNK_SIZE - 1) {
stackSize = push(solid, visited, stack, stackSize, current + 1);
}
}
if (anchored) {
continue;
}
for (int index = 0; index < componentSize; index++) {
int cell = component[index];
int columnIndex = cell & (CHUNK_AREA - 1);
access.markCarved(columnIndex >>> 4, bandFloor + (cell / CHUNK_AREA), columnIndex & (CHUNK_SIZE - 1));
marked++;
}
}
return marked;
}
private static int push(long[] solid, long[] visited, int[] stack, int stackSize, int neighbor) {
if (!getBit(solid, neighbor) || getBit(visited, neighbor)) {
return stackSize;
}
setBit(visited, neighbor);
stack[stackSize] = neighbor;
return stackSize + 1;
}
private static boolean isSolidBelowBand(CarveAccess access, int[] surfaceHeights, int columnIndex, int localX, int localZ, int belowY, int worldFloorY) {
if (belowY < worldFloorY || belowY > surfaceHeights[columnIndex]) {
return false;
}
return !access.isCarved(localX, belowY, localZ);
}
private static boolean getBit(long[] bits, int index) {
return (bits[index >>> 6] & (1L << (index & 63))) != 0L;
}
private static void setBit(long[] bits, int index) {
bits[index >>> 6] |= 1L << (index & 63);
}
private static final class MantleCarveAccess implements CarveAccess {
private final MantleChunk<Matter> chunk;
private MatterSlice<MatterCavern> cachedSlice;
private int cachedSectionIndex = -1;
private MantleCarveAccess(MantleChunk<Matter> chunk) {
this.chunk = chunk;
}
@Override
public boolean isCarved(int localX, int y, int localZ) {
MatterSlice<MatterCavern> cavernSlice = resolveSlice(y >> 4);
if (cavernSlice == null) {
return false;
}
MatterCavern cavern = cavernSlice.get(localX, y & 15, localZ);
return cavern != null && cavern.isCavern();
}
@Override
public void markCarved(int localX, int y, int localZ) {
chunk.getOrCreate(y >> 4).slice(MatterCavern.class).set(localX, y & 15, localZ, ORPHAN_CAVERN);
cachedSectionIndex = -1;
cachedSlice = null;
}
private MatterSlice<MatterCavern> resolveSlice(int sectionIndex) {
if (sectionIndex == cachedSectionIndex) {
return cachedSlice;
}
Matter section = sectionIndex >= 0 && sectionIndex < chunk.sectionCount() ? chunk.get(sectionIndex) : null;
cachedSlice = section == null ? null : section.getSlice(MatterCavern.class);
cachedSectionIndex = sectionIndex;
return cachedSlice;
}
}
private static final class SweepScratch {
private final int[] component = new int[MAX_ORPHAN_CELLS];
private long[] solid = new long[0];
private long[] visited = new long[0];
private int[] stack = new int[0];
private void prepare(int cellCount) {
int words = (cellCount + 63) >>> 6;
if (solid.length < words) {
solid = new long[words];
visited = new long[words];
} else {
Arrays.fill(solid, 0, words, 0L);
Arrays.fill(visited, 0, words, 0L);
}
if (stack.length < cellCount) {
stack = new int[cellCount];
}
}
}
}
@@ -46,7 +46,6 @@ public class IrisCaveCarver3D {
private static final byte LIQUID_FORCED_AIR = 3;
private static final int ADAPTIVE_MIN_PLANE_COLUMNS = 16;
private static final int ADAPTIVE_DEEP_SAMPLE_STEP = 8;
private static final int ADAPTIVE_DEEP_SURFACE_MARGIN = 12;
private static final double ADAPTIVE_LOCAL_RANGE_SCALE = 0.125D;
private static final double ADAPTIVE_DEEP_MARGIN_BOOST = 0.015D;
@@ -586,6 +585,12 @@ public class IrisCaveCarver3D {
boolean[] planeCarve = scratch.planeCarve;
int minSection = PowerOfTwoCoordinates.floorDivPow2(minY, 4);
int maxSection = PowerOfTwoCoordinates.floorDivPow2(maxY, 4);
int effectiveAdaptiveSampleStep = Math.max(adaptiveSampleStep, ADAPTIVE_DEEP_SAMPLE_STEP);
double effectiveAdaptiveThresholdMargin = resolveAdaptivePlaneThresholdMargin(
adaptiveThresholdMargin,
adaptiveSampleStep,
effectiveAdaptiveSampleStep
);
for (int sectionIndex = minSection; sectionIndex <= maxSection; sectionIndex++) {
int sectionMinY = Math.max(minY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex));
@@ -614,12 +619,6 @@ public class IrisCaveCarver3D {
continue;
}
int effectiveAdaptiveSampleStep = resolveAdaptivePlaneSampleStep(y, adaptiveSampleStep);
double effectiveAdaptiveThresholdMargin = resolveAdaptivePlaneThresholdMargin(
adaptiveThresholdMargin,
adaptiveSampleStep,
effectiveAdaptiveSampleStep
);
classifyDensityPlaneAdaptive(
scratch,
x0,
@@ -678,16 +677,6 @@ public class IrisCaveCarver3D {
return carved;
}
private int resolveAdaptivePlaneSampleStep(int y, int adaptiveSampleStep) {
if (adaptiveSampleStep >= ADAPTIVE_DEEP_SAMPLE_STEP) {
return adaptiveSampleStep;
}
int profileMaxY = (int) Math.ceil(profile.getVerticalRange().getMax());
int fineBandFloorY = profileMaxY - profile.getSurfaceBreakDepth() - ADAPTIVE_DEEP_SURFACE_MARGIN;
return y >= fineBandFloorY ? adaptiveSampleStep : ADAPTIVE_DEEP_SAMPLE_STEP;
}
private double resolveAdaptivePlaneThresholdMargin(
double adaptiveThresholdMargin,
int adaptiveSampleStep,
@@ -101,6 +101,23 @@ public class MantleCarvingComponent extends IrisMantleComponent {
carveUpperTerrain(upperCtx, weightedProfiles, writer, x, z, chunkSurfaceHeights, waterSupportPlan);
}
waterSupportPlan.resolve(writer.acquireChunk(x, z));
if (!weightedProfiles.isEmpty()) {
CarveOrphanSweep.sweepChunk(
writer.acquireChunk(x, z),
chunkSurfaceHeights,
maxSurfaceBreakDepth(weightedProfiles),
writer.getMantle().getWorldHeight() - 1
);
}
}
private static int maxSurfaceBreakDepth(List<WeightedProfile> weightedProfiles) {
int maxDepth = 0;
for (WeightedProfile weightedProfile : weightedProfiles) {
maxDepth = Math.max(maxDepth, Math.max(0, weightedProfile.profile.getSurfaceBreakDepth()));
}
return maxDepth;
}
@ChunkCoordinates
@@ -100,10 +100,8 @@ public class ModeOverworld extends IrisEngineMode implements EngineMode {
sGenMatter,
sTerrain
));
registerStage(burst(
sCave,
sPost
));
registerStage(sCave);
registerStage(sPost);
registerStage(sFloatingTerrainSolid);
registerStage(burst(
sDeposit,
@@ -81,16 +81,6 @@ public class IrisCaveProfile {
@Desc("Additional adaptive ambiguity margin used before the cave predictor falls back to exact sampling.")
private double adaptiveThresholdMargin = 0.04;
@MinNumber(0)
@MaxNumber(4096)
@Desc("Minimum carved cells expected from this profile before recovery boost applies.")
private int minCarveCells = 0;
@MinNumber(0)
@MaxNumber(1)
@Desc("Additional threshold boost used when profile carve output is too sparse.")
private double recoveryThresholdBoost = 0.08;
@MinNumber(0)
@MaxNumber(64)
@Desc("Minimum solid clearance below terrain surface where carving may occur.")
@@ -52,7 +52,7 @@ public class IrisImportedStructureControl {
private boolean datapackOverrides = true;
@ArrayType(type = IrisVanillaStructureAdjustment.class, min = 1)
@Desc("Per-structure adjustments applied to vanilla, mod, and datapack structures that still generate natively. Vertical shifts from every matching entry stack. A matching vegetation option explicitly clears logs and leaves inside structure piece bounds. A matching preserveSourceY option disables Iris burial repositioning for that structure. The last matching entry with stilt settings controls foundation columns, and likewise for terrain and yBand settings. A structure suppressed by an Iris placement is unaffected.")
@Desc("Per-structure adjustments applied to vanilla, mod, and datapack structures that still generate natively. Vertical shifts from every matching entry stack. A matching preserveSourceY option disables Iris burial repositioning for that structure. The last matching entry with stilt settings controls foundation columns, and likewise for terrain and yBand settings. A structure suppressed by an Iris placement is unaffected.")
private KList<IrisVanillaStructureAdjustment> adjustments = new KList<>();
public boolean shouldGenerate(String key) {
@@ -64,7 +64,6 @@ public class IrisImportedStructureControl {
adjustments, "importedStructures.adjustments must not be null");
int y = undergroundStep ? undergroundYShift : 0;
boolean preserveSourceY = false;
boolean clearVegetation = false;
IrisStructureStiltSettings stilt = null;
IrisStructureTerrain terrain = null;
IrisStructureYBand yBand = null;
@@ -72,7 +71,6 @@ public class IrisImportedStructureControl {
if (adjustment != null && adjustment.matches(key)) {
y += adjustment.getYShift();
preserveSourceY |= adjustment.isPreserveSourceY();
clearVegetation |= adjustment.isClearVegetation();
if (adjustment.getStilt() != null) {
stilt = adjustment.getStilt();
}
@@ -85,7 +83,7 @@ public class IrisImportedStructureControl {
}
}
return new IrisNativeStructureDecision(
generationStatus(key), y, yBand, preserveSourceY, clearVegetation, stilt, terrain);
generationStatus(key), y, yBand, preserveSourceY, stilt, terrain);
}
private NativeStructureGenerationStatus generationStatus(String key) {
@@ -23,7 +23,6 @@ public record IrisNativeStructureDecision(
int yShift,
IrisStructureYBand yBand,
boolean preserveSourceY,
boolean clearVegetation,
IrisStructureStiltSettings stilt,
IrisStructureTerrain terrain
) {
@@ -33,6 +32,6 @@ public record IrisNativeStructureDecision(
public IrisNativeStructureDecision withStatus(NativeStructureGenerationStatus replacement) {
return new IrisNativeStructureDecision(
replacement, yShift, yBand, preserveSourceY, clearVegetation, stilt, terrain);
replacement, yShift, yBand, preserveSourceY, stilt, terrain);
}
}
@@ -22,7 +22,7 @@ public class IrisStructureTerrain {
private static final double MAX_EROSION_FREQUENCY = 1D;
private static final double MAX_LOBE_FREQUENCY = 1D;
@Desc("Terrain operation. SOURCE applies the registered native structure's authored terrain adaptation and is a no-op for editable Iris structures. PRESERVE disables terrain integration. VACUUM forces a 12-block surface bend to every rigid native piece base. BORE and FORCE_CARVE clear the requested envelope, while ENCASE fills it before placement so native shells are not lost to pre-carved air.")
@Desc("Terrain operation. SOURCE applies the registered native structure's authored terrain adaptation and is a no-op for editable Iris structures. PRESERVE disables terrain integration. VACUUM raises terrain from processed rigid-template foundations at or below each authored ground plane with a 12-block falloff without lowering existing ground. BORE and FORCE_CARVE clear the requested envelope, while ENCASE fills it before placement so native shells are not lost to pre-carved air.")
private IrisStructureTerrainMode mode = IrisStructureTerrainMode.SOURCE;
@MinNumber(0)
@@ -9,7 +9,7 @@ public enum IrisStructureTerrainMode {
BORE,
FORCE_CARVE,
@Desc("Bends surface terrain to every rigid piece base with a 12-block falloff, even when the registered structure has no authored terrain adaptation. Terrain is raised or lowered rather than carved away.")
@Desc("Raises surface terrain from processed solid rigid-template foundations at or below each authored ground plane with a 12-block falloff, even when the registered structure has no terrain adaptation. Existing higher terrain and authored air remain untouched.")
VACUUM,
@Desc("Fills the padded piece volume with solid blocks before any piece is placed so shells, walls, and floors land in solid ground instead of pre-carved air. Only air and liquid cells are filled; existing terrain and structures are never overwritten. Native pieces then carve their own interiors.")
@@ -32,7 +32,7 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@Desc("A per-structure adjustment applied to vanilla, mod, and datapack structures that still generate natively (those NOT suppressed by an Iris 'structures' placement). Vertical shifts move the structure start, pieces, bounds, and jigsaw metadata together before references and placement. Surface structures clear intersecting trees automatically; optional postprocessing can force vegetation clearing or build palette-driven foundation columns.")
@Desc("A per-structure adjustment applied to vanilla, mod, and datapack structures that still generate natively (those NOT suppressed by an Iris 'structures' placement). Vertical shifts move the structure start, pieces, bounds, and jigsaw metadata together before references and placement. Intersecting trees are cleared automatically inside structure piece envelopes; optional postprocessing can build palette-driven foundation columns.")
@Data
public class IrisVanillaStructureAdjustment {
@ArrayType(type = String.class, min = 1)
@@ -51,13 +51,10 @@ public class IrisVanillaStructureAdjustment {
@Desc("When true, skip Iris burial repositioning so the structure keeps the Y its own vanilla placement chose. Underground structures are otherwise pushed below the lowest solid column across their whole footprint, which hides terrain-aware structures such as mineshafts that intentionally breach cliffs and surfaces. Vertical shifts still apply on top of the preserved Y: this dimension's undergroundYShift plus every matching adjustment's yShift.")
private boolean preserveSourceY = false;
@Desc("When true, force logs and leaves out of the structure footprint even when the structure does not reach the detected tree base. Normal surface-intersecting structures are protected automatically.")
private boolean clearVegetation = false;
@Desc("Optional foundation columns placed beneath the native structure piece bases after placement.")
private IrisStructureStiltSettings stilt = null;
@Desc("Optional terrain integration override. VACUUM forces surface terrain to bend to every rigid native piece base even when the structure did not author terrain adaptation. BORE and FORCE_CARVE clear every intersecting chunk before native pieces are placed, while ENCASE fills it with solid blocks instead. Left unset, SOURCE replays the registered structure's authored terrain adaptation, including surface fitting, burial, and encapsulation.")
@Desc("Optional terrain integration override. VACUUM raises surface terrain from processed solid rigid-template foundations at or below each authored ground plane with a 12-block falloff without lowering existing ground. BORE and FORCE_CARVE clear every intersecting chunk before native pieces are placed, while ENCASE fills it with solid blocks instead. Left unset, SOURCE replays the registered structure's authored terrain adaptation, including surface fitting, burial, and encapsulation.")
private IrisStructureTerrain terrain = null;
public boolean matches(String key) {