This commit is contained in:
Brian Neumann-Fopiano
2026-02-19 22:35:29 -05:00
parent 1c72d6410f
commit afc5f67bc2
10 changed files with 1939 additions and 88 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -27,7 +27,6 @@ import art.arcane.iris.engine.object.*;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.collection.KSet;
import art.arcane.volmlib.util.io.IO;
import art.arcane.iris.util.common.format.C;
import art.arcane.iris.util.common.misc.ServerProperties;
import art.arcane.iris.util.common.plugin.VolmitSender;
@@ -42,8 +41,6 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@@ -147,42 +144,24 @@ public class ServerConfigurator {
File source = Iris.instance.getDataFolder("datapacks");
source.mkdirs();
File[] datapacks = source.listFiles();
if (datapacks == null || datapacks.length == 0) {
return;
ExternalDataPackPipeline.syncPinnedDatapacks(source);
int removedLegacyCopies = ExternalDataPackPipeline.removeLegacyWorldDatapackCopies(source, folders);
ExternalDataPackPipeline.ImportSummary summary = ExternalDataPackPipeline.importDatapackStructures(source);
if (removedLegacyCopies > 0) {
Iris.info("Removed " + removedLegacyCopies + " legacy external datapack world copies.");
}
int copied = 0;
for (File targetFolder : folders) {
targetFolder.mkdirs();
for (File entry : datapacks) {
if (entry == null || !entry.exists() || entry.getName().startsWith(".")) {
continue;
}
File output = new File(targetFolder, entry.getName());
try {
if (entry.isDirectory()) {
IO.copyDirectory(entry.toPath(), output.toPath());
} else if (entry.isFile()) {
File parent = output.getParentFile();
if (parent != null) {
parent.mkdirs();
}
Files.copy(entry.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);
} else {
continue;
}
copied++;
} catch (Throwable e) {
Iris.warn("Failed to install datapack \"" + entry.getName() + "\" into \"" + targetFolder.getPath() + "\"");
Iris.reportError(e);
}
}
if (summary.getSources() > 0) {
Iris.info("External datapack structure import: sources=" + summary.getSources()
+ ", cached=" + summary.getCachedSources()
+ ", scanned=" + summary.getNbtScanned()
+ ", converted=" + summary.getConverted()
+ ", failed=" + summary.getFailed()
+ ", skipped=" + summary.getSkipped()
+ ", entitiesIgnored=" + summary.getEntitiesIgnored()
+ ", blockEntities=" + summary.getBlockEntities());
}
if (copied > 0) {
Iris.info("Installed " + copied + " external datapack copy operation" + (copied == 1 ? "" : "s") + " from " + source.getPath());
if (summary.getSources() > 0 || summary.getConverted() > 0) {
Iris.info("External datapack world install is disabled; only structure template import is applied.");
}
}

View File

@@ -222,7 +222,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
}
}
return getCaveBiome(x, z);
return getCaveBiome(x, y, z);
}
@ChunkCoordinates
@@ -236,6 +236,28 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
return getComplex().getCaveBiomeStream().get(x, z);
}
@BlockCoordinates
default IrisBiome getCaveBiome(int x, int y, int z) {
IrisBiome caveBiome = getCaveBiome(x, z);
IrisBiome surfaceBiome = getSurfaceBiome(x, z);
if (caveBiome == null) {
return surfaceBiome;
}
int surfaceY = getComplex().getHeightStream().get(x, z).intValue();
int depthBelowSurface = surfaceY - y;
if (depthBelowSurface <= 0) {
return surfaceBiome;
}
int minDepth = Math.max(0, caveBiome.getCaveMinDepthBelowSurface());
if (depthBelowSurface < minDepth) {
return surfaceBiome;
}
return caveBiome;
}
@BlockCoordinates
default IrisBiome getSurfaceBiome(int x, int z) {
return getComplex().getTrueBiomeStream().get(x, z);
@@ -514,7 +536,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
IrisRegion region = getComplex().getRegionStream().get(rx, rz);
IrisBiome biomeSurface = getComplex().getTrueBiomeStream().get(rx, rz);
IrisBiome biomeUnder = ry < he ? getComplex().getCaveBiomeStream().get(rx, rz) : biomeSurface;
IrisBiome biomeUnder = ry < he ? getCaveBiome(rx, ry, rz) : biomeSurface;
double multiplier = 1D * getDimension().getLoot().getMultiplier() * region.getLoot().getMultiplier() * biomeSurface.getLoot().getMultiplier() * biomeUnder.getLoot().getMultiplier();
boolean fallback = tables.isEmpty();
@@ -796,7 +818,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
default IrisBiome getBiome(int x, int y, int z) {
if (y <= getHeight(x, z) - 2) {
return getCaveBiome(x, z);
return getCaveBiome(x, y, z);
}
return getSurfaceBiome(x, z);

View File

@@ -98,35 +98,132 @@ public class IrisCaveCarver3D {
int x0 = chunkX << 4;
int z0 = chunkZ << 4;
int[] columnSurface = new int[256];
int[] columnMaxY = new int[256];
int[] surfaceBreakFloorY = new int[256];
boolean[] surfaceBreakColumn = new boolean[256];
double[] columnThreshold = new double[256];
for (int lx = 0; lx < 16; lx++) {
int x = x0 + lx;
for (int lz = 0; lz < 16; lz++) {
int z = z0 + lz;
int index = (lx << 4) | lz;
int columnSurfaceY = engine.getHeight(x, z);
int clearanceTopY = Math.min(maxY, Math.max(minY, columnSurfaceY - surfaceClearance));
boolean breakColumn = allowSurfaceBreak
&& signed(surfaceBreakDensity.noise(x, z)) >= surfaceBreakNoiseThreshold;
int columnTopY = breakColumn
? Math.min(maxY, Math.max(minY, columnSurfaceY))
: clearanceTopY;
columnSurface[index] = columnSurfaceY;
columnMaxY[index] = columnTopY;
surfaceBreakFloorY[index] = Math.max(minY, columnSurfaceY - surfaceBreakDepth);
surfaceBreakColumn[index] = breakColumn;
columnThreshold[index] = profile.getDensityThreshold().get(thresholdRng, x, z, data) - profile.getThresholdBias();
}
}
int carved = carvePass(
writer,
x0,
z0,
minY,
maxY,
sampleStep,
surfaceBreakThresholdBoost,
waterMinDepthBelowSurface,
waterRequiresFloor,
columnSurface,
columnMaxY,
surfaceBreakFloorY,
surfaceBreakColumn,
columnThreshold,
0D,
false
);
int minCarveCells = Math.max(0, profile.getMinCarveCells());
double recoveryThresholdBoost = Math.max(0, profile.getRecoveryThresholdBoost());
if (carved < minCarveCells && recoveryThresholdBoost > 0D) {
carved += carvePass(
writer,
x0,
z0,
minY,
maxY,
sampleStep,
surfaceBreakThresholdBoost,
waterMinDepthBelowSurface,
waterRequiresFloor,
columnSurface,
columnMaxY,
surfaceBreakFloorY,
surfaceBreakColumn,
columnThreshold,
recoveryThresholdBoost,
true
);
}
return carved;
}
private int carvePass(
MantleWriter writer,
int x0,
int z0,
int minY,
int maxY,
int sampleStep,
double surfaceBreakThresholdBoost,
int waterMinDepthBelowSurface,
boolean waterRequiresFloor,
int[] columnSurface,
int[] columnMaxY,
int[] surfaceBreakFloorY,
boolean[] surfaceBreakColumn,
double[] columnThreshold,
double thresholdBoost,
boolean skipExistingCarved
) {
int carved = 0;
for (int x = x0; x < x0 + 16; x++) {
for (int z = z0; z < z0 + 16; z++) {
double threshold = profile.getDensityThreshold().get(thresholdRng, x, z, data) - profile.getThresholdBias();
int columnSurface = engine.getHeight(x, z);
int clearanceTopY = Math.min(maxY, Math.max(minY, columnSurface - surfaceClearance));
boolean surfaceBreakColumn = allowSurfaceBreak
&& signed(surfaceBreakDensity.noise(x, z)) >= surfaceBreakNoiseThreshold;
int columnMaxY = surfaceBreakColumn
? Math.min(maxY, Math.max(minY, columnSurface))
: clearanceTopY;
int surfaceBreakFloorY = Math.max(minY, columnSurface - surfaceBreakDepth);
if (columnMaxY < minY) {
for (int lx = 0; lx < 16; lx++) {
int x = x0 + lx;
for (int lz = 0; lz < 16; lz++) {
int z = z0 + lz;
int index = (lx << 4) | lz;
int columnTopY = columnMaxY[index];
if (columnTopY < minY) {
continue;
}
for (int y = minY; y <= columnMaxY; y += sampleStep) {
boolean breakColumn = surfaceBreakColumn[index];
int breakFloorY = surfaceBreakFloorY[index];
int surfaceY = columnSurface[index];
double threshold = columnThreshold[index] + thresholdBoost;
for (int y = minY; y <= columnTopY; y += sampleStep) {
double localThreshold = threshold;
if (surfaceBreakColumn && y >= surfaceBreakFloorY) {
if (breakColumn && y >= breakFloorY) {
localThreshold += surfaceBreakThresholdBoost;
}
if (sampleDensity(x, y, z) <= localThreshold) {
int carveMaxY = Math.min(columnMaxY, y + sampleStep - 1);
for (int yy = y; yy <= carveMaxY; yy++) {
writer.setData(x, yy, z, resolveMatter(x, yy, z, localThreshold, waterMinDepthBelowSurface, waterRequiresFloor));
carved++;
localThreshold = applyVerticalEdgeFade(localThreshold, y, minY, maxY);
if (sampleDensity(x, y, z) > localThreshold) {
continue;
}
int carveMaxY = Math.min(columnTopY, y + sampleStep - 1);
for (int yy = y; yy <= carveMaxY; yy++) {
if (skipExistingCarved && writer.isCarved(x, yy, z)) {
continue;
}
writer.setData(x, yy, z, resolveMatter(x, yy, z, surfaceY, localThreshold, waterMinDepthBelowSurface, waterRequiresFloor));
carved++;
}
}
}
@@ -135,6 +232,25 @@ public class IrisCaveCarver3D {
return carved;
}
private double applyVerticalEdgeFade(double threshold, int y, int minY, int maxY) {
int fadeRange = Math.max(0, profile.getVerticalEdgeFade());
if (fadeRange <= 0 || maxY <= minY) {
return threshold;
}
int floorDistance = y - minY;
int ceilingDistance = maxY - y;
int edgeDistance = Math.min(floorDistance, ceilingDistance);
if (edgeDistance >= fadeRange) {
return threshold;
}
double t = Math.max(0D, Math.min(1D, edgeDistance / (double) fadeRange));
double smooth = t * t * (3D - (2D * t));
double fadeStrength = Math.max(0D, profile.getVerticalEdgeFadeStrength());
return threshold - ((1D - smooth) * fadeStrength);
}
private double sampleDensity(int x, int y, int z) {
double warpedX = x;
double warpedY = y;
@@ -142,9 +258,11 @@ public class IrisCaveCarver3D {
double warpStrength = profile.getWarpStrength();
if (warpStrength > 0) {
double offsetX = signed(warpDensity.noise(x, y, z)) * warpStrength;
double offsetY = signed(warpDensity.noise(y, z, x)) * warpStrength;
double offsetZ = signed(warpDensity.noise(z, x, y)) * warpStrength;
double warpA = signed(warpDensity.noise(x, y, z));
double warpB = signed(warpDensity.noise(x + 31.37D, y - 17.21D, z + 23.91D));
double offsetX = warpA * warpStrength;
double offsetY = warpB * warpStrength;
double offsetZ = (warpA - warpB) * 0.5D * warpStrength;
warpedX += offsetX;
warpedY += offsetY;
warpedZ += offsetZ;
@@ -169,7 +287,7 @@ public class IrisCaveCarver3D {
return density / normalization;
}
private MatterCavern resolveMatter(int x, int y, int z, double localThreshold, int waterMinDepthBelowSurface, boolean waterRequiresFloor) {
private MatterCavern resolveMatter(int x, int y, int z, int surfaceY, double localThreshold, int waterMinDepthBelowSurface, boolean waterRequiresFloor) {
int lavaHeight = engine.getDimension().getCaveLavaHeight();
int fluidHeight = engine.getDimension().getFluidHeight();
@@ -178,7 +296,6 @@ public class IrisCaveCarver3D {
}
if (profile.isAllowWater() && y <= fluidHeight) {
int surfaceY = engine.getHeight(x, z);
if (surfaceY - y < waterMinDepthBelowSurface) {
return carveAir;
}
@@ -207,7 +324,6 @@ public class IrisCaveCarver3D {
private boolean hasAquiferCupSupport(int x, int y, int z, double threshold) {
int floorY = Math.max(0, y - 1);
int deepFloorY = Math.max(0, y - 2);
int aboveY = Math.min(engine.getHeight() - 1, y + 1);
if (!isDensitySolid(x, floorY, z, threshold)) {
return false;
}
@@ -229,11 +345,8 @@ public class IrisCaveCarver3D {
if (isDensitySolid(x, y, z - 1, threshold)) {
support++;
}
if (isDensitySolid(x, aboveY, z, threshold)) {
support++;
}
return support >= 4;
return support >= 3;
}
private boolean isDensitySolid(int x, int y, int z, double threshold) {

View File

@@ -50,23 +50,47 @@ public class MantleCarvingComponent extends IrisMantleComponent {
int xxx = 8 + (x << 4);
int zzz = 8 + (z << 4);
IrisRegion region = getComplex().getRegionStream().get(xxx, zzz);
IrisBiome biome = getComplex().getTrueBiomeStream().get(xxx, zzz);
carve(writer, rng, x, z, region, biome);
IrisBiome surfaceBiome = getComplex().getTrueBiomeStream().get(xxx, zzz);
IrisCaveProfile caveBiomeProfile = resolveDominantCaveBiomeProfile(x, z);
carve(writer, rng, x, z, region, surfaceBiome, caveBiomeProfile);
}
@ChunkCoordinates
private void carve(MantleWriter writer, RNG rng, int cx, int cz, IrisRegion region, IrisBiome biome) {
private void carve(MantleWriter writer, RNG rng, int cx, int cz, IrisRegion region, IrisBiome surfaceBiome, IrisCaveProfile caveBiomeProfile) {
IrisCaveProfile dimensionProfile = getDimension().getCaveProfile();
IrisCaveProfile biomeProfile = biome.getCaveProfile();
IrisCaveProfile surfaceBiomeProfile = surfaceBiome.getCaveProfile();
IrisCaveProfile regionProfile = region.getCaveProfile();
IrisCaveProfile activeProfile = resolveActiveProfile(dimensionProfile, regionProfile, biomeProfile);
IrisCaveProfile activeProfile = resolveActiveProfile(dimensionProfile, regionProfile, surfaceBiomeProfile, caveBiomeProfile);
if (isProfileEnabled(activeProfile)) {
carveProfile(activeProfile, writer, cx, cz);
return;
int carved = carveProfile(activeProfile, writer, cx, cz);
if (carved > 0) {
return;
}
if (activeProfile != regionProfile && isProfileEnabled(regionProfile)) {
carved = carveProfile(regionProfile, writer, cx, cz);
if (carved > 0) {
return;
}
}
if (activeProfile != surfaceBiomeProfile && isProfileEnabled(surfaceBiomeProfile)) {
carved = carveProfile(surfaceBiomeProfile, writer, cx, cz);
if (carved > 0) {
return;
}
}
if (activeProfile != dimensionProfile && isProfileEnabled(dimensionProfile)) {
carved = carveProfile(dimensionProfile, writer, cx, cz);
if (carved > 0) {
return;
}
}
}
carve(getDimension().getCarving(), writer, nextCarveRng(rng, cx, cz), cx, cz);
carve(biome.getCarving(), writer, nextCarveRng(rng, cx, cz), cx, cz);
carve(surfaceBiome.getCarving(), writer, nextCarveRng(rng, cx, cz), cx, cz);
carve(region.getCarving(), writer, nextCarveRng(rng, cx, cz), cx, cz);
}
@@ -80,13 +104,13 @@ public class MantleCarvingComponent extends IrisMantleComponent {
}
@ChunkCoordinates
private void carveProfile(IrisCaveProfile profile, MantleWriter writer, int cx, int cz) {
private int carveProfile(IrisCaveProfile profile, MantleWriter writer, int cx, int cz) {
if (!isProfileEnabled(profile)) {
return;
return 0;
}
IrisCaveCarver3D carver = getCarver(profile);
carver.carve(writer, cx, cz);
return carver.carve(writer, cx, cz);
}
private IrisCaveCarver3D getCarver(IrisCaveProfile profile) {
@@ -106,9 +130,59 @@ public class MantleCarvingComponent extends IrisMantleComponent {
return profile != null && profile.isEnabled();
}
private IrisCaveProfile resolveActiveProfile(IrisCaveProfile dimensionProfile, IrisCaveProfile regionProfile, IrisCaveProfile biomeProfile) {
if (isProfileEnabled(biomeProfile)) {
return biomeProfile;
@ChunkCoordinates
private IrisCaveProfile resolveDominantCaveBiomeProfile(int chunkX, int chunkZ) {
int[] offsets = new int[]{1, 4, 8, 12, 15};
Map<IrisCaveProfile, Integer> profileVotes = new IdentityHashMap<>();
int validSamples = 0;
IrisCaveProfile dominantProfile = null;
int dominantVotes = 0;
for (int offsetX : offsets) {
for (int offsetZ : offsets) {
int sampleX = (chunkX << 4) + offsetX;
int sampleZ = (chunkZ << 4) + offsetZ;
int surfaceY = getEngineMantle().getEngine().getHeight(sampleX, sampleZ, true);
int sampleY = Math.max(1, surfaceY - 56);
IrisBiome caveBiome = getEngineMantle().getEngine().getCaveBiome(sampleX, sampleY, sampleZ);
if (caveBiome == null) {
continue;
}
IrisCaveProfile profile = caveBiome.getCaveProfile();
if (!isProfileEnabled(profile)) {
continue;
}
int votes = profileVotes.getOrDefault(profile, 0) + 1;
profileVotes.put(profile, votes);
validSamples++;
if (votes > dominantVotes) {
dominantVotes = votes;
dominantProfile = profile;
}
}
}
if (dominantProfile == null || validSamples <= 0) {
return null;
}
int requiredVotes = Math.max(13, (int) Math.ceil(validSamples * 0.65D));
if (dominantVotes < requiredVotes) {
return null;
}
return dominantProfile;
}
private IrisCaveProfile resolveActiveProfile(IrisCaveProfile dimensionProfile, IrisCaveProfile regionProfile, IrisCaveProfile surfaceBiomeProfile, IrisCaveProfile caveBiomeProfile) {
if (isProfileEnabled(caveBiomeProfile)) {
return caveBiomeProfile;
}
if (isProfileEnabled(surfaceBiomeProfile)) {
return surfaceBiomeProfile;
}
if (isProfileEnabled(regionProfile)) {

View File

@@ -64,7 +64,12 @@ public class MantleObjectComponent extends IrisMantleComponent {
int zzz = 8 + (z << 4);
IrisRegion region = getComplex().getRegionStream().get(xxx, zzz);
IrisBiome surfaceBiome = getComplex().getTrueBiomeStream().get(xxx, zzz);
IrisBiome caveBiome = getComplex().getCaveBiomeStream().get(xxx, zzz);
int surfaceY = getEngineMantle().getEngine().getHeight(xxx, zzz, true);
int sampleY = Math.max(1, surfaceY - 48);
IrisBiome caveBiome = getEngineMantle().getEngine().getCaveBiome(xxx, sampleY, zzz);
if (caveBiome == null) {
caveBiome = surfaceBiome;
}
if (traceRegen) {
Iris.info("Regen object layer start: chunk=" + x + "," + z
+ " surfaceBiome=" + surfaceBiome.getLoadKey()

View File

@@ -123,7 +123,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
walls.forEach((i, v) -> {
IrisBiome biome = v.getCustomBiome().isEmpty()
? getEngine().getCaveBiome(i.getX() + (x << 4), i.getZ() + (z << 4))
? getEngine().getCaveBiome(i.getX() + (x << 4), i.getY(), i.getZ() + (z << 4))
: getEngine().getData().getBiomeLoader().load(v.getCustomBiome());
if (biome != null) {
@@ -207,7 +207,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
}
IrisBiome biome = customBiome.isEmpty()
? getEngine().getCaveBiome(xx, zz)
? getEngine().getCaveBiome(xx, center, zz)
: getEngine().getData().getBiomeLoader().load(customBiome);
if (biome == null) {

View File

@@ -137,6 +137,10 @@ public class IrisBiome extends IrisRegistrant implements IRare {
@RegistryListResource(IrisBiome.class)
@Desc("The carving biome. If specified the biome will be used when under a carving instead of this current biome.")
private String carvingBiome = "";
@MinNumber(0)
@MaxNumber(256)
@Desc("Minimum depth below terrain surface required before this cave biome can be selected.")
private int caveMinDepthBelowSurface = 0;
@Desc("The default slab if iris decides to place a slab in this biome. Default is no slab.")
private IrisBiomePaletteLayer slab = new IrisBiomePaletteLayer().zero();
@Desc("The default wall if iris decides to place a wall higher than 2 blocks (steep hills or possibly cliffs)")

View File

@@ -24,6 +24,16 @@ public class IrisCaveProfile {
@Desc("Global vertical bounds for profile cave carving.")
private IrisRange verticalRange = new IrisRange(0, 384);
@MinNumber(0)
@MaxNumber(128)
@Desc("Vertical fade range applied near cave profile min/max bounds to avoid abrupt hard-stop ceilings/floors.")
private int verticalEdgeFade = 20;
@MinNumber(0)
@MaxNumber(1)
@Desc("Strength of the vertical edge fade at cave profile min/max bounds.")
private double verticalEdgeFadeStrength = 0.18;
@Desc("Base density style for cave field generation.")
private IrisGeneratorStyle baseDensityStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
@@ -58,6 +68,16 @@ public class IrisCaveProfile {
@Desc("Vertical sample step used while evaluating cave density.")
private int sampleStep = 2;
@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.")

View File

@@ -205,9 +205,12 @@ public class CustomBiomeSource extends BiomeSource {
int blockZ = z << 2;
int blockY = y << 2;
int surfaceY = engine.getComplex().getHeightStream().get(blockX, blockZ).intValue();
boolean underground = blockY <= surfaceY - 2;
int caveSwitchY = Math.min(-8, engine.getMinHeight() + 40);
boolean deepUnderground = blockY <= caveSwitchY;
boolean belowSurface = blockY <= surfaceY - 8;
boolean underground = deepUnderground && belowSurface;
IrisBiome irisBiome = underground
? engine.getComplex().getCaveBiomeStream().get(blockX, blockZ)
? engine.getCaveBiome(blockX, blockY, blockZ)
: engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
if (irisBiome == null && underground) {
irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);