This commit is contained in:
Brian Neumann-Fopiano
2026-04-22 19:23:50 -04:00
parent 23fad24fb7
commit 3d128b70a7
17 changed files with 1102 additions and 488 deletions
+1 -1
View File
@@ -1 +1 @@
699705819
-1935789196
@@ -27,7 +27,9 @@ import art.arcane.volmlib.util.math.Spiraler;
import lombok.Builder;
import lombok.Data;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@Builder
@Data
@@ -117,10 +119,50 @@ public class PregenTask {
}));
}
@FunctionalInterface
public interface InterleavedChunkConsumer {
boolean on(int regionX, int regionZ, int chunkX, int chunkZ, boolean firstChunkInRegion, boolean lastChunkInRegion);
}
public void iterateAllChunks(Spiraled s) {
iterateRegions(((rX, rZ) -> iterateChunks(rX, rZ, s)));
}
public void iterateAllChunksInterleaved(InterleavedChunkConsumer consumer) {
List<int[]> regions = new ArrayList<>();
iterateRegions((rX, rZ) -> regions.add(new int[]{rX, rZ}));
List<List<int[]>> regionChunks = new ArrayList<>();
for (int[] region : regions) {
List<int[]> chunks = new ArrayList<>();
iterateChunks(region[0], region[1], (cx, cz) -> chunks.add(new int[]{region[0], region[1], cx, cz}));
if (!chunks.isEmpty()) {
regionChunks.add(chunks);
}
}
int[] indices = new int[regionChunks.size()];
boolean anyRemaining = true;
while (anyRemaining) {
anyRemaining = false;
for (int r = 0; r < regionChunks.size(); r++) {
List<int[]> chunks = regionChunks.get(r);
int idx = indices[r];
if (idx >= chunks.size()) {
continue;
}
anyRemaining = true;
int[] entry = chunks.get(idx);
boolean first = idx == 0;
boolean last = idx == chunks.size() - 1;
indices[r]++;
if (!consumer.on(entry[0], entry[1], entry[2], entry[3], first, last)) {
return;
}
}
}
}
private class Bounds {
private Bound chunk = null;
private Bound region = null;
@@ -355,13 +355,13 @@ public class AsyncPregenMethod implements PregeneratorMethod {
static int computePaperLikeRecommendedCap(int workerThreads) {
int normalizedWorkers = Math.max(1, workerThreads);
int recommendedCap = normalizedWorkers * 4;
int recommendedCap = normalizedWorkers * 2;
if (recommendedCap < 8) {
return 8;
}
if (recommendedCap > 128) {
return 128;
if (recommendedCap > 96) {
return 96;
}
return recommendedCap;
@@ -328,7 +328,14 @@ public final class WorldRuntimeControlService {
int[] scanOrder = new int[maxY - minY + 1];
int index = 0;
for (int y = maxY; y >= minY; y--) {
int runtimeSurface = world.getHighestBlockYAt((int) source.getX(), (int) source.getZ());
int startY = Math.min(maxY, runtimeSurface + 1);
for (int y = startY; y >= minY; y--) {
scanOrder[index++] = y;
}
for (int y = startY + 1; y <= maxY; y++) {
scanOrder[index++] = y;
}
@@ -0,0 +1,430 @@
/*
* 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.decorator;
import art.arcane.iris.Iris;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.mantle.EngineMantle;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisDecorationPart;
import art.arcane.iris.engine.object.IrisDecorator;
import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.volmlib.util.math.RNG;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockSupport;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.MultipleFacing;
import org.bukkit.block.data.type.PointedDripstone;
final class DecoratorCore {
private static final long SEED_OFFSET = 29356788L;
private static final long PART_FACTOR = 10439677L;
static final ThreadLocal<PlaceOpts> SCRATCH_OPTS = ThreadLocal.withInitial(PlaceOpts::new);
static final class PlaceOpts {
boolean caveSkipFluid;
boolean underwater;
int fluidHeight;
void reset() {
caveSkipFluid = false;
underwater = false;
fluidHeight = 0;
}
}
static long partSeed(long baseSeed, int partOrdinal) {
return baseSeed + SEED_OFFSET - (partOrdinal * PART_FACTOR);
}
static long partSeed(long baseSeed, IrisDecorationPart part) {
return partSeed(baseSeed, part.ordinal());
}
static IrisDecorator pickDecorator(IrisBiome biome, IrisDecorationPart part, RNG gRNG,
RNG colRng, IrisData data, double realX, double realZ) {
IrisDecorator[] bucket = biome.getDecoratorBucket(part);
if (bucket.length == 0) {
return null;
}
IrisDecorator picked = null;
int count = 0;
for (IrisDecorator d : bucket) {
try {
if (d.passesChanceGate(gRNG, realX, realZ, data)) {
count++;
if (count == 1 || colRng.nextInt(count) == 0) {
picked = d;
}
}
} catch (Throwable e) {
Iris.reportError(e);
}
}
return picked;
}
static void placeSingleUp(IrisDecorator decorator, int x, int z,
int realX, int height, int realZ, Hunk<BlockData> data,
RNG rng, IrisData irisData, boolean caveSkipFluid, EngineMantle mantle) {
BlockData bd = decorator.pickBlockData(rng, irisData, realX, realZ);
if (bd == null) {
return;
}
if (bd instanceof Bisected) {
BlockData top = bd.clone();
((Bisected) top).setHalf(Bisected.Half.TOP);
try {
if (!caveSkipFluid || !B.isFluid(data.get(x, height + 2, z))) {
data.set(x, height + 2, z, top);
}
} catch (Throwable e) {
Iris.reportError(e);
}
bd = bd.clone();
((Bisected) bd).setHalf(Bisected.Half.BOTTOM);
}
if (B.isAir(data.get(x, height + 1, z))) {
data.set(x, height + 1, z, fixFacesForHunk(bd, data, x, z, realX, height + 1, realZ, mantle));
}
}
static void placeSurfaceSingle(IrisDecorator decorator,
int x, int z, int realX, int height, int realZ,
Hunk<BlockData> data, RNG rng, IrisData irisData,
boolean underwater, boolean caveSkipFluid, EngineMantle mantle) {
BlockData bdx = data.get(x, height, z);
BlockData bd = decorator.pickBlockData(rng, irisData, realX, realZ);
if (!underwater && !canGoOn(bd, bdx)
&& !decorator.isForcePlace() && decorator.getForceBlock() == null) {
return;
}
if (decorator.getForceBlock() != null) {
if (caveSkipFluid && B.isFluid(bdx)) {
return;
}
data.set(x, height, z, fixFacesForHunk(
decorator.getForceBlock().getBlockData(irisData), data, x, z, realX, height, realZ, mantle));
return;
}
if (!decorator.isForcePlace()) {
if (decorator.getWhitelist() != null
&& decorator.getWhitelist().stream().noneMatch(d -> d.getBlockData(irisData).equals(bdx))) {
return;
}
if (decorator.getBlacklist() != null
&& decorator.getBlacklist().stream().anyMatch(d -> d.getBlockData(irisData).equals(bdx))) {
return;
}
}
if (bd instanceof Bisected) {
BlockData top = bd.clone();
((Bisected) top).setHalf(Bisected.Half.TOP);
try {
if (!caveSkipFluid || !B.isFluid(data.get(x, height + 2, z))) {
data.set(x, height + 2, z, top);
}
} catch (Throwable e) {
Iris.reportError(e);
}
bd = bd.clone();
((Bisected) bd).setHalf(Bisected.Half.BOTTOM);
}
if (B.isAir(data.get(x, height + 1, z))) {
data.set(x, height + 1, z, fixFacesForHunk(bd, data, x, z, realX, height + 1, realZ, mantle));
}
}
static void placeSingleAt(IrisDecorator decorator, int x, int z,
int realX, int height, int realZ, Hunk<BlockData> data,
RNG rng, IrisData irisData, boolean applyFixFaces, EngineMantle mantle) {
BlockData bd = decorator.pickBlockData(rng, irisData, realX, realZ);
if (bd == null) {
return;
}
if (applyFixFaces) {
bd = fixFacesForHunk(bd, data, x, z, realX, height, realZ, mantle);
}
data.set(x, height, z, bd);
}
static void placeStackUp(IrisDecorator decorator, int x, int z, int realX, int realZ,
int height, int max, Hunk<BlockData> data,
RNG rng, IrisData irisData, PlaceOpts opts) {
int effectiveMax = max;
if (opts.underwater && height < opts.fluidHeight) {
effectiveMax = opts.fluidHeight;
}
int stack = computeStack(decorator, rng, realX, realZ, irisData, effectiveMax);
if (stack == 1) {
if (opts.caveSkipFluid && B.isFluid(data.get(x, height, z))) {
return;
}
data.set(x, height, z, decorator.pickBlockDataTop(rng, irisData, realX, realZ));
return;
}
BlockData bdx = data.get(x, height, z);
for (int i = 0; i < stack; i++) {
int h = height + i;
double threshold = ((double) i) / (stack - 1);
BlockData bd = threshold >= decorator.getTopThreshold()
? decorator.pickBlockDataTop(rng, irisData, realX, realZ)
: decorator.pickBlockData(rng, irisData, realX, realZ);
if (bd == null) {
break;
}
if (i == 0 && !opts.underwater && !canGoOn(bd, bdx)) {
break;
}
if (opts.underwater && height + 1 + i > opts.fluidHeight) {
break;
}
if (opts.caveSkipFluid && B.isFluid(data.get(x, height + 1 + i, z))) {
break;
}
if (bd instanceof PointedDripstone) {
bd = dripstoneBlock(stack, i, BlockFace.UP);
}
data.set(x, height + 1 + i, z, bd);
}
}
static void placeStackDown(IrisDecorator decorator, int x, int z, int realX, int realZ,
int height, int minHeight, Hunk<BlockData> data,
RNG rng, IrisData irisData, int max, PlaceOpts opts, EngineMantle mantle) {
int stack = computeStack(decorator, rng, realX, realZ, irisData, max);
if (stack == 1) {
if (opts.caveSkipFluid && B.isFluid(data.get(x, height, z))) {
return;
}
data.set(x, height, z, fixFacesForHunk(
decorator.pickBlockDataTop(rng, irisData, realX, realZ),
data, x, z, realX, height, realZ, mantle));
return;
}
for (int i = 0; i < stack; i++) {
int h = height - i;
if (h < minHeight) {
continue;
}
double threshold = ((double) i) / (double) (stack - 1);
BlockData bd = threshold >= decorator.getTopThreshold()
? decorator.pickBlockDataTop(rng, irisData, realX, realZ)
: decorator.pickBlockData(rng, irisData, realX, realZ);
if (bd instanceof PointedDripstone) {
bd = dripstoneBlock(stack, i, BlockFace.DOWN);
}
if (opts.caveSkipFluid && B.isFluid(data.get(x, h, z))) {
break;
}
data.set(x, h, z, fixFacesForHunk(bd, data, x, z, realX, h, realZ, mantle));
}
}
static void placeFloatingSimple(IrisDecorator decorator,
int xf, int zf, int realX, int realZ,
int height, int max, Hunk<BlockData> data,
RNG rng, IrisData irisData) {
BlockData bd = decorator.pickBlockData(rng, irisData, realX, realZ);
if (bd == null) {
return;
}
if (bd instanceof Bisected) {
BlockData top = bd.clone();
((Bisected) top).setHalf(Bisected.Half.TOP);
try {
if (max > 2) {
data.set(xf, height + 2, zf, top);
}
} catch (Throwable e) {
Iris.reportError(e);
}
bd = bd.clone();
((Bisected) bd).setHalf(Bisected.Half.BOTTOM);
}
if (max > 1) {
data.set(xf, height + 1, zf, bd);
}
}
static int placeFloatingStacked(IrisDecorator decorator,
int xf, int zf, int realX, int realZ,
int height, int max, Hunk<BlockData> data,
RNG rng, IrisData irisData) {
int stack = decorator.getHeight(rng, realX, realZ, irisData);
if (decorator.isScaleStack()) {
stack = Math.min((int) Math.ceil((double) max * ((double) stack / 100)), decorator.getAbsoluteMaxStack());
} else {
stack = Math.min(max, stack);
}
int placed = 0;
for (int i = 0; i < stack; i++) {
int h = height + 1 + i;
if (h >= height + max) {
break;
}
double threshold = stack == 1 ? 0.0 : ((double) i) / (stack - 1);
BlockData bd = threshold >= decorator.getTopThreshold()
? decorator.pickBlockDataTop(rng, irisData, realX, realZ)
: decorator.pickBlockData(rng, irisData, realX, realZ);
if (bd == null) {
break;
}
data.set(xf, h, zf, bd);
placed++;
}
return placed;
}
static BlockData fixFacesForHunk(BlockData b, Hunk<BlockData> hunk, int rX, int rZ,
int x, int y, int z, EngineMantle mantle) {
if (!B.isVineBlock(b)) {
return b;
}
MultipleFacing data = (MultipleFacing) b.clone();
data.getFaces().forEach(f -> data.setFace(f, false));
boolean found = false;
for (BlockFace f : BlockFace.values()) {
if (!f.isCartesian()) {
continue;
}
int yy = y + f.getModY();
BlockData r = null;
if (mantle != null) {
r = mantle.getMantle().get(x + f.getModX(), yy, z + f.getModZ(), BlockData.class);
}
if (r == null) {
r = EngineMantle.AIR;
}
if (r.isFaceSturdy(f.getOppositeFace(), BlockSupport.FULL)) {
found = true;
data.setFace(f, true);
continue;
}
int xx = rX + f.getModX();
int zz = rZ + f.getModZ();
if (xx < 0 || xx > 15 || zz < 0 || zz > 15 || yy < 0 || yy > hunk.getHeight()) {
continue;
}
r = hunk.get(xx, yy, zz);
if (r.isFaceSturdy(f.getOppositeFace(), BlockSupport.FULL)) {
found = true;
data.setFace(f, true);
}
}
if (!found) {
data.setFace(BlockFace.DOWN, true);
}
return data;
}
static boolean canGoOn(BlockData decorator, BlockData surface) {
return surface.isFaceSturdy(BlockFace.UP, BlockSupport.FULL);
}
private static int computeStack(IrisDecorator decorator, RNG rng, double realX, double realZ,
IrisData irisData, int max) {
int stack = decorator.getHeight(rng, realX, realZ, irisData);
if (decorator.isScaleStack()) {
stack = Math.min((int) Math.ceil((double) max * ((double) stack / 100)), decorator.getAbsoluteMaxStack());
} else {
stack = Math.min(max, stack);
}
return stack;
}
// Lazily populated on first dripstone decoration — avoids Bukkit API at class-load time.
// Index: 0=TIP, 1=FRUSTUM, 2=BASE. Race on init is benign (only allocation cost, not correctness).
private static volatile BlockData[] dripstoneUp;
private static volatile BlockData[] dripstoneDown;
private static BlockData[] buildDripstoneArr(BlockFace direction) {
PointedDripstone.Thickness[] order = {
PointedDripstone.Thickness.TIP,
PointedDripstone.Thickness.FRUSTUM,
PointedDripstone.Thickness.BASE
};
BlockData[] arr = new BlockData[3];
for (int k = 0; k < 3; k++) {
BlockData bd = Material.POINTED_DRIPSTONE.createBlockData();
((PointedDripstone) bd).setThickness(order[k]);
((PointedDripstone) bd).setVerticalDirection(direction);
arr[k] = bd;
}
return arr;
}
private static BlockData dripstoneBlock(int stack, int i, BlockFace direction) {
int thIdx;
if (i == stack - 1) {
thIdx = 0;
} else if (i == stack - 2) {
thIdx = 1;
} else {
thIdx = 2;
}
if (direction == BlockFace.UP) {
if (dripstoneUp == null) {
dripstoneUp = buildDripstoneArr(BlockFace.UP);
}
return dripstoneUp[thIdx];
}
if (dripstoneDown == null) {
dripstoneDown = buildDripstoneArr(BlockFace.DOWN);
}
return dripstoneDown[thIdx];
}
}
@@ -18,117 +18,38 @@
package art.arcane.iris.engine.decorator;
import art.arcane.iris.Iris;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisDecorationPart;
import art.arcane.iris.engine.object.IrisDecorator;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.math.RNG;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.BlockData;
import java.util.concurrent.atomic.AtomicLong;
/*
* Floating island decoration path. Bypasses all canGoOn, slope, whitelist, and blacklist
* gating from IrisSurfaceDecorator — the island top IS the biome's designated surface by
* construction, so those material-compatibility checks are never meaningful here.
*/
public class FloatingDecorator {
public static final AtomicLong decCandidatesNull = new AtomicLong();
private static final long SEED_SALT = 29356788L;
private static final long PART_SALT = 10439677L;
public static int decorateColumn(Engine engine, IrisBiome target, IrisDecorationPart part,
int xf, int zf, int realX, int realZ,
int height, int max, Hunk<BlockData> data, RNG rng) {
long gSeed = engine.getSeedManager().getDecorator() + SEED_SALT - (part.ordinal() * PART_SALT);
RNG gRNG = new RNG(gSeed);
KList<IrisDecorator> candidates = new KList<>();
int height, int max, Hunk<BlockData> data, RNG rng,
Runnable candidatesNullCallback) {
RNG gRNG = new RNG(DecoratorCore.partSeed(engine.getSeedManager().getDecorator(), part));
IrisDecorator decorator = DecoratorCore.pickDecorator(target, part, gRNG, rng, engine.getData(), realX, realZ);
for (IrisDecorator d : target.getDecorators()) {
try {
if (d.getPartOf().equals(part) && d.getBlockData(target, gRNG, realX, realZ, engine.getData()) != null) {
candidates.add(d);
}
} catch (Throwable e) {
Iris.reportError(e);
}
}
if (candidates.isEmpty()) {
decCandidatesNull.incrementAndGet();
if (decorator == null) {
candidatesNullCallback.run();
return 0;
}
IrisDecorator decorator = candidates.get(rng.nextInt(candidates.size()));
if (!decorator.isStacking()) {
return placeSimple(decorator, target, xf, zf, realX, realZ, height, max, data, rng, engine);
} else {
return placeStacked(decorator, target, xf, zf, realX, realZ, height, max, data, rng, engine);
}
}
private static int placeSimple(IrisDecorator decorator, IrisBiome target,
int xf, int zf, int realX, int realZ,
int height, int max, Hunk<BlockData> data, RNG rng, Engine engine) {
BlockData bd = decorator.getBlockData100(target, rng, realX, height, realZ, engine.getData());
if (bd == null) {
return 0;
DecoratorCore.placeFloatingSimple(decorator, xf, zf, realX, realZ, height, max, data, rng, engine.getData());
return max > 1 ? 1 : 0;
}
if (bd instanceof Bisected) {
BlockData top = bd.clone();
((Bisected) top).setHalf(Bisected.Half.TOP);
try {
if (max > 2) {
data.set(xf, height + 2, zf, top);
}
} catch (Throwable e) {
Iris.reportError(e);
}
bd = bd.clone();
((Bisected) bd).setHalf(Bisected.Half.BOTTOM);
}
if (max > 1) {
data.set(xf, height + 1, zf, bd);
return 1;
}
return 0;
}
private static int placeStacked(IrisDecorator decorator, IrisBiome target,
int xf, int zf, int realX, int realZ,
int height, int max, Hunk<BlockData> data, RNG rng, Engine engine) {
int stack = decorator.getHeight(rng, realX, realZ, engine.getData());
if (decorator.isScaleStack()) {
stack = Math.min((int) Math.ceil((double) max * ((double) stack / 100)), decorator.getAbsoluteMaxStack());
} else {
stack = Math.min(max, stack);
}
int placed = 0;
for (int i = 0; i < stack; i++) {
int h = height + 1 + i;
if (h >= height + max) {
break;
}
double threshold = stack == 1 ? 0.0 : ((double) i) / (stack - 1);
BlockData bd = threshold >= decorator.getTopThreshold()
? decorator.getBlockDataForTop(target, rng, realX, height + i, realZ, engine.getData())
: decorator.getBlockData100(target, rng, realX, height + i, realZ, engine.getData());
if (bd == null) {
break;
}
data.set(xf, h, zf, bd);
placed++;
}
return placed;
return DecoratorCore.placeFloatingStacked(decorator, xf, zf, realX, realZ, height, max, data, rng, engine.getData());
}
}
@@ -24,89 +24,42 @@ import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisDecorationPart;
import art.arcane.iris.engine.object.IrisDecorator;
import art.arcane.iris.util.common.data.B;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
import art.arcane.volmlib.util.math.RNG;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.PointedDripstone;
public class IrisCeilingDecorator extends IrisEngineDecorator {
private final RNG partRNG;
public IrisCeilingDecorator(Engine engine) {
super(engine, "Ceiling", IrisDecorationPart.CEILING);
this.partRNG = new RNG(DecoratorCore.partSeed(getSeed(), IrisDecorationPart.CEILING));
}
@BlockCoordinates
@Override
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1, Hunk<BlockData> data, IrisBiome biome, int height, int max) {
RNG rng = getRNG(realX, realZ);
IrisDecorator decorator = getDecorator(rng, biome, realX, realZ);
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1,
Hunk<BlockData> data, IrisBiome biome, int height, int max) {
boolean caveSkipFluid = biome.getInferredType() == InferredType.CAVE;
RNG rng = getRNG(realX, realZ);
IrisDecorator decorator = DecoratorCore.pickDecorator(biome, getPart(), partRNG, rng, getData(), realX, realZ);
if (decorator != null) {
if (!decorator.isStacking()) {
if (caveSkipFluid && B.isFluid(data.get(x, height, z))) {
return;
}
data.set(x, height, z, fixFaces(decorator.getBlockData100(biome, rng, realX, height, realZ, getData()), data, x, z, realX, height, realZ));
} else {
int stack = decorator.getHeight(rng, realX, realZ, getData());
if (decorator.isScaleStack()) {
stack = Math.min((int) Math.ceil((double) max * ((double) stack / 100)), decorator.getAbsoluteMaxStack());
} else {
stack = Math.min(max, stack);
}
if (stack == 1) {
if (caveSkipFluid && B.isFluid(data.get(x, height, z))) {
return;
}
data.set(x, height, z, decorator.getBlockDataForTop(biome, rng, realX, height, realZ, getData()));
return;
}
for (int i = 0; i < stack; i++) {
int h = height - i;
if (h < getEngine().getMinHeight()) {
continue;
}
double threshold = (((double) i) / (double) (stack - 1));
BlockData bd = threshold >= decorator.getTopThreshold() ?
decorator.getBlockDataForTop(biome, rng, realX, h, realZ, getData()) :
decorator.getBlockData100(biome, rng, realX, h, realZ, getData());
if (bd instanceof PointedDripstone) {
PointedDripstone.Thickness th = PointedDripstone.Thickness.BASE;
if (stack == 2) {
th = PointedDripstone.Thickness.FRUSTUM;
if (i == stack - 1) {
th = PointedDripstone.Thickness.TIP;
}
} else {
if (i == stack - 1) {
th = PointedDripstone.Thickness.TIP;
} else if (i == stack - 2) {
th = PointedDripstone.Thickness.FRUSTUM;
}
}
bd = Material.POINTED_DRIPSTONE.createBlockData();
((PointedDripstone) bd).setThickness(th);
((PointedDripstone) bd).setVerticalDirection(BlockFace.DOWN);
}
if (caveSkipFluid && B.isFluid(data.get(x, h, z))) {
break;
}
data.set(x, h, z, bd);
}
}
if (decorator == null) {
return;
}
if (!decorator.isStacking()) {
if (caveSkipFluid && B.isFluid(data.get(x, height, z))) {
return;
}
DecoratorCore.placeSingleAt(decorator, x, z, realX, height, realZ, data, rng, getData(), true, getEngine().getMantle());
return;
}
DecoratorCore.PlaceOpts opts = DecoratorCore.SCRATCH_OPTS.get();
opts.reset();
opts.caveSkipFluid = caveSkipFluid;
DecoratorCore.placeStackDown(decorator, x, z, realX, realZ, height, getEngine().getMinHeight(), data, rng, getData(), max, opts, getEngine().getMantle());
}
}
@@ -18,102 +18,28 @@
package art.arcane.iris.engine.decorator;
import art.arcane.iris.Iris;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.EngineAssignedComponent;
import art.arcane.iris.engine.framework.EngineDecorator;
import art.arcane.iris.engine.mantle.EngineMantle;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisDecorationPart;
import art.arcane.iris.engine.object.IrisDecorator;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
import art.arcane.volmlib.util.math.RNG;
import lombok.Getter;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockSupport;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.MultipleFacing;
public abstract class IrisEngineDecorator extends EngineAssignedComponent implements EngineDecorator {
@Getter
private final IrisDecorationPart part;
private final long seed;
private final long modX, modZ;
public IrisEngineDecorator(Engine engine, String name, IrisDecorationPart part) {
super(engine, name + " Decorator");
this.part = part;
this.seed = getSeed() + 29356788 - (part.ordinal() * 10439677L);
this.modX = 29356788 ^ (part.ordinal() + 6);
this.modZ = 10439677 ^ (part.ordinal() + 1);
}
@BlockCoordinates
protected RNG getRNG(int x, int z) {
long seed = DecoratorCore.partSeed(getSeed(), part);
long modX = 29356788L ^ (part.ordinal() + 6);
long modZ = 10439677L ^ (part.ordinal() + 1);
return new RNG(x * modX + z * modZ + seed);
}
protected IrisDecorator getDecorator(RNG rng, IrisBiome biome, double realX, double realZ) {
KList<IrisDecorator> v = new KList<>();
RNG gRNG = new RNG(seed);
for (IrisDecorator i : biome.getDecorators()) {
try {
if (i.getPartOf().equals(part) && i.getBlockData(biome, gRNG, realX, realZ, getData()) != null) {
v.add(i);
}
} catch (Throwable e) {
Iris.reportError(e);
Iris.error("PART OF: " + biome.getLoadFile().getAbsolutePath() + " HAS AN INVALID DECORATOR near 'partOf'!!!");
}
}
if (v.isNotEmpty()) {
return v.get(rng.nextInt(v.size()));
}
return null;
}
protected BlockData fixFaces(BlockData b, Hunk<BlockData> hunk, int rX, int rZ, int x, int y, int z) {
if (B.isVineBlock(b)) {
MultipleFacing data = (MultipleFacing) b.clone();
data.getFaces().forEach(f -> data.setFace(f, false));
boolean found = false;
for (BlockFace f : BlockFace.values()) {
if (!f.isCartesian())
continue;
int yy = y + f.getModY();
BlockData r = getEngine().getMantle().getMantle().get(x + f.getModX(), yy, z + f.getModZ(), BlockData.class);
if (r == null) {
r = EngineMantle.AIR;
}
if (r.isFaceSturdy(f.getOppositeFace(), BlockSupport.FULL)) {
found = true;
data.setFace(f, true);
continue;
}
int xx = rX + f.getModX();
int zz = rZ + f.getModZ();
if (xx < 0 || xx > 15 || zz < 0 || zz > 15 || yy < 0 || yy > hunk.getHeight())
continue;
r = hunk.get(xx, yy, zz);
if (r.isFaceSturdy(f.getOppositeFace(), BlockSupport.FULL)) {
found = true;
data.setFace(f, true);
}
}
if (!found)
data.setFace(BlockFace.DOWN, true);
return data;
}
return b;
}
}
@@ -22,56 +22,63 @@ import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisDecorationPart;
import art.arcane.iris.engine.object.IrisDecorator;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
import art.arcane.volmlib.util.math.RNG;
import org.bukkit.block.data.BlockData;
public class IrisSeaFloorDecorator extends IrisEngineDecorator {
private final RNG partRNG;
public IrisSeaFloorDecorator(Engine engine) {
super(engine, "Sea Floor", IrisDecorationPart.SEA_FLOOR);
this.partRNG = new RNG(DecoratorCore.partSeed(getSeed(), IrisDecorationPart.SEA_FLOOR));
}
@BlockCoordinates
@Override
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1, Hunk<BlockData> data, IrisBiome biome, int height, int max) {
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1,
Hunk<BlockData> data, IrisBiome biome, int height, int max) {
RNG rng = getRNG(realX, realZ);
IrisDecorator decorator = getDecorator(rng, biome, realX, realZ);
IrisDecorator decorator = DecoratorCore.pickDecorator(biome, getPart(), partRNG, rng, getData(), realX, realZ);
if (decorator != null) {
if (!decorator.isStacking()) {
if (!decorator.isForcePlace() && !decorator.getSlopeCondition().isDefault()
&& !decorator.getSlopeCondition().isValid(getComplex().getSlopeStream().get(realX, realZ))) {
return;
}
if (height >= 0 || height < getEngine().getHeight()) {
data.set(x, height, z, decorator.getBlockData100(biome, rng, realX, height, realZ, getData()));
}
} else {
int stack = decorator.getHeight(rng, realX, realZ, getData());
if (decorator.isScaleStack()) {
int maxStack = max - height;
stack = (int) Math.ceil((double) maxStack * ((double) stack / 100));
} else stack = Math.min(stack, max - height);
if (stack == 1) {
data.set(x, height, z, decorator.getBlockDataForTop(biome, rng, realX, height, realZ, getData()));
return;
}
for (int i = 0; i < stack; i++) {
int h = height + i;
if (h > max || h > getEngine().getHeight()) {
continue;
}
double threshold = ((double) i) / (stack - 1);
data.set(x, h, z, threshold >= decorator.getTopThreshold() ?
decorator.getBlockDataForTop(biome, rng, realX, h, realZ, getData()) :
decorator.getBlockData100(biome, rng, realX, h, realZ, getData()));
}
}
if (decorator == null) {
return;
}
if (!decorator.isStacking()) {
if (!decorator.isForcePlace() && !decorator.getSlopeCondition().isDefault()
&& !decorator.getSlopeCondition().isValid(getComplex().getSlopeStream().get(realX, realZ))) {
return;
}
if (height >= 0 || height < getEngine().getHeight()) {
data.set(x, height, z, decorator.getBlockData100(biome, rng, realX, height, realZ, getData()));
}
return;
}
int stack = decorator.getHeight(rng, realX, realZ, getData());
if (decorator.isScaleStack()) {
stack = (int) Math.ceil((double) (max - height) * ((double) stack / 100));
} else {
stack = Math.min(stack, max - height);
}
if (stack == 1) {
data.set(x, height, z, decorator.getBlockDataForTop(biome, rng, realX, height, realZ, getData()));
return;
}
int engineHeight = getEngine().getHeight();
for (int i = 0; i < stack; i++) {
int h = height + i;
if (h > max || h > engineHeight) {
continue;
}
double threshold = ((double) i) / (stack - 1);
data.set(x, h, z, threshold >= decorator.getTopThreshold()
? decorator.getBlockDataForTop(biome, rng, realX, h, realZ, getData())
: decorator.getBlockData100(biome, rng, realX, h, realZ, getData()));
}
}
}
@@ -22,51 +22,57 @@ import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisDecorationPart;
import art.arcane.iris.engine.object.IrisDecorator;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
import art.arcane.volmlib.util.math.RNG;
import org.bukkit.block.data.BlockData;
public class IrisSeaSurfaceDecorator extends IrisEngineDecorator {
private final RNG partRNG;
public IrisSeaSurfaceDecorator(Engine engine) {
super(engine, "Sea Surface", IrisDecorationPart.SEA_SURFACE);
this.partRNG = new RNG(DecoratorCore.partSeed(getSeed(), IrisDecorationPart.SEA_SURFACE));
}
@BlockCoordinates
@Override
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1, Hunk<BlockData> data, IrisBiome biome, int height, int max) {
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1,
Hunk<BlockData> data, IrisBiome biome, int height, int max) {
RNG rng = getRNG(realX, realZ);
IrisDecorator decorator = getDecorator(rng, biome, realX, realZ);
IrisDecorator decorator = DecoratorCore.pickDecorator(biome, getPart(), partRNG, rng, getData(), realX, realZ);
if (decorator != null) {
if (!decorator.isStacking()) {
if (height >= 0 || height < getEngine().getHeight()) {
data.set(x, height + 1, z, decorator.getBlockData100(biome, rng, realX, height, realZ, getData()));
}
} else {
int stack = decorator.getHeight(rng, realX, realZ, getData());
if (decorator.isScaleStack()) {
int maxStack = max - height;
stack = (int) Math.ceil((double) maxStack * ((double) stack / 100));
}
if (decorator == null) {
return;
}
if (stack == 1) {
data.set(x, height, z, decorator.getBlockDataForTop(biome, rng, realX, height, realZ, getData()));
return;
}
for (int i = 0; i < stack; i++) {
int h = height + i;
if (h >= max || h >= getEngine().getHeight()) {
continue;
}
double threshold = ((double) i) / (stack - 1);
data.set(x, h + 1, z, threshold >= decorator.getTopThreshold() ?
decorator.getBlockDataForTop(biome, rng, realX, h, realZ, getData()) :
decorator.getBlockData100(biome, rng, realX, h, realZ, getData()));
}
if (!decorator.isStacking()) {
if (height >= 0 || height < getEngine().getHeight()) {
data.set(x, height + 1, z, decorator.getBlockData100(biome, rng, realX, height, realZ, getData()));
}
return;
}
int stack = decorator.getHeight(rng, realX, realZ, getData());
if (decorator.isScaleStack()) {
stack = (int) Math.ceil((double) (max - height) * ((double) stack / 100));
}
if (stack == 1) {
data.set(x, height, z, decorator.getBlockDataForTop(biome, rng, realX, height, realZ, getData()));
return;
}
int engineHeight = getEngine().getHeight();
for (int i = 0; i < stack; i++) {
int h = height + i;
if (h >= max || h >= engineHeight) {
continue;
}
double threshold = ((double) i) / (stack - 1);
data.set(x, h + 1, z, threshold >= decorator.getTopThreshold()
? decorator.getBlockDataForTop(biome, rng, realX, h, realZ, getData())
: decorator.getBlockData100(biome, rng, realX, h, realZ, getData()));
}
}
}
@@ -22,59 +22,72 @@ import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisDecorationPart;
import art.arcane.iris.engine.object.IrisDecorator;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.iris.util.project.stream.ProceduralStream;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
import art.arcane.volmlib.util.math.RNG;
import org.bukkit.block.data.BlockData;
public class IrisShoreLineDecorator extends IrisEngineDecorator {
private final RNG partRNG;
public IrisShoreLineDecorator(Engine engine) {
super(engine, "Shore Line", IrisDecorationPart.SHORE_LINE);
this.partRNG = new RNG(DecoratorCore.partSeed(getSeed(), IrisDecorationPart.SHORE_LINE));
}
@BlockCoordinates
@Override
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1, Hunk<BlockData> data, IrisBiome biome, int height, int max) {
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1,
Hunk<BlockData> data, IrisBiome biome, int height, int max) {
if (height != getDimension().getFluidHeight()) {
return;
}
if (height == getDimension().getFluidHeight()) {
if (Math.round(getComplex().getHeightStream().get(realX1, realZ)) < getComplex().getFluidHeight() ||
Math.round(getComplex().getHeightStream().get(realX_1, realZ)) < getComplex().getFluidHeight() ||
Math.round(getComplex().getHeightStream().get(realX, realZ1)) < getComplex().getFluidHeight() ||
Math.round(getComplex().getHeightStream().get(realX, realZ_1)) < getComplex().getFluidHeight()
) {
RNG rng = getRNG(realX, realZ);
IrisDecorator decorator = getDecorator(rng, biome, realX, realZ);
double complexFluidHeight = getComplex().getFluidHeight();
ProceduralStream<Double> heightStream = getComplex().getHeightStream();
if (Math.round(heightStream.get(realX1, realZ)) >= complexFluidHeight
&& Math.round(heightStream.get(realX_1, realZ)) >= complexFluidHeight
&& Math.round(heightStream.get(realX, realZ1)) >= complexFluidHeight
&& Math.round(heightStream.get(realX, realZ_1)) >= complexFluidHeight) {
return;
}
if (decorator != null) {
if (!decorator.isForcePlace() && !decorator.getSlopeCondition().isDefault()
&& !decorator.getSlopeCondition().isValid(getComplex().getSlopeStream().get(realX, realZ))) {
return;
}
RNG rng = getRNG(realX, realZ);
IrisDecorator decorator = DecoratorCore.pickDecorator(biome, getPart(), partRNG, rng, getData(), realX, realZ);
if (!decorator.isStacking()) {
data.set(x, height + 1, z, decorator.getBlockData100(biome, rng, realX, height, realZ, getData()));
} else {
int stack = decorator.getHeight(rng, realX, realZ, getData());
if (decorator.isScaleStack()) {
int maxStack = max - height;
stack = (int) Math.ceil((double) maxStack * ((double) stack / 100));
} else stack = Math.min(max - height, stack);
if (decorator == null) {
return;
}
if (stack == 1) {
data.set(x, height, z, decorator.getBlockDataForTop(biome, rng, realX, height, realZ, getData()));
return;
}
if (!decorator.isForcePlace() && !decorator.getSlopeCondition().isDefault()
&& !decorator.getSlopeCondition().isValid(getComplex().getSlopeStream().get(realX, realZ))) {
return;
}
for (int i = 0; i < stack; i++) {
int h = height + i;
double threshold = ((double) i) / (stack - 1);
data.set(x, h + 1, z, threshold >= decorator.getTopThreshold() ?
decorator.getBlockDataForTop(biome, rng, realX, h, realZ, getData()) :
decorator.getBlockData100(biome, rng, realX, h, realZ, getData()));
}
}
}
}
if (!decorator.isStacking()) {
data.set(x, height + 1, z, decorator.getBlockData100(biome, rng, realX, height, realZ, getData()));
return;
}
int stack = decorator.getHeight(rng, realX, realZ, getData());
if (decorator.isScaleStack()) {
stack = (int) Math.ceil((double) (max - height) * ((double) stack / 100));
} else {
stack = Math.min(max - height, stack);
}
if (stack == 1) {
data.set(x, height, z, decorator.getBlockDataForTop(biome, rng, realX, height, realZ, getData()));
return;
}
for (int i = 0; i < stack; i++) {
int h = height + i;
double threshold = ((double) i) / (stack - 1);
data.set(x, h + 1, z, threshold >= decorator.getTopThreshold()
? decorator.getBlockDataForTop(biome, rng, realX, h, realZ, getData())
: decorator.getBlockData100(biome, rng, realX, h, realZ, getData()));
}
}
}
@@ -18,29 +18,27 @@
package art.arcane.iris.engine.decorator;
import art.arcane.iris.Iris;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.InferredType;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisDecorationPart;
import art.arcane.iris.engine.object.IrisDecorator;
import art.arcane.iris.util.common.data.B;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.volmlib.util.documentation.BlockCoordinates;
import art.arcane.volmlib.util.math.RNG;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.PointedDripstone;
public class IrisSurfaceDecorator extends IrisEngineDecorator {
private final RNG partRNG;
public IrisSurfaceDecorator(Engine engine) {
super(engine, "Surface", IrisDecorationPart.NONE);
this.partRNG = new RNG(DecoratorCore.partSeed(getSeed(), IrisDecorationPart.NONE));
}
protected IrisSurfaceDecorator(Engine engine, String name) {
super(engine, name, IrisDecorationPart.NONE);
this.partRNG = new RNG(DecoratorCore.partSeed(getSeed(), IrisDecorationPart.NONE));
}
protected boolean isSlopeValid(IrisDecorator decorator, int realX, int realZ) {
@@ -52,133 +50,33 @@ public class IrisSurfaceDecorator extends IrisEngineDecorator {
@BlockCoordinates
@Override
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1, Hunk<BlockData> data, IrisBiome biome, int height, int max) {
if (biome.getInferredType().equals(InferredType.SHORE) && height < getDimension().getFluidHeight()) {
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1,
Hunk<BlockData> data, IrisBiome biome, int height, int max) {
int fluidHeight = getDimension().getFluidHeight();
if (biome.getInferredType().equals(InferredType.SHORE) && height < fluidHeight) {
return;
}
BlockData bd, bdx;
RNG rng = getRNG(realX, realZ);
IrisDecorator decorator = getDecorator(rng, biome, realX, realZ);
bdx = data.get(x, height, z);
boolean underwater = height < getDimension().getFluidHeight() && biome.getInferredType() != InferredType.CAVE;
boolean underwater = height < fluidHeight && biome.getInferredType() != InferredType.CAVE;
boolean caveSkipFluid = biome.getInferredType() == InferredType.CAVE;
RNG rng = getRNG(realX, realZ);
IrisDecorator decorator = DecoratorCore.pickDecorator(biome, getPart(), partRNG, rng, getData(), realX, realZ);
if (decorator != null) {
if (!isSlopeValid(decorator, realX, realZ)) {
return;
}
if (!decorator.isStacking()) {
bd = decorator.getBlockData100(biome, rng, realX, height, realZ, getData());
if (!underwater) {
if (!canGoOn(bd, bdx) && (!decorator.isForcePlace() && decorator.getForceBlock() == null)) {
return;
}
}
if (decorator.getForceBlock() != null) {
if (caveSkipFluid && B.isFluid(data.get(x, height, z))) {
return;
}
data.set(x, height, z, fixFaces(decorator.getForceBlock().getBlockData(getData()), data, x, z, realX, height, realZ));
} else if (!decorator.isForcePlace()) {
if (decorator.getWhitelist() != null && decorator.getWhitelist().stream().noneMatch(d -> d.getBlockData(getData()).equals(bdx))) {
return;
}
if (decorator.getBlacklist() != null && decorator.getBlacklist().stream().anyMatch(d -> d.getBlockData(getData()).equals(bdx))) {
return;
}
}
if (bd instanceof Bisected) {
bd = bd.clone();
((Bisected) bd).setHalf(Bisected.Half.TOP);
try {
if (!caveSkipFluid || !B.isFluid(data.get(x, height + 2, z))) {
data.set(x, height + 2, z, bd);
}
} catch (Throwable e) {
Iris.reportError(e);
}
bd = bd.clone();
((Bisected) bd).setHalf(Bisected.Half.BOTTOM);
}
if (B.isAir(data.get(x, height + 1, z))) {
data.set(x, height + 1, z, fixFaces(bd, data, x, z, realX, height + 1, realZ));
}
} else {
if (height < getDimension().getFluidHeight()) {
max = getDimension().getFluidHeight();
}
int stack = decorator.getHeight(rng, realX, realZ, getData());
if (decorator.isScaleStack()) {
stack = Math.min((int) Math.ceil((double) max * ((double) stack / 100)), decorator.getAbsoluteMaxStack());
} else {
stack = Math.min(max, stack);
}
if (stack == 1) {
if (caveSkipFluid && B.isFluid(data.get(x, height, z))) {
return;
}
data.set(x, height, z, decorator.getBlockDataForTop(biome, rng, realX, height, realZ, getData()));
return;
}
for (int i = 0; i < stack; i++) {
int h = height + i;
double threshold = ((double) i) / (stack - 1);
bd = threshold >= decorator.getTopThreshold() ?
decorator.getBlockDataForTop(biome, rng, realX, h, realZ, getData()) :
decorator.getBlockData100(biome, rng, realX, h, realZ, getData());
if (bd == null) {
break;
}
if (i == 0 && !underwater && !canGoOn(bd, bdx)) {
break;
}
if (underwater && height + 1 + i > getDimension().getFluidHeight()) {
break;
}
if (caveSkipFluid && B.isFluid(data.get(x, height + 1 + i, z))) {
break;
}
if (bd instanceof PointedDripstone) {
PointedDripstone.Thickness th = PointedDripstone.Thickness.BASE;
if (stack == 2) {
th = PointedDripstone.Thickness.FRUSTUM;
if (i == stack - 1) {
th = PointedDripstone.Thickness.TIP;
}
} else {
if (i == stack - 1) {
th = PointedDripstone.Thickness.TIP;
} else if (i == stack - 2) {
th = PointedDripstone.Thickness.FRUSTUM;
}
}
bd = Material.POINTED_DRIPSTONE.createBlockData();
((PointedDripstone) bd).setThickness(th);
((PointedDripstone) bd).setVerticalDirection(BlockFace.UP);
}
data.set(x, height + 1 + i, z, bd);
}
}
if (decorator == null || !isSlopeValid(decorator, realX, realZ)) {
return;
}
if (decorator.isStacking()) {
DecoratorCore.PlaceOpts opts = DecoratorCore.SCRATCH_OPTS.get();
opts.reset();
opts.underwater = underwater;
opts.fluidHeight = fluidHeight;
opts.caveSkipFluid = caveSkipFluid;
DecoratorCore.placeStackUp(decorator, x, z, realX, realZ, height, max, data, rng, getData(), opts);
return;
}
DecoratorCore.placeSurfaceSingle(decorator, x, z, realX, height, realZ,
data, rng, getData(), underwater, caveSkipFluid, getEngine().getMantle());
}
}
@@ -59,8 +59,10 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
private static final AtomicLong decorateNoChange = new AtomicLong();
private static final AtomicLong decorateFloorNull = new AtomicLong();
private static final java.util.concurrent.ConcurrentHashMap<String, AtomicLong> floorMatHisto = new java.util.concurrent.ConcurrentHashMap<>();
private static final AtomicLong decCandidatesNull = new AtomicLong();
private static final AtomicLong lastReportMs = new AtomicLong(0L);
private static final AtomicLong reportCycle = new AtomicLong(0L);
private static final Runnable INC_DEC_CANDIDATES_NULL = () -> decCandidatesNull.incrementAndGet();
private final RNG rng;
private final EngineDecorator seaSurfaceDecorator;
@@ -84,7 +86,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
+ " decPlaced=" + decoratePlaced.get()
+ " decNoChange=" + decorateNoChange.get()
+ " decFloorNull=" + decorateFloorNull.get()
+ " decCandidatesNull=" + FloatingDecorator.decCandidatesNull.get()
+ " decCandidatesNull=" + decCandidatesNull.get()
+ " decSkipNonAir=" + decorateSkippedNotAir.get()
+ " decSkipNoInherit=" + decorateSkippedNoInherit.get()
+ " decPhaseCols=" + decoratePhaseColumns.get()
@@ -126,7 +128,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
decorateNoChange.set(0);
decorateFloorNull.set(0);
floorMatHisto.clear();
FloatingDecorator.decCandidatesNull.set(0);
decCandidatesNull.set(0);
MantleFloatingObjectComponent.resetObjectCounters();
}
@@ -285,7 +287,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
}
try {
RNG colRng = rng.nextParallelRNG((int) FloatingIslandSample.columnSeed(baseSeed, wx, wz));
int placed = FloatingDecorator.decorateColumn(getEngine(), target, IrisDecorationPart.NONE, xf, zf, wx, wz, topY, max, output, colRng);
int placed = FloatingDecorator.decorateColumn(getEngine(), target, IrisDecorationPart.NONE, xf, zf, wx, wz, topY, max, output, colRng, INC_DEC_CANDIDATES_NULL);
if (placed > 0) {
decoratePlaced.addAndGet(placed);
} else {
@@ -48,6 +48,7 @@ import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import java.awt.*;
import java.util.EnumMap;
@Accessors(chain = true)
@NoArgsConstructor
@@ -77,6 +78,8 @@ public class IrisBiome extends IrisRegistrant implements IRare {
private final transient AtomicCache<KList<CNG>> layerSeaHeightGenerators = new AtomicCache<>();
private final transient AtomicCache<KList<IrisOreGenerator>> surfaceOreCache = new AtomicCache<>();
private final transient AtomicCache<KList<IrisOreGenerator>> undergroundOreCache = new AtomicCache<>();
private final transient AtomicCache<EnumMap<IrisDecorationPart, IrisDecorator[]>> decoratorBuckets = new AtomicCache<>();
private static final IrisDecorator[] EMPTY_BUCKET = new IrisDecorator[0];
@MinNumber(2)
@Required
@Desc("This is the human readable name for this biome. This can and should be different than the file name. This is not used for loading biomes in other objects.")
@@ -791,7 +794,23 @@ public class IrisBiome extends IrisRegistrant implements IRare {
return "biomes";
}
@Override
public IrisDecorator[] getDecoratorBucket(IrisDecorationPart part) {
return decoratorBuckets.aquire(this::buildDecoratorBuckets).getOrDefault(part, EMPTY_BUCKET);
}
private EnumMap<IrisDecorationPart, IrisDecorator[]> buildDecoratorBuckets() {
EnumMap<IrisDecorationPart, KList<IrisDecorator>> staging = new EnumMap<>(IrisDecorationPart.class);
for (IrisDecorator d : decorators) {
staging.computeIfAbsent(d.getPartOf(), k -> new KList<>()).add(d);
}
EnumMap<IrisDecorationPart, IrisDecorator[]> result = new EnumMap<>(IrisDecorationPart.class);
for (IrisDecorationPart part : IrisDecorationPart.values()) {
KList<IrisDecorator> list = staging.get(part);
result.put(part, list == null ? EMPTY_BUCKET : list.toArray(EMPTY_BUCKET));
}
return result;
}
public String getTypeName() {
return "Biome";
}
@@ -43,6 +43,8 @@ public class IrisDecorator {
private final transient AtomicCache<CNG> heightGenerator = new AtomicCache<>();
private final transient AtomicCache<KList<BlockData>> blockData = new AtomicCache<>();
private final transient AtomicCache<KList<BlockData>> blockDataTops = new AtomicCache<>();
private final transient AtomicCache<BlockData[]> blockDataArray = new AtomicCache<>();
private final transient AtomicCache<BlockData[]> blockDataTopsArray = new AtomicCache<>();
@Desc("The varience dispersion is used when multiple blocks are put in the palette. Scatter scrambles them, Wispy shows streak-looking varience")
private IrisGeneratorStyle variance = NoiseStyle.STATIC.style();
@Desc("Forcefully place this decorant anywhere it is supposed to go even if it should not go on a specific surface block. For example, you could force tallgrass to place on top of stone by using this.")
@@ -134,24 +136,23 @@ public class IrisDecorator {
return palette;
}
public BlockData getBlockData(IrisBiome b, RNG rng, double x, double z, IrisData data) {
public boolean passesChanceGate(RNG rng, double x, double z, IrisData data) {
if (getBlockData(data).isEmpty()) {
Iris.warn("Empty Block Data for " + b.getName());
return null;
return false;
}
double xx = x / style.getZoom();
double zz = z / style.getZoom();
return getGenerator(rng, data).fitDouble(0D, 1D, xx, zz) <= chance;
}
if (getGenerator(rng, data).fitDouble(0D, 1D, xx, zz) <= chance) {
if (getBlockData(data).size() == 1) {
return getBlockData(data).get(0);
}
return getVarianceGenerator(rng, data).fit(getBlockData(data), z, x); //X and Z must be switched
public BlockData getBlockData(IrisBiome b, RNG rng, double x, double z, IrisData data) {
if (!passesChanceGate(rng, x, z, data)) {
return null;
}
return null;
if (getBlockData(data).size() == 1) {
return getBlockData(data).get(0);
}
return getVarianceGenerator(rng, data).fit(getBlockData(data), z, x);
}
public BlockData getBlockData100(IrisBiome b, RNG rng, double x, double y, double z, IrisData data) {
@@ -230,6 +231,42 @@ public class IrisDecorator {
});
}
public BlockData[] getBlockDataArray(IrisData data) {
return blockDataArray.aquire(() -> {
KList<BlockData> list = getBlockData(data);
return list.toArray(new BlockData[0]);
});
}
public BlockData[] getBlockDataTopsArray(IrisData data) {
return blockDataTopsArray.aquire(() -> {
KList<BlockData> list = getBlockDataTops(data);
return list.toArray(new BlockData[0]);
});
}
public BlockData pickBlockData(RNG rng, IrisData data, double x, double z) {
BlockData[] arr = getBlockDataArray(data);
if (arr.length == 0) {
return null;
}
if (arr.length == 1) {
return arr[0];
}
return arr[Math.abs((int) getVarianceGenerator(rng, data).fit(0, arr.length - 1, z, x))];
}
public BlockData pickBlockDataTop(RNG rng, IrisData data, double x, double z) {
BlockData[] arr = getBlockDataTopsArray(data);
if (arr.length == 0) {
return pickBlockData(rng, data, x, z);
}
if (arr.length == 1) {
return arr[0];
}
return arr[Math.abs((int) getVarianceGenerator(rng, data).fit(0, arr.length - 1, z, x))];
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isStacking() {
return getStackMax() > 1;
@@ -0,0 +1,129 @@
package art.arcane.iris.engine.decorator;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisDecorationPart;
import art.arcane.iris.engine.object.IrisDecorator;
import art.arcane.volmlib.util.math.RNG;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
public class DecoratorCoreTest {
@Test
public void partSeed_differsByPartOrdinal() {
long base = 123456789L;
long s0 = DecoratorCore.partSeed(base, 0);
long s1 = DecoratorCore.partSeed(base, 1);
long s2 = DecoratorCore.partSeed(base, 2);
assertNotEquals(s0, s1);
assertNotEquals(s1, s2);
}
@Test
public void partSeed_isDeterministic() {
long base = 987654321L;
assertEquals(DecoratorCore.partSeed(base, 0), DecoratorCore.partSeed(base, 0));
assertEquals(DecoratorCore.partSeed(base, 3), DecoratorCore.partSeed(base, 3));
}
@Test
public void placeOpts_resetClearsAllFields() {
DecoratorCore.PlaceOpts opts = DecoratorCore.SCRATCH_OPTS.get();
opts.caveSkipFluid = true;
opts.underwater = true;
opts.fluidHeight = 99;
opts.reset();
assertFalse(opts.caveSkipFluid);
assertFalse(opts.underwater);
assertEquals(0, opts.fluidHeight);
}
@Test
public void scratchOpts_sameInstanceReturnedWithinThread() {
DecoratorCore.PlaceOpts a = DecoratorCore.SCRATCH_OPTS.get();
DecoratorCore.PlaceOpts b = DecoratorCore.SCRATCH_OPTS.get();
assertSame(a, b);
}
@Test
public void pickDecorator_emptyBucket_returnsNull() {
IrisBiome biome = mock(IrisBiome.class);
IrisData data = mock(IrisData.class);
when(biome.getDecoratorBucket(IrisDecorationPart.NONE)).thenReturn(new IrisDecorator[0]);
IrisDecorator result = DecoratorCore.pickDecorator(
biome, IrisDecorationPart.NONE, new RNG(1L), new RNG(2L), data, 0.0, 0.0);
assertNull(result);
}
@Test
public void pickDecorator_nonePassChanceGate_returnsNull() {
IrisBiome biome = mock(IrisBiome.class);
IrisData data = mock(IrisData.class);
IrisDecorator d = mock(IrisDecorator.class);
when(d.passesChanceGate(any(RNG.class), anyDouble(), anyDouble(), any(IrisData.class))).thenReturn(false);
when(biome.getDecoratorBucket(IrisDecorationPart.NONE)).thenReturn(new IrisDecorator[]{d});
IrisDecorator result = DecoratorCore.pickDecorator(
biome, IrisDecorationPart.NONE, new RNG(1L), new RNG(2L), data, 0.0, 0.0);
assertNull(result);
}
@Test
public void pickDecorator_singleCandidate_alwaysReturnsThat() {
IrisBiome biome = mock(IrisBiome.class);
IrisData data = mock(IrisData.class);
IrisDecorator d = mock(IrisDecorator.class);
when(d.passesChanceGate(any(RNG.class), anyDouble(), anyDouble(), any(IrisData.class))).thenReturn(true);
when(biome.getDecoratorBucket(IrisDecorationPart.NONE)).thenReturn(new IrisDecorator[]{d});
RNG gRNG = new RNG(42L);
for (int t = 0; t < 50; t++) {
IrisDecorator result = DecoratorCore.pickDecorator(
biome, IrisDecorationPart.NONE, gRNG, new RNG(t * 13L + 7), data, 0.0, 0.0);
assertSame(d, result);
}
}
@Test
public void pickDecorator_multiplePassingCandidates_selectsUniformly() {
IrisBiome biome = mock(IrisBiome.class);
IrisData data = mock(IrisData.class);
int n = 4;
IrisDecorator[] decorators = new IrisDecorator[n];
for (int i = 0; i < n; i++) {
IrisDecorator d = mock(IrisDecorator.class);
when(d.passesChanceGate(any(RNG.class), anyDouble(), anyDouble(), any(IrisData.class))).thenReturn(true);
decorators[i] = d;
}
when(biome.getDecoratorBucket(IrisDecorationPart.NONE)).thenReturn(decorators);
RNG gRNG = new RNG(99L);
int[] counts = new int[n];
int trials = 2000;
for (int t = 0; t < trials; t++) {
IrisDecorator picked = DecoratorCore.pickDecorator(
biome, IrisDecorationPart.NONE, gRNG, new RNG(t * 31L + 3), data, 0.0, 0.0);
assertNotNull(picked);
for (int i = 0; i < n; i++) {
if (picked == decorators[i]) {
counts[i]++;
break;
}
}
}
double expected = trials / (double) n;
for (int i = 0; i < n; i++) {
double deviation = Math.abs(counts[i] - expected) / expected;
assertTrue("Decorator " + i + " selected " + counts[i] + " times; expected ~" + (int) expected
+ " (deviation " + String.format("%.0f%%", deviation * 100) + ")", deviation < 0.20);
}
}
}