This commit is contained in:
Brian Neumann-Fopiano
2026-02-19 19:50:29 -05:00
parent c2c612eb35
commit 1c72d6410f
18 changed files with 1138 additions and 82 deletions

View File

@@ -342,11 +342,16 @@ public class IrisCreator {
int chunkX = rawLocation.getBlockX() >> 4;
int chunkZ = rawLocation.getBlockZ() >> 4;
try {
CompletableFuture<Chunk> chunkFuture = PaperLib.getChunkAtAsync(world, chunkX, chunkZ, true);
CompletableFuture<Chunk> chunkFuture = PaperLib.getChunkAtAsync(world, chunkX, chunkZ, false);
if (chunkFuture != null) {
chunkFuture.get(15, TimeUnit.SECONDS);
chunkFuture.get(10, TimeUnit.SECONDS);
}
} catch (Throwable ignored) {
return rawLocation;
}
if (!world.isChunkLoaded(chunkX, chunkZ)) {
return rawLocation;
}
CompletableFuture<Location> regionFuture = new CompletableFuture<>();
@@ -376,19 +381,19 @@ public class IrisCreator {
int z = source.getBlockZ();
int minY = world.getMinHeight() + 1;
int maxY = world.getMaxHeight() - 2;
int topY = world.getHighestBlockYAt(x, z, HeightMap.MOTION_BLOCKING_NO_LEAVES);
int startY = Math.max(minY, Math.min(maxY, topY + 1));
int sourceY = source.getBlockY();
int startY = Math.max(minY, Math.min(maxY, sourceY));
float yaw = source.getYaw();
float pitch = source.getPitch();
int upperBound = Math.min(maxY, startY + 16);
int upperBound = Math.min(maxY, startY + 32);
for (int y = startY; y <= upperBound; y++) {
if (isSafeStandingLocation(world, x, y, z)) {
return new Location(world, x + 0.5D, y, z + 0.5D, yaw, pitch);
}
}
int lowerBound = Math.max(minY, startY - 24);
int lowerBound = Math.max(minY, startY - 64);
for (int y = startY - 1; y >= lowerBound; y--) {
if (isSafeStandingLocation(world, x, y, z)) {
return new Location(world, x + 0.5D, y, z + 0.5D, yaw, pitch);

View File

@@ -433,6 +433,10 @@ public class IrisComplex implements DataProvider {
for (IrisGenerator gen : generators) {
String key = gen.getLoadKey();
if (key == null || key.isBlank()) {
continue;
}
max += biome.getGenLinkMax(key, engine);
min += biome.getGenLinkMin(key, engine);
}

View File

@@ -107,6 +107,7 @@ public class IrisEngine implements Engine {
private double maxBiomeLayerDensity;
private double maxBiomeDecoratorDensity;
private IrisComplex complex;
private final AtomicBoolean modeFallbackLogged;
public IrisEngine(EngineTarget target, boolean studio) {
this.studio = studio;
@@ -129,6 +130,7 @@ public class IrisEngine implements Engine {
context = new IrisContext(this);
cleaning = new AtomicBoolean(false);
noisemapPrebakeRunning = new AtomicBoolean(false);
modeFallbackLogged = new AtomicBoolean(false);
execution = getData().getEnvironment().with(this);
if (studio) {
getData().dump();
@@ -165,10 +167,29 @@ public class IrisEngine implements Engine {
}
private void prehotload() {
worldManager.close();
complex.close();
effects.close();
mode.close();
EngineWorldManager currentWorldManager = worldManager;
worldManager = null;
if (currentWorldManager != null) {
currentWorldManager.close();
}
IrisComplex currentComplex = complex;
complex = null;
if (currentComplex != null) {
currentComplex.close();
}
EngineEffects currentEffects = effects;
effects = null;
if (currentEffects != null) {
currentEffects.close();
}
EngineMode currentMode = mode;
mode = null;
if (currentMode != null) {
currentMode.close();
}
execution = getData().getEnvironment().with(this);
J.a(() -> new IrisProject(getData().getDataFolder()).updateWorkspace());
@@ -178,12 +199,14 @@ public class IrisEngine implements Engine {
try {
Iris.debug("Setup Engine " + getCacheID());
cacheId = RNG.r.nextInt();
worldManager = new IrisWorldManager(this);
complex = new IrisComplex(this);
complex = ensureComplex();
effects = new IrisEngineEffects(this);
hash32 = new CompletableFuture<>();
mantle.hotload();
setupMode();
IrisWorldManager manager = new IrisWorldManager(this);
worldManager = manager;
manager.startManager();
getDimension().getEngineScripts().forEach(execution::execute);
J.a(this::computeBiomeMaxes);
J.a(() -> {
@@ -207,11 +230,76 @@ public class IrisEngine implements Engine {
}
private void setupMode() {
if (mode != null) {
mode.close();
EngineMode currentMode = mode;
if (currentMode != null) {
currentMode.close();
}
mode = getDimension().getMode().create(this);
mode = null;
mode = ensureMode();
}
private EngineMode ensureMode() {
EngineMode currentMode = mode;
if (currentMode != null) {
return currentMode;
}
synchronized (this) {
currentMode = mode;
if (currentMode != null) {
return currentMode;
}
try {
IrisComplex readyComplex = ensureComplex();
if (readyComplex == null) {
throw new IllegalStateException("Iris complex is unavailable");
}
IrisDimensionMode configuredMode = getDimension().getMode();
if (configuredMode == null) {
configuredMode = new IrisDimensionMode();
getDimension().setMode(configuredMode);
}
currentMode = configuredMode.create(this);
if (currentMode == null) {
throw new IllegalStateException("Dimension mode factory returned null");
}
} catch (Throwable e) {
Iris.reportError(e);
if (modeFallbackLogged.compareAndSet(false, true)) {
Iris.warn("Failed to initialize configured dimension mode for " + getDimension().getLoadKey() + ", falling back to OVERWORLD mode.");
}
currentMode = IrisDimensionModeType.OVERWORLD.create(this);
}
mode = currentMode;
return currentMode;
}
}
private IrisComplex ensureComplex() {
IrisComplex currentComplex = complex;
if (currentComplex != null) {
return currentComplex;
}
if (closed) {
return null;
}
synchronized (this) {
currentComplex = complex;
if (currentComplex != null) {
return currentComplex;
}
currentComplex = new IrisComplex(this);
complex = currentComplex;
return currentComplex;
}
}
private void scheduleStartupNoisemapPrebake() {
@@ -447,17 +535,39 @@ public class IrisEngine implements Engine {
PregeneratorJob.shutdownInstance();
closed = true;
J.car(art);
getWorldManager().close();
EngineWorldManager currentWorldManager = getWorldManager();
if (currentWorldManager != null) {
currentWorldManager.close();
}
getTarget().close();
saveEngineData();
getMantle().close();
getComplex().close();
mode.close();
IrisComplex currentComplex = complex;
if (currentComplex != null) {
currentComplex.close();
}
complex = null;
EngineMode currentMode = mode;
if (currentMode != null) {
currentMode.close();
}
mode = null;
effects = null;
worldManager = null;
getData().dump();
getData().clearLists();
Iris.service(PreservationSVC.class).dereference();
Iris.debug("Engine Fully Shutdown!");
complex = null;
}
@Override
public IrisComplex getComplex() {
return ensureComplex();
}
@Override
public EngineMode getMode() {
return ensureMode();
}
@Override
@@ -510,7 +620,8 @@ public class IrisEngine implements Engine {
}
}
} else {
mode.generate(x, z, blocks, vbiomes, multicore);
EngineMode activeMode = ensureMode();
activeMode.generate(x, z, blocks, vbiomes, multicore);
}
World realWorld = getWorld().realWorld();

View File

@@ -195,7 +195,12 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
};
looper.setPriority(Thread.MIN_PRIORITY);
looper.setName("Iris World Manager " + getTarget().getWorld().name());
looper.start();
}
public void startManager() {
if (!looper.isAlive()) {
looper.start();
}
}
private void discoverChunks() {
@@ -528,6 +533,11 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
return;
}
IrisComplex complex = getEngine().getComplex();
if (complex == null) {
return;
}
if (initial) {
energy += 1.2;
}

View File

@@ -0,0 +1,265 @@
/*
* 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.iris.core.loader.IrisData;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.mantle.MantleWriter;
import art.arcane.iris.engine.object.IrisCaveFieldModule;
import art.arcane.iris.engine.object.IrisCaveProfile;
import art.arcane.iris.engine.object.IrisRange;
import art.arcane.iris.util.project.noise.CNG;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.matter.MatterCavern;
public class IrisCaveCarver3D {
private static final byte LIQUID_AIR = 0;
private static final byte LIQUID_WATER = 1;
private static final byte LIQUID_LAVA = 2;
private static final byte LIQUID_FORCED_AIR = 3;
private final Engine engine;
private final IrisData data;
private final IrisCaveProfile profile;
private final CNG baseDensity;
private final CNG detailDensity;
private final CNG warpDensity;
private final CNG surfaceBreakDensity;
private final RNG thresholdRng;
private final KList<ModuleState> modules;
private final double normalization;
private final MatterCavern carveAir;
private final MatterCavern carveWater;
private final MatterCavern carveLava;
private final MatterCavern carveForcedAir;
public IrisCaveCarver3D(Engine engine, IrisCaveProfile profile) {
this.engine = engine;
this.data = engine.getData();
this.profile = profile;
this.carveAir = new MatterCavern(true, "", LIQUID_AIR);
this.carveWater = new MatterCavern(true, "", LIQUID_WATER);
this.carveLava = new MatterCavern(true, "", LIQUID_LAVA);
this.carveForcedAir = new MatterCavern(true, "", LIQUID_FORCED_AIR);
this.modules = new KList<>();
RNG baseRng = new RNG(engine.getSeedManager().getCarve());
this.baseDensity = profile.getBaseDensityStyle().create(baseRng.nextParallelRNG(934_447), data);
this.detailDensity = profile.getDetailDensityStyle().create(baseRng.nextParallelRNG(612_991), data);
this.warpDensity = profile.getWarpStyle().create(baseRng.nextParallelRNG(770_713), data);
this.surfaceBreakDensity = profile.getSurfaceBreakStyle().create(baseRng.nextParallelRNG(341_219), data);
this.thresholdRng = baseRng.nextParallelRNG(489_112);
double weight = Math.abs(profile.getBaseWeight()) + Math.abs(profile.getDetailWeight());
int index = 0;
for (IrisCaveFieldModule module : profile.getModules()) {
CNG moduleDensity = module.getStyle().create(baseRng.nextParallelRNG(1_000_003L + (index * 65_537L)), data);
ModuleState state = new ModuleState(module, moduleDensity);
modules.add(state);
weight += Math.abs(state.weight);
index++;
}
normalization = weight <= 0 ? 1 : weight;
}
public int carve(MantleWriter writer, int chunkX, int chunkZ) {
int worldHeight = writer.getMantle().getWorldHeight();
int minY = Math.max(0, (int) Math.floor(profile.getVerticalRange().getMin()));
int maxY = Math.min(worldHeight - 1, (int) Math.ceil(profile.getVerticalRange().getMax()));
int sampleStep = Math.max(1, profile.getSampleStep());
int surfaceClearance = Math.max(0, profile.getSurfaceClearance());
int surfaceBreakDepth = Math.max(0, profile.getSurfaceBreakDepth());
double surfaceBreakNoiseThreshold = profile.getSurfaceBreakNoiseThreshold();
double surfaceBreakThresholdBoost = Math.max(0, profile.getSurfaceBreakThresholdBoost());
int waterMinDepthBelowSurface = Math.max(0, profile.getWaterMinDepthBelowSurface());
boolean waterRequiresFloor = profile.isWaterRequiresFloor();
boolean allowSurfaceBreak = profile.isAllowSurfaceBreak();
if (maxY < minY) {
return 0;
}
int x0 = chunkX << 4;
int z0 = chunkZ << 4;
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) {
continue;
}
for (int y = minY; y <= columnMaxY; y += sampleStep) {
double localThreshold = threshold;
if (surfaceBreakColumn && y >= surfaceBreakFloorY) {
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++;
}
}
}
}
}
return carved;
}
private double sampleDensity(int x, int y, int z) {
double warpedX = x;
double warpedY = y;
double warpedZ = z;
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;
warpedX += offsetX;
warpedY += offsetY;
warpedZ += offsetZ;
}
double density = signed(baseDensity.noise(warpedX, warpedY, warpedZ)) * profile.getBaseWeight();
density += signed(detailDensity.noise(warpedX, warpedY, warpedZ)) * profile.getDetailWeight();
for (ModuleState module : modules) {
if (y < module.minY || y > module.maxY) {
continue;
}
double moduleDensity = signed(module.density.noise(warpedX, warpedY, warpedZ)) - module.threshold;
if (module.invert) {
moduleDensity = -moduleDensity;
}
density += moduleDensity * module.weight;
}
return density / normalization;
}
private MatterCavern resolveMatter(int x, int y, int z, double localThreshold, int waterMinDepthBelowSurface, boolean waterRequiresFloor) {
int lavaHeight = engine.getDimension().getCaveLavaHeight();
int fluidHeight = engine.getDimension().getFluidHeight();
if (profile.isAllowLava() && y <= lavaHeight) {
return carveLava;
}
if (profile.isAllowWater() && y <= fluidHeight) {
int surfaceY = engine.getHeight(x, z);
if (surfaceY - y < waterMinDepthBelowSurface) {
return carveAir;
}
double depthFactor = Math.max(0, Math.min(1.5, (fluidHeight - y) / 48D));
double cutoff = 0.35 + (depthFactor * 0.2);
double aquifer = signed(detailDensity.noise(x, y * 0.5D, z));
if (aquifer <= cutoff) {
return carveAir;
}
if (waterRequiresFloor && !hasAquiferCupSupport(x, y, z, localThreshold)) {
return carveAir;
}
return carveWater;
}
if (!profile.isAllowLava() && y <= lavaHeight) {
return carveForcedAir;
}
return carveAir;
}
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;
}
if (!isDensitySolid(x, deepFloorY, z, threshold - 0.05D)) {
return false;
}
int support = 0;
if (isDensitySolid(x + 1, y, z, threshold)) {
support++;
}
if (isDensitySolid(x - 1, y, z, threshold)) {
support++;
}
if (isDensitySolid(x, y, z + 1, threshold)) {
support++;
}
if (isDensitySolid(x, y, z - 1, threshold)) {
support++;
}
if (isDensitySolid(x, aboveY, z, threshold)) {
support++;
}
return support >= 4;
}
private boolean isDensitySolid(int x, int y, int z, double threshold) {
return sampleDensity(x, y, z) > threshold;
}
private double signed(double value) {
return (value * 2D) - 1D;
}
private static final class ModuleState {
private final CNG density;
private final int minY;
private final int maxY;
private final double weight;
private final double threshold;
private final boolean invert;
private ModuleState(IrisCaveFieldModule module, CNG density) {
IrisRange range = module.getVerticalRange();
this.density = density;
this.minY = (int) Math.floor(range.getMin());
this.maxY = (int) Math.ceil(range.getMax());
this.weight = module.getWeight();
this.threshold = module.getThreshold();
this.invert = module.isInvert();
}
}
}

View File

@@ -25,14 +25,21 @@ import art.arcane.iris.engine.mantle.IrisMantleComponent;
import art.arcane.iris.engine.mantle.MantleWriter;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisCarving;
import art.arcane.iris.engine.object.IrisCaveProfile;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.util.project.context.ChunkContext;
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
import art.arcane.volmlib.util.math.RNG;
import java.util.IdentityHashMap;
import java.util.Map;
@ComponentFlag(ReservedFlag.CARVED)
public class MantleCarvingComponent extends IrisMantleComponent {
private final Map<IrisCaveProfile, IrisCaveCarver3D> profileCarvers = new IdentityHashMap<>();
public MantleCarvingComponent(EngineMantle engineMantle) {
super(engineMantle, ReservedFlag.CARVED, 0);
}
@@ -49,9 +56,18 @@ public class MantleCarvingComponent extends IrisMantleComponent {
@ChunkCoordinates
private void carve(MantleWriter writer, RNG rng, int cx, int cz, IrisRegion region, IrisBiome biome) {
carve(getDimension().getCarving(), writer, new RNG((rng.nextLong() * cx) + 490495 + cz), cx, cz);
carve(biome.getCarving(), writer, new RNG((rng.nextLong() * cx) + 490495 + cz), cx, cz);
carve(region.getCarving(), writer, new RNG((rng.nextLong() * cx) + 490495 + cz), cx, cz);
IrisCaveProfile dimensionProfile = getDimension().getCaveProfile();
IrisCaveProfile biomeProfile = biome.getCaveProfile();
IrisCaveProfile regionProfile = region.getCaveProfile();
IrisCaveProfile activeProfile = resolveActiveProfile(dimensionProfile, regionProfile, biomeProfile);
if (isProfileEnabled(activeProfile)) {
carveProfile(activeProfile, writer, cx, cz);
return;
}
carve(getDimension().getCarving(), writer, nextCarveRng(rng, cx, cz), cx, cz);
carve(biome.getCarving(), writer, nextCarveRng(rng, cx, cz), cx, cz);
carve(region.getCarving(), writer, nextCarveRng(rng, cx, cz), cx, cz);
}
@ChunkCoordinates
@@ -59,17 +75,60 @@ public class MantleCarvingComponent extends IrisMantleComponent {
carving.doCarving(writer, rng, getEngineMantle().getEngine(), cx << 4, -1, cz << 4, 0);
}
private RNG nextCarveRng(RNG rng, int cx, int cz) {
return new RNG((rng.nextLong() * cx) + 490495L + cz);
}
@ChunkCoordinates
private void carveProfile(IrisCaveProfile profile, MantleWriter writer, int cx, int cz) {
if (!isProfileEnabled(profile)) {
return;
}
IrisCaveCarver3D carver = getCarver(profile);
carver.carve(writer, cx, cz);
}
private IrisCaveCarver3D getCarver(IrisCaveProfile profile) {
synchronized (profileCarvers) {
IrisCaveCarver3D carver = profileCarvers.get(profile);
if (carver != null) {
return carver;
}
IrisCaveCarver3D createdCarver = new IrisCaveCarver3D(getEngineMantle().getEngine(), profile);
profileCarvers.put(profile, createdCarver);
return createdCarver;
}
}
private boolean isProfileEnabled(IrisCaveProfile profile) {
return profile != null && profile.isEnabled();
}
private IrisCaveProfile resolveActiveProfile(IrisCaveProfile dimensionProfile, IrisCaveProfile regionProfile, IrisCaveProfile biomeProfile) {
if (isProfileEnabled(biomeProfile)) {
return biomeProfile;
}
if (isProfileEnabled(regionProfile)) {
return regionProfile;
}
return dimensionProfile;
}
protected int computeRadius() {
var dimension = getDimension();
IrisDimension dimension = getDimension();
int max = 0;
max = Math.max(max, dimension.getCarving().getMaxRange(getData(), 0));
for (var i : dimension.getAllRegions(this::getData)) {
for (IrisRegion i : dimension.getAllRegions(this::getData)) {
max = Math.max(max, i.getCarving().getMaxRange(getData(), 0));
}
for (var i : dimension.getAllBiomes(this::getData)) {
for (IrisBiome i : dimension.getAllBiomes(this::getData)) {
max = Math.max(max, i.getCarving().getMaxRange(getData(), 0));
}

View File

@@ -63,21 +63,29 @@ public class MantleObjectComponent 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);
IrisBiome surfaceBiome = getComplex().getTrueBiomeStream().get(xxx, zzz);
IrisBiome caveBiome = getComplex().getCaveBiomeStream().get(xxx, zzz);
if (traceRegen) {
Iris.info("Regen object layer start: chunk=" + x + "," + z
+ " biome=" + biome.getLoadKey()
+ " surfaceBiome=" + surfaceBiome.getLoadKey()
+ " caveBiome=" + caveBiome.getLoadKey()
+ " region=" + region.getLoadKey()
+ " biomePlacers=" + biome.getSurfaceObjects().size()
+ " regionPlacers=" + region.getSurfaceObjects().size());
+ " biomeSurfacePlacers=" + surfaceBiome.getSurfaceObjects().size()
+ " biomeCavePlacers=" + caveBiome.getCarvingObjects().size()
+ " regionSurfacePlacers=" + region.getSurfaceObjects().size()
+ " regionCavePlacers=" + region.getCarvingObjects().size());
}
ObjectPlacementSummary summary = placeObjects(writer, rng, x, z, biome, region, traceRegen);
ObjectPlacementSummary summary = placeObjects(writer, rng, x, z, surfaceBiome, caveBiome, region, traceRegen);
if (traceRegen) {
Iris.info("Regen object layer done: chunk=" + x + "," + z
+ " biomePlacersChecked=" + summary.biomePlacersChecked()
+ " biomePlacersTriggered=" + summary.biomePlacersTriggered()
+ " regionPlacersChecked=" + summary.regionPlacersChecked()
+ " regionPlacersTriggered=" + summary.regionPlacersTriggered()
+ " biomeSurfacePlacersChecked=" + summary.biomeSurfacePlacersChecked()
+ " biomeSurfacePlacersTriggered=" + summary.biomeSurfacePlacersTriggered()
+ " biomeCavePlacersChecked=" + summary.biomeCavePlacersChecked()
+ " biomeCavePlacersTriggered=" + summary.biomeCavePlacersTriggered()
+ " regionSurfacePlacersChecked=" + summary.regionSurfacePlacersChecked()
+ " regionSurfacePlacersTriggered=" + summary.regionSurfacePlacersTriggered()
+ " regionCavePlacersChecked=" + summary.regionCavePlacersChecked()
+ " regionCavePlacersTriggered=" + summary.regionCavePlacersTriggered()
+ " objectAttempts=" + summary.objectAttempts()
+ " objectPlaced=" + summary.objectPlaced()
+ " objectRejected=" + summary.objectRejected()
@@ -92,32 +100,40 @@ public class MantleObjectComponent extends IrisMantleComponent {
}
@ChunkCoordinates
private ObjectPlacementSummary placeObjects(MantleWriter writer, RNG rng, int x, int z, IrisBiome biome, IrisRegion region, boolean traceRegen) {
int biomeChecked = 0;
int biomeTriggered = 0;
int regionChecked = 0;
int regionTriggered = 0;
private ObjectPlacementSummary placeObjects(MantleWriter writer, RNG rng, int x, int z, IrisBiome surfaceBiome, IrisBiome caveBiome, IrisRegion region, boolean traceRegen) {
int biomeSurfaceChecked = 0;
int biomeSurfaceTriggered = 0;
int biomeCaveChecked = 0;
int biomeCaveTriggered = 0;
int regionSurfaceChecked = 0;
int regionSurfaceTriggered = 0;
int regionCaveChecked = 0;
int regionCaveTriggered = 0;
int attempts = 0;
int placed = 0;
int rejected = 0;
int nullObjects = 0;
int errors = 0;
IrisCaveProfile biomeCaveProfile = resolveCaveProfile(caveBiome.getCaveProfile(), region.getCaveProfile());
IrisCaveProfile regionCaveProfile = resolveCaveProfile(region.getCaveProfile(), caveBiome.getCaveProfile());
int biomeSurfaceExclusionDepth = resolveSurfaceObjectExclusionDepth(biomeCaveProfile);
int regionSurfaceExclusionDepth = resolveSurfaceObjectExclusionDepth(regionCaveProfile);
for (IrisObjectPlacement i : biome.getSurfaceObjects()) {
biomeChecked++;
for (IrisObjectPlacement i : surfaceBiome.getSurfaceObjects()) {
biomeSurfaceChecked++;
boolean chance = rng.chance(i.getChance() + rng.d(-0.005, 0.005));
if (traceRegen) {
Iris.info("Regen object placer chance: chunk=" + x + "," + z
+ " scope=biome"
+ " scope=biome-surface"
+ " chanceResult=" + chance
+ " chanceBase=" + i.getChance()
+ " densityMid=" + i.getDensity()
+ " objects=" + i.getPlace().size());
}
if (chance) {
biomeTriggered++;
biomeSurfaceTriggered++;
try {
ObjectPlacementResult result = placeObject(writer, rng, x << 4, z << 4, i, traceRegen, x, z, "biome");
ObjectPlacementResult result = placeObject(writer, rng, x << 4, z << 4, i, biomeSurfaceExclusionDepth, traceRegen, x, z, "biome-surface");
attempts += result.attempts();
placed += result.placed();
rejected += result.rejected();
@@ -126,7 +142,41 @@ public class MantleObjectComponent extends IrisMantleComponent {
} catch (Throwable e) {
errors++;
Iris.reportError(e);
Iris.error("Failed to place objects in the following biome: " + biome.getName());
Iris.error("Failed to place objects in the following biome: " + surfaceBiome.getName());
Iris.error("Object(s) " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ").");
Iris.error("Are these objects missing?");
e.printStackTrace();
}
}
}
for (IrisObjectPlacement i : caveBiome.getCarvingObjects()) {
if (!i.getCarvingSupport().equals(CarvingMode.CARVING_ONLY)) {
continue;
}
biomeCaveChecked++;
boolean chance = rng.chance(i.getChance() + rng.d(-0.005, 0.005));
if (traceRegen) {
Iris.info("Regen object placer chance: chunk=" + x + "," + z
+ " scope=biome-cave"
+ " chanceResult=" + chance
+ " chanceBase=" + i.getChance()
+ " densityMid=" + i.getDensity()
+ " objects=" + i.getPlace().size());
}
if (chance) {
biomeCaveTriggered++;
try {
ObjectPlacementResult result = placeCaveObject(writer, rng, x, z, i, biomeCaveProfile, traceRegen, x, z, "biome-cave");
attempts += result.attempts();
placed += result.placed();
rejected += result.rejected();
nullObjects += result.nullObjects();
errors += result.errors();
} catch (Throwable e) {
errors++;
Iris.reportError(e);
Iris.error("Failed to place cave objects in the following biome: " + caveBiome.getName());
Iris.error("Object(s) " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ").");
Iris.error("Are these objects missing?");
e.printStackTrace();
@@ -135,20 +185,20 @@ public class MantleObjectComponent extends IrisMantleComponent {
}
for (IrisObjectPlacement i : region.getSurfaceObjects()) {
regionChecked++;
regionSurfaceChecked++;
boolean chance = rng.chance(i.getChance() + rng.d(-0.005, 0.005));
if (traceRegen) {
Iris.info("Regen object placer chance: chunk=" + x + "," + z
+ " scope=region"
+ " scope=region-surface"
+ " chanceResult=" + chance
+ " chanceBase=" + i.getChance()
+ " densityMid=" + i.getDensity()
+ " objects=" + i.getPlace().size());
}
if (chance) {
regionTriggered++;
regionSurfaceTriggered++;
try {
ObjectPlacementResult result = placeObject(writer, rng, x << 4, z << 4, i, traceRegen, x, z, "region");
ObjectPlacementResult result = placeObject(writer, rng, x << 4, z << 4, i, regionSurfaceExclusionDepth, traceRegen, x, z, "region-surface");
attempts += result.attempts();
placed += result.placed();
rejected += result.rejected();
@@ -165,11 +215,49 @@ public class MantleObjectComponent extends IrisMantleComponent {
}
}
for (IrisObjectPlacement i : region.getCarvingObjects()) {
if (!i.getCarvingSupport().equals(CarvingMode.CARVING_ONLY)) {
continue;
}
regionCaveChecked++;
boolean chance = rng.chance(i.getChance() + rng.d(-0.005, 0.005));
if (traceRegen) {
Iris.info("Regen object placer chance: chunk=" + x + "," + z
+ " scope=region-cave"
+ " chanceResult=" + chance
+ " chanceBase=" + i.getChance()
+ " densityMid=" + i.getDensity()
+ " objects=" + i.getPlace().size());
}
if (chance) {
regionCaveTriggered++;
try {
ObjectPlacementResult result = placeCaveObject(writer, rng, x, z, i, regionCaveProfile, traceRegen, x, z, "region-cave");
attempts += result.attempts();
placed += result.placed();
rejected += result.rejected();
nullObjects += result.nullObjects();
errors += result.errors();
} catch (Throwable e) {
errors++;
Iris.reportError(e);
Iris.error("Failed to place cave objects in the following region: " + region.getName());
Iris.error("Object(s) " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ").");
Iris.error("Are these objects missing?");
e.printStackTrace();
}
}
}
return new ObjectPlacementSummary(
biomeChecked,
biomeTriggered,
regionChecked,
regionTriggered,
biomeSurfaceChecked,
biomeSurfaceTriggered,
biomeCaveChecked,
biomeCaveTriggered,
regionSurfaceChecked,
regionSurfaceTriggered,
regionCaveChecked,
regionCaveTriggered,
attempts,
placed,
rejected,
@@ -185,6 +273,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
int x,
int z,
IrisObjectPlacement objectPlacement,
int surfaceObjectExclusionDepth,
boolean traceRegen,
int chunkX,
int chunkZ,
@@ -213,6 +302,11 @@ public class MantleObjectComponent extends IrisMantleComponent {
}
int xx = rng.i(x, x + 15);
int zz = rng.i(z, z + 15);
int surfaceObjectExclusionRadius = resolveSurfaceObjectExclusionRadius(v);
if (surfaceObjectExclusionDepth > 0 && hasSurfaceCarveExposure(writer, xx, zz, surfaceObjectExclusionDepth, surfaceObjectExclusionRadius)) {
rejected++;
continue;
}
int id = rng.i(0, Integer.MAX_VALUE);
try {
int result = v.place(xx, -1, zz, writer, objectPlacement, rng, (b, data) -> {
@@ -253,16 +347,270 @@ public class MantleObjectComponent extends IrisMantleComponent {
return new ObjectPlacementResult(attempts, placed, rejected, nullObjects, errors);
}
@ChunkCoordinates
private ObjectPlacementResult placeCaveObject(
MantleWriter writer,
RNG rng,
int chunkX,
int chunkZ,
IrisObjectPlacement objectPlacement,
IrisCaveProfile caveProfile,
boolean traceRegen,
int metricChunkX,
int metricChunkZ,
String scope
) {
int attempts = 0;
int placed = 0;
int rejected = 0;
int nullObjects = 0;
int errors = 0;
int minX = chunkX << 4;
int minZ = chunkZ << 4;
int density = objectPlacement.getDensity(rng, minX, minZ, getData());
KMap<Long, KList<Integer>> anchorCache = new KMap<>();
IrisCaveAnchorMode anchorMode = resolveAnchorMode(objectPlacement, caveProfile);
int anchorScanStep = resolveAnchorScanStep(caveProfile);
int objectMinDepthBelowSurface = resolveObjectMinDepthBelowSurface(caveProfile);
int anchorSearchAttempts = resolveAnchorSearchAttempts(caveProfile);
for (int i = 0; i < density; i++) {
attempts++;
IrisObject object = objectPlacement.getScale().get(rng, objectPlacement.getObject(getComplex(), rng));
if (object == null) {
nullObjects++;
if (traceRegen) {
Iris.warn("Regen cave object placement null object: chunk=" + metricChunkX + "," + metricChunkZ
+ " scope=" + scope
+ " densityIndex=" + i
+ " density=" + density
+ " placementKeys=" + objectPlacement.getPlace().toString(","));
}
continue;
}
int x = 0;
int z = 0;
int y = -1;
for (int search = 0; search < anchorSearchAttempts; search++) {
int candidateX = rng.i(minX, minX + 15);
int candidateZ = rng.i(minZ, minZ + 15);
int candidateY = findCaveAnchorY(writer, rng, candidateX, candidateZ, anchorMode, anchorScanStep, objectMinDepthBelowSurface, anchorCache);
if (candidateY < 0) {
continue;
}
x = candidateX;
z = candidateZ;
y = candidateY;
break;
}
if (y < 0) {
rejected++;
continue;
}
int id = rng.i(0, Integer.MAX_VALUE);
try {
int result = object.place(x, y, z, writer, objectPlacement, rng, (b, data) -> {
writer.setData(b.getX(), b.getY(), b.getZ(), object.getLoadKey() + "@" + id);
if (objectPlacement.isDolphinTarget() && objectPlacement.isUnderwater() && B.isStorageChest(data)) {
writer.setData(b.getX(), b.getY(), b.getZ(), MatterStructurePOI.BURIED_TREASURE);
}
}, null, getData());
if (result >= 0) {
placed++;
} else {
rejected++;
}
if (traceRegen) {
Iris.info("Regen cave object placement result: chunk=" + metricChunkX + "," + metricChunkZ
+ " scope=" + scope
+ " object=" + object.getLoadKey()
+ " resultY=" + result
+ " anchorY=" + y
+ " px=" + x
+ " pz=" + z
+ " densityIndex=" + i
+ " density=" + density);
}
} catch (Throwable e) {
errors++;
Iris.reportError(e);
Iris.error("Regen cave object placement exception: chunk=" + metricChunkX + "," + metricChunkZ
+ " scope=" + scope
+ " object=" + object.getLoadKey()
+ " densityIndex=" + i
+ " density=" + density
+ " error=" + e.getClass().getSimpleName() + ":" + e.getMessage());
}
}
return new ObjectPlacementResult(attempts, placed, rejected, nullObjects, errors);
}
private int findCaveAnchorY(MantleWriter writer, RNG rng, int x, int z, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, KMap<Long, KList<Integer>> anchorCache) {
long key = Cache.key(x, z);
KList<Integer> anchors = anchorCache.computeIfAbsent(key, (k) -> scanCaveAnchorColumn(writer, anchorMode, anchorScanStep, objectMinDepthBelowSurface, x, z));
if (anchors.isEmpty()) {
return -1;
}
if (anchors.size() == 1) {
return anchors.get(0);
}
return anchors.get(rng.i(0, anchors.size() - 1));
}
private KList<Integer> scanCaveAnchorColumn(MantleWriter writer, IrisCaveAnchorMode anchorMode, int anchorScanStep, int objectMinDepthBelowSurface, int x, int z) {
KList<Integer> anchors = new KList<>();
int height = getEngineMantle().getEngine().getHeight();
int step = Math.max(1, anchorScanStep);
int surfaceY = getEngineMantle().getEngine().getHeight(x, z);
int maxAnchorY = Math.min(height - 1, surfaceY - Math.max(0, objectMinDepthBelowSurface));
if (maxAnchorY <= 1) {
return anchors;
}
for (int y = 1; y < maxAnchorY; y += step) {
if (!writer.isCarved(x, y, z)) {
continue;
}
boolean solidBelow = y <= 0 || !writer.isCarved(x, y - 1, z);
boolean solidAbove = y >= (height - 1) || !writer.isCarved(x, y + 1, z);
if (matchesCaveAnchor(anchorMode, solidBelow, solidAbove)) {
anchors.add(y);
}
}
return anchors;
}
private boolean matchesCaveAnchor(IrisCaveAnchorMode anchorMode, boolean solidBelow, boolean solidAbove) {
return switch (anchorMode) {
case PROFILE_DEFAULT, FLOOR -> solidBelow;
case CEILING -> solidAbove;
case CENTER -> !solidBelow && !solidAbove;
case ANY -> true;
};
}
private IrisCaveProfile resolveCaveProfile(IrisCaveProfile preferred, IrisCaveProfile secondary) {
IrisCaveProfile dimensionProfile = getDimension().getCaveProfile();
if (preferred != null && preferred.isEnabled()) {
return preferred;
}
if (secondary != null && secondary.isEnabled()) {
return secondary;
}
if (dimensionProfile != null) {
return dimensionProfile;
}
return new IrisCaveProfile();
}
private IrisCaveAnchorMode resolveAnchorMode(IrisObjectPlacement objectPlacement, IrisCaveProfile caveProfile) {
IrisCaveAnchorMode placementMode = objectPlacement.getCaveAnchorMode();
if (placementMode != null && !placementMode.equals(IrisCaveAnchorMode.PROFILE_DEFAULT)) {
return placementMode;
}
if (caveProfile == null) {
return IrisCaveAnchorMode.FLOOR;
}
IrisCaveAnchorMode profileMode = caveProfile.getDefaultObjectAnchor();
if (profileMode == null || profileMode.equals(IrisCaveAnchorMode.PROFILE_DEFAULT)) {
return IrisCaveAnchorMode.FLOOR;
}
return profileMode;
}
private int resolveAnchorScanStep(IrisCaveProfile caveProfile) {
if (caveProfile == null) {
return 1;
}
return Math.max(1, caveProfile.getAnchorScanStep());
}
private int resolveObjectMinDepthBelowSurface(IrisCaveProfile caveProfile) {
if (caveProfile == null) {
return 6;
}
return Math.max(0, caveProfile.getObjectMinDepthBelowSurface());
}
private int resolveSurfaceObjectExclusionDepth(IrisCaveProfile caveProfile) {
if (caveProfile == null) {
return 5;
}
return Math.max(0, caveProfile.getSurfaceObjectExclusionDepth());
}
private int resolveSurfaceObjectExclusionRadius(IrisObject object) {
if (object == null) {
return 1;
}
int maxDimension = Math.max(object.getW(), object.getD());
return Math.max(1, Math.min(8, Math.floorDiv(Math.max(1, maxDimension), 2)));
}
private int resolveAnchorSearchAttempts(IrisCaveProfile caveProfile) {
if (caveProfile == null) {
return 6;
}
return Math.max(1, caveProfile.getAnchorSearchAttempts());
}
private boolean hasSurfaceCarveExposure(MantleWriter writer, int x, int z, int depth, int radius) {
int horizontalRadius = Math.max(0, radius);
for (int dx = -horizontalRadius; dx <= horizontalRadius; dx++) {
for (int dz = -horizontalRadius; dz <= horizontalRadius; dz++) {
int columnX = x + dx;
int columnZ = z + dz;
int surfaceY = getEngineMantle().getEngine().getHeight(columnX, columnZ, true);
int fromY = Math.max(1, surfaceY - Math.max(0, depth));
int toY = Math.min(getEngineMantle().getEngine().getHeight() - 1, surfaceY + 1);
for (int y = fromY; y <= toY; y++) {
if (writer.isCarved(columnX, y, columnZ)) {
return true;
}
}
}
}
return false;
}
private boolean isRegenTraceThread() {
return Thread.currentThread().getName().startsWith("Iris-Regen-")
&& IrisSettings.get().getGeneral().isDebug();
}
private record ObjectPlacementSummary(
int biomePlacersChecked,
int biomePlacersTriggered,
int regionPlacersChecked,
int regionPlacersTriggered,
int biomeSurfacePlacersChecked,
int biomeSurfacePlacersTriggered,
int biomeCavePlacersChecked,
int biomeCavePlacersTriggered,
int regionSurfacePlacersChecked,
int regionSurfacePlacersTriggered,
int regionCavePlacersChecked,
int regionCavePlacersTriggered,
int objectAttempts,
int objectPlaced,
int objectRejected,

View File

@@ -108,6 +108,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
output.set(rx, yy, rz, context.getFluid().get(rx, rz));
} else if (c.isLava()) {
output.set(rx, yy, rz, LAVA);
} else if (c.getLiquid() == 3) {
output.set(rx, yy, rz, AIR);
} else {
if (getEngine().getDimension().getCaveLavaHeight() > yy) {
output.set(rx, yy, rz, LAVA);

View File

@@ -102,6 +102,8 @@ public class IrisBiome extends IrisRegistrant implements IRare {
private int lockLayersMax = 7;
@Desc("Carving configuration for the dimension")
private IrisCarving carving = new IrisCarving();
@Desc("Profile-driven 3D cave configuration")
private IrisCaveProfile caveProfile = new IrisCaveProfile();
@Desc("Configuration of fluid bodies such as rivers & lakes")
private IrisFluidBodies fluidBodies = new IrisFluidBodies();
@MinNumber(1)
@@ -194,12 +196,21 @@ public class IrisBiome extends IrisRegistrant implements IRare {
}
public double getGenLinkMax(String loadKey, Engine engine) {
if (loadKey == null || loadKey.isBlank()) {
return 0;
}
Integer v = genCacheMax.aquire(() ->
{
KMap<String, Integer> l = new KMap<>();
for (IrisBiomeGeneratorLink i : getGenerators()) {
l.put(i.getGenerator(), i.getMax());
String generatorKey = i.getGenerator();
if (generatorKey == null || generatorKey.isBlank()) {
continue;
}
l.put(generatorKey, i.getMax());
}
@@ -210,12 +221,21 @@ public class IrisBiome extends IrisRegistrant implements IRare {
}
public double getGenLinkMin(String loadKey, Engine engine) {
if (loadKey == null || loadKey.isBlank()) {
return 0;
}
Integer v = genCacheMin.aquire(() ->
{
KMap<String, Integer> l = new KMap<>();
for (IrisBiomeGeneratorLink i : getGenerators()) {
l.put(i.getGenerator(), i.getMin());
String generatorKey = i.getGenerator();
if (generatorKey == null || generatorKey.isBlank()) {
continue;
}
l.put(generatorKey, i.getMin());
}
return l;
@@ -225,12 +245,21 @@ public class IrisBiome extends IrisRegistrant implements IRare {
}
public IrisBiomeGeneratorLink getGenLink(String loadKey) {
if (loadKey == null || loadKey.isBlank()) {
return null;
}
return genCache.aquire(() ->
{
KMap<String, IrisBiomeGeneratorLink> l = new KMap<>();
for (IrisBiomeGeneratorLink i : getGenerators()) {
l.put(i.getGenerator(), i);
String generatorKey = i.getGenerator();
if (generatorKey == null || generatorKey.isBlank()) {
continue;
}
l.put(generatorKey, i);
}
return l;

View File

@@ -52,12 +52,21 @@ public class IrisBiomeGeneratorLink {
public IrisGenerator getCachedGenerator(DataProvider g) {
return gen.aquire(() -> {
IrisGenerator gen = g.getData().getGeneratorLoader().load(getGenerator());
String generatorKey = getGenerator();
if (generatorKey == null || generatorKey.isBlank()) {
generatorKey = "default";
}
IrisGenerator gen = g.getData().getGeneratorLoader().load(generatorKey);
if (gen == null) {
gen = new IrisGenerator();
}
if (gen.getLoadKey() == null || gen.getLoadKey().isBlank()) {
gen.setLoadKey(generatorKey);
}
return gen;
});
}

View File

@@ -0,0 +1,21 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.engine.object.annotations.Desc;
@Desc("Defines which carved-space anchor to target for cave object placement.")
public enum IrisCaveAnchorMode {
@Desc("Use the active cave profile default anchor mode.")
PROFILE_DEFAULT,
@Desc("Target cave floor anchors where carved space has solid support below.")
FLOOR,
@Desc("Target cave ceiling anchors where carved space has solid support above.")
CEILING,
@Desc("Target carved positions with no immediate solid support above or below.")
CENTER,
@Desc("Target any carved-space anchor.")
ANY
}

View File

@@ -0,0 +1,36 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.engine.object.annotations.Desc;
import art.arcane.iris.engine.object.annotations.MaxNumber;
import art.arcane.iris.engine.object.annotations.MinNumber;
import art.arcane.iris.engine.object.annotations.Snippet;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Snippet("cave-field-module")
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@Desc("Represents a modular cave-density layer.")
@Data
public class IrisCaveFieldModule {
@Desc("Density style used by this module.")
private IrisGeneratorStyle style = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
@MinNumber(0)
@Desc("Layer contribution multiplier.")
private double weight = 1;
@MinNumber(-1)
@MaxNumber(1)
@Desc("Threshold offset applied to this layer before blending.")
private double threshold = 0;
@Desc("Vertical bounds where this module can contribute.")
private IrisRange verticalRange = new IrisRange(0, 384);
@Desc("Invert this module before weighting.")
private boolean invert = false;
}

View File

@@ -0,0 +1,127 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.engine.object.annotations.ArrayType;
import art.arcane.iris.engine.object.annotations.Desc;
import art.arcane.iris.engine.object.annotations.MaxNumber;
import art.arcane.iris.engine.object.annotations.MinNumber;
import art.arcane.iris.engine.object.annotations.Snippet;
import art.arcane.volmlib.util.collection.KList;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Snippet("cave-profile")
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@Desc("Represents a configurable 3D cave profile.")
@Data
public class IrisCaveProfile {
@Desc("Enable profile-driven cave carving.")
private boolean enabled = false;
@Desc("Global vertical bounds for profile cave carving.")
private IrisRange verticalRange = new IrisRange(0, 384);
@Desc("Base density style for cave field generation.")
private IrisGeneratorStyle baseDensityStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
@Desc("Detail density style blended into base caves.")
private IrisGeneratorStyle detailDensityStyle = new IrisGeneratorStyle(NoiseStyle.SIMPLEX);
@Desc("Warp style used to distort cave coordinates.")
private IrisGeneratorStyle warpStyle = new IrisGeneratorStyle(NoiseStyle.FLAT);
@MinNumber(0)
@Desc("Base cave field multiplier.")
private double baseWeight = 1;
@MinNumber(0)
@Desc("Detail cave field multiplier.")
private double detailWeight = 0.35;
@MinNumber(0)
@Desc("Coordinate warp strength for cave fields.")
private double warpStrength = 0;
@Desc("Threshold range used for carve cutoff decisions.")
private IrisStyledRange densityThreshold = new IrisStyledRange(-0.2, 0.2, NoiseStyle.CELLULAR_IRIS_DOUBLE.style());
@MinNumber(0)
@MaxNumber(1)
@Desc("Extra threshold bias subtracted from sampled threshold before carve tests.")
private double thresholdBias = 0.16;
@MinNumber(1)
@MaxNumber(8)
@Desc("Vertical sample step used while evaluating cave density.")
private int sampleStep = 2;
@MinNumber(0)
@MaxNumber(64)
@Desc("Minimum solid clearance below terrain surface where carving may occur.")
private int surfaceClearance = 4;
@Desc("Allow profile-driven cave carving to break through terrain surface in selected columns.")
private boolean allowSurfaceBreak = true;
@Desc("Noise style used to decide where surface-breaking cave columns are allowed.")
private IrisGeneratorStyle surfaceBreakStyle = new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.08);
@MinNumber(-1)
@MaxNumber(1)
@Desc("Minimum signed surface-break noise value required before near-surface carving is allowed.")
private double surfaceBreakNoiseThreshold = 0.62;
@MinNumber(0)
@MaxNumber(64)
@Desc("Near-surface depth window used for surface-break carve logic.")
private int surfaceBreakDepth = 18;
@MinNumber(0)
@MaxNumber(1)
@Desc("Additional threshold boost applied while carving in the surface-break depth window.")
private double surfaceBreakThresholdBoost = 0.2;
@MinNumber(0)
@MaxNumber(64)
@Desc("Minimum depth below terrain surface required for cave-only object anchor placement.")
private int objectMinDepthBelowSurface = 6;
@MinNumber(0)
@MaxNumber(32)
@Desc("Skip surface-object placement when carved cells exist this many blocks below terrain surface.")
private int surfaceObjectExclusionDepth = 5;
@ArrayType(type = IrisCaveFieldModule.class, min = 1)
@Desc("Additional layered cave-density modules.")
private KList<IrisCaveFieldModule> modules = new KList<>();
@Desc("Default cave anchor mode for cave-only object placement.")
private IrisCaveAnchorMode defaultObjectAnchor = IrisCaveAnchorMode.FLOOR;
@MinNumber(1)
@MaxNumber(8)
@Desc("Vertical scan step used while searching cave anchors.")
private int anchorScanStep = 1;
@MinNumber(1)
@MaxNumber(64)
@Desc("Maximum random column retries while searching a valid cave object anchor in the chunk.")
private int anchorSearchAttempts = 6;
@Desc("Allow cave water placement below fluid level.")
private boolean allowWater = true;
@MinNumber(0)
@MaxNumber(64)
@Desc("Minimum depth below terrain surface required before cave water may be placed.")
private int waterMinDepthBelowSurface = 12;
@Desc("Require solid floor support below cave water to reduce cascading cave waterfalls.")
private boolean waterRequiresFloor = true;
@Desc("Allow cave lava placement based on lava height.")
private boolean allowLava = true;
}

View File

@@ -142,10 +142,12 @@ public class IrisDimension extends IrisRegistrant {
private boolean postProcessingSlabs = true;
@Desc("Add painted walls in post processing")
private boolean postProcessingWalls = true;
@Desc("Carving configuration for the dimension")
private IrisCarving carving = new IrisCarving();
@Desc("Configuration of fluid bodies such as rivers & lakes")
private IrisFluidBodies fluidBodies = new IrisFluidBodies();
@Desc("Carving configuration for the dimension")
private IrisCarving carving = new IrisCarving();
@Desc("Profile-driven 3D cave configuration")
private IrisCaveProfile caveProfile = new IrisCaveProfile();
@Desc("Configuration of fluid bodies such as rivers & lakes")
private IrisFluidBodies fluidBodies = new IrisFluidBodies();
@Desc("forceConvertTo320Height")
private Boolean forceConvertTo320Height = false;
@Desc("The world environment")

View File

@@ -689,10 +689,7 @@ public class IrisObject extends IrisRegistrant {
// todo Convert this to a dedicated mode.
y = (getH() + 1) + rty;
if (!config.isForcePlace()) {
if (placer.isCarved(x, y, z) ||
placer.isCarved(x, y - 1, z) ||
placer.isCarved(x, y - 2, z) ||
placer.isCarved(x, y - 3, z)) {
if (shouldBailForCarvingAnchor(placer, config, x, y, z)) {
bail = true;
}
}
@@ -700,7 +697,7 @@ public class IrisObject extends IrisRegistrant {
if (config.getMode().equals(ObjectPlaceMode.CENTER_HEIGHT) || config.getMode() == ObjectPlaceMode.CENTER_STILT) {
y = (c != null ? c.getSurface() : placer.getHighest(x, z, getLoader(), config.isUnderwater())) + rty;
if (!config.isForcePlace()) {
if (placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z)) {
if (shouldBailForCarvingAnchor(placer, config, x, y, z)) {
bail = true;
}
}
@@ -717,7 +714,7 @@ public class IrisObject extends IrisRegistrant {
for (int ii = minZ; ii <= maxZ; ii++) {
int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty;
if (!config.isForcePlace()) {
if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) {
if (shouldBailForCarvingAnchor(placer, config, i, h, ii)) {
bail = true;
break;
}
@@ -743,7 +740,7 @@ public class IrisObject extends IrisRegistrant {
for (int ii = minZ; ii <= maxZ; ii += Math.abs(zRadius) + 1) {
int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty;
if (!config.isForcePlace()) {
if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) {
if (shouldBailForCarvingAnchor(placer, config, i, h, ii)) {
bail = true;
break;
}
@@ -767,7 +764,7 @@ public class IrisObject extends IrisRegistrant {
for (int ii = minZ; ii <= maxZ; ii++) {
int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty;
if (!config.isForcePlace()) {
if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) {
if (shouldBailForCarvingAnchor(placer, config, i, h, ii)) {
bail = true;
break;
}
@@ -795,7 +792,7 @@ public class IrisObject extends IrisRegistrant {
for (int ii = minZ; ii <= maxZ; ii += Math.abs(zRadius) + 1) {
int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty;
if (!config.isForcePlace()) {
if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) {
if (shouldBailForCarvingAnchor(placer, config, i, h, ii)) {
bail = true;
break;
}
@@ -808,7 +805,7 @@ public class IrisObject extends IrisRegistrant {
} else if (config.getMode().equals(ObjectPlaceMode.PAINT)) {
y = placer.getHighest(x, z, getLoader(), config.isUnderwater()) + rty;
if (!config.isForcePlace()) {
if (placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z)) {
if (shouldBailForCarvingAnchor(placer, config, x, y, z)) {
bail = true;
}
}
@@ -816,7 +813,7 @@ public class IrisObject extends IrisRegistrant {
} else {
y = yv;
if (!config.isForcePlace()) {
if (placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z)) {
if (shouldBailForCarvingAnchor(placer, config, x, y, z)) {
bail = true;
}
}
@@ -825,7 +822,7 @@ public class IrisObject extends IrisRegistrant {
if (yv >= 0 && config.isBottom()) {
y += Math.floorDiv(h, 2);
if (!config.isForcePlace()) {
bail = placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z);
bail = shouldBailForCarvingAnchor(placer, config, x, y, z);
}
}
@@ -1160,6 +1157,23 @@ public class IrisObject extends IrisRegistrant {
return y;
}
private boolean shouldBailForCarvingAnchor(IObjectPlacer placer, IrisObjectPlacement placement, int x, int y, int z) {
boolean carved = isCarvedAnchor(placer, x, y, z);
CarvingMode carvingMode = placement.getCarvingSupport();
return switch (carvingMode) {
case SURFACE_ONLY -> carved;
case CARVING_ONLY -> !carved;
case ANYWHERE -> false;
};
}
private boolean isCarvedAnchor(IObjectPlacer placer, int x, int y, int z) {
return placer.isCarved(x, y, z)
|| placer.isCarved(x, y - 1, z)
|| placer.isCarved(x, y - 2, z)
|| placer.isCarved(x, y - 3, z);
}
public IrisObject rotateCopy(IrisObjectRotation rt) {
IrisObject copy = copy();
copy.rotate(rt, 0, 0, 0);

View File

@@ -99,6 +99,8 @@ public class IrisObjectPlacement {
private boolean underwater = false;
@Desc("If set to true, objects will place in carvings (such as underground) or under an overhang.")
private CarvingMode carvingSupport = CarvingMode.SURFACE_ONLY;
@Desc("When carving placement is enabled, select which carved-space anchor this placement targets.")
private IrisCaveAnchorMode caveAnchorMode = IrisCaveAnchorMode.PROFILE_DEFAULT;
@Desc("If this is defined, this object wont place on the terrain heightmap, but instead on this virtual heightmap")
private IrisNoiseGenerator heightmap;
@Desc("If set to true, Iris will try to fill the insides of 'rooms' and 'pockets' where air should fit based off of raytrace checks. This prevents a village house placing in an area where a tree already exists, and instead replaces the parts of the tree where the interior of the structure is. \n\nThis operation does not affect warmed-up generation speed however it does slow down loading objects.")
@@ -165,6 +167,7 @@ public class IrisObjectPlacement {
p.setOnwater(onwater);
p.setSmartBore(smartBore);
p.setCarvingSupport(carvingSupport);
p.setCaveAnchorMode(caveAnchorMode);
p.setUnderwater(underwater);
p.setBoreExtendMaxY(boreExtendMaxY);
p.setBoreExtendMinY(boreExtendMinY);

View File

@@ -114,6 +114,8 @@ public class IrisRegion extends IrisRegistrant implements IRare {
private double caveBiomeZoom = 1;
@Desc("Carving configuration for the dimension")
private IrisCarving carving = new IrisCarving();
@Desc("Profile-driven 3D cave configuration")
private IrisCaveProfile caveProfile = new IrisCaveProfile();
@Desc("Configuration of fluid bodies such as rivers & lakes")
private IrisFluidBodies fluidBodies = new IrisFluidBodies();
@RegistryListResource(IrisBiome.class)

View File

@@ -203,8 +203,15 @@ public class CustomBiomeSource extends BiomeSource {
int blockX = x << 2;
int blockZ = z << 2;
int blockY = (y - engine.getMinHeight()) << 2;
IrisBiome irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
int blockY = y << 2;
int surfaceY = engine.getComplex().getHeightStream().get(blockX, blockZ).intValue();
boolean underground = blockY <= surfaceY - 2;
IrisBiome irisBiome = underground
? engine.getComplex().getCaveBiomeStream().get(blockX, blockZ)
: engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
if (irisBiome == null && underground) {
irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
}
if (irisBiome == null) {
return getFallbackBiome();
}
@@ -226,7 +233,9 @@ public class CustomBiomeSource extends BiomeSource {
return getFallbackBiome();
}
org.bukkit.block.Biome vanillaBiome = irisBiome.getSkyBiome(noiseRng, blockX, blockY, blockZ);
org.bukkit.block.Biome vanillaBiome = underground
? irisBiome.getGroundBiome(noiseRng, blockX, blockY, blockZ)
: irisBiome.getSkyBiome(noiseRng, blockX, blockY, blockZ);
Holder<Biome> holder = NMSBinding.biomeToBiomeBase(biomeRegistry, vanillaBiome);
if (holder != null) {
return holder;