mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 04:37:47 +00:00
d
This commit is contained in:
@@ -47,13 +47,11 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import lombok.Data;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState> {
|
||||
private static final byte LIQUID_FLUID = 1;
|
||||
private static final ThreadLocal<CarveScratch> SCRATCH = ThreadLocal.withInitial(CarveScratch::new);
|
||||
private static final ThreadLocal<IrisCarveScratch> SCRATCH = ThreadLocal.withInitial(IrisCarveScratch::new);
|
||||
private static final int CAVE_BIOME_BLEND_RADIUS = 3;
|
||||
private static final int CAVE_BIOME_BLEND_CENTER_WEIGHT = 4;
|
||||
private static final int CAVE_BIOME_BLEND_TOTAL_WEIGHT = 8;
|
||||
@@ -72,15 +70,15 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
@Override
|
||||
@ChunkCoordinates
|
||||
public void onModify(int x, int z, Hunk<PlatformBlockState> output, boolean multicore, ChunkContext context) {
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
PrecisionStopwatch caveStopwatch = PrecisionStopwatch.start();
|
||||
Mantle<Matter> mantle = getEngine().getMantle().getMantle();
|
||||
IrisDimensionCarvingResolver.State resolverState = new IrisDimensionCarvingResolver.State();
|
||||
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache = new Long2ObjectOpenHashMap<>(2048);
|
||||
CarveScratch scratch = SCRATCH.get();
|
||||
IrisCarveScratch scratch = SCRATCH.get();
|
||||
scratch.reset();
|
||||
PackedWallBuffer walls = scratch.walls;
|
||||
ColumnMask[] columnMasks = scratch.columnMasks;
|
||||
ColumnMask[] boundaryMasks = scratch.boundaryMasks;
|
||||
CarveWallBuffer walls = scratch.walls;
|
||||
CarveColumnMask[] columnMasks = scratch.columnMasks;
|
||||
CarveColumnMask[] boundaryMasks = scratch.boundaryMasks;
|
||||
MatterCavern[] boundaryCaverns = scratch.boundaryCaverns;
|
||||
int[] surfaceHeights = scratch.surfaceHeights;
|
||||
Map<String, IrisBiome> customBiomeCache = scratch.customBiomeCache;
|
||||
@@ -102,13 +100,13 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
}
|
||||
}
|
||||
|
||||
MantleChunk<Matter> mc = mantle.getChunk(x, z).use();
|
||||
MantleChunk<Matter> mantleChunk = mantle.getChunk(x, z).use();
|
||||
try {
|
||||
PrecisionStopwatch resolveStopwatch = PrecisionStopwatch.start();
|
||||
final int worldHeightSpan = getEngine().getWorld().maxHeight() - getEngine().getWorld().minHeight();
|
||||
final int caveLavaHeight = getEngine().getDimension().getCaveLavaHeight();
|
||||
mc.iterate(MatterCavern.class, (xx, yy, zz, c) -> {
|
||||
if (c == null) {
|
||||
int worldHeightSpan = getEngine().getWorld().maxHeight() - getEngine().getWorld().minHeight();
|
||||
int caveLavaHeight = getEngine().getDimension().getCaveLavaHeight();
|
||||
mantleChunk.iterate(MatterCavern.class, (xx, yy, zz, cavern) -> {
|
||||
if (cavern == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -125,15 +123,15 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
}
|
||||
|
||||
PlatformBlockState current = output.getRaw(rx, yy, rz);
|
||||
boolean explicitCarveIntent = hasExplicitCarveIntent(c);
|
||||
boolean explicitCarveIntent = hasExplicitCarveIntent(cavern);
|
||||
|
||||
if (shouldPreserveExistingFluid(c, current)) {
|
||||
if (shouldPreserveExistingFluid(cavern, current)) {
|
||||
return;
|
||||
}
|
||||
|
||||
columnMasks[columnIndex].add(yy);
|
||||
|
||||
if (!c.getCustomBiome().isEmpty()) {
|
||||
if (!cavern.getCustomBiome().isEmpty()) {
|
||||
scratch.customCaveBiomePresent = true;
|
||||
}
|
||||
|
||||
@@ -144,8 +142,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
if (explicitCarveIntent) {
|
||||
// Only a fluid cavern consumes the fluid sample, and on the maintenance path that
|
||||
// sample is a full procedural stream evaluation, so never take it per voxel.
|
||||
PlatformBlockState fluid = isFluidIntent(c) ? context.getFluid().get(rx, rz) : null;
|
||||
output.setRaw(rx, yy, rz, resolveExplicitCarveState(c, fluid, LAVA, AIR));
|
||||
PlatformBlockState fluid = isFluidIntent(cavern) ? context.getFluid().get(rx, rz) : null;
|
||||
output.setRaw(rx, yy, rz, resolveExplicitCarveState(cavern, fluid, LAVA, AIR));
|
||||
} else if (usesDefaultLava(caveLavaHeight, yy)) {
|
||||
output.setRaw(rx, yy, rz, LAVA);
|
||||
} else {
|
||||
@@ -153,18 +151,18 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
}
|
||||
});
|
||||
if (scratch.customCaveBiomePresent) {
|
||||
addInternalWallsFromMantle(mc, walls, columnMasks);
|
||||
addInternalWallsFromMantle(mantleChunk, walls, columnMasks);
|
||||
} else {
|
||||
addInternalWallsFromMasks(walls, columnMasks);
|
||||
}
|
||||
addCrossChunkBoundaryWalls(mantle, mc, walls, boundaryMasks, boundaryCaverns, x, z, surfaceHeights);
|
||||
addCrossChunkBoundaryWalls(mantle, mantleChunk, walls, boundaryMasks, boundaryCaverns, x, z, surfaceHeights);
|
||||
getEngine().getMetrics().getCarveResolve().put(resolveStopwatch.getMilliseconds());
|
||||
|
||||
PrecisionStopwatch applyStopwatch = PrecisionStopwatch.start();
|
||||
try {
|
||||
walls.forEach((rx, yy, rz, cavern) -> {
|
||||
int worldX = rx + PowerOfTwoCoordinates.chunkToBlock(x);
|
||||
int worldZ = rz + PowerOfTwoCoordinates.chunkToBlock(z);
|
||||
int worldX = rx + chunkBlockX;
|
||||
int worldZ = rz + chunkBlockZ;
|
||||
String customBiome = cavern.getCustomBiome();
|
||||
IrisBiome biome = customBiome.isEmpty()
|
||||
? resolveCaveBiome(caveBiomeCache, worldX, yy, worldZ, resolverState)
|
||||
@@ -181,7 +179,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
});
|
||||
|
||||
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||
processColumnFromMask(output, mc, mantle, columnMasks[columnIndex], columnIndex, x, z, resolverState, caveBiomeCache);
|
||||
processColumnFromMask(output, mantleChunk, mantle, columnMasks[columnIndex], columnIndex, x, z, resolverState, caveBiomeCache);
|
||||
}
|
||||
|
||||
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||
@@ -194,12 +192,33 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
}
|
||||
processBoundaryColumnFromMask(output, boundaryMasks[columnIndex], cavern, columnIndex, x, z, resolverState, caveBiomeCache, customBiomeCache);
|
||||
}
|
||||
|
||||
// Surface-break carving must not leave an ore cap suspended across the opening.
|
||||
for (int columnIndex = 0; columnIndex < surfaceHeights.length; columnIndex++) {
|
||||
int surfaceY = surfaceHeights[columnIndex];
|
||||
if (surfaceY <= 0 || surfaceY >= output.getHeight()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int belowY = surfaceY - 1;
|
||||
if (!columnMasks[columnIndex].contains(belowY) && !boundaryMasks[columnIndex].contains(belowY)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int localX = PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
|
||||
int localZ = columnIndex & 15;
|
||||
PlatformBlockState surface = output.getRaw(localX, surfaceY, localZ);
|
||||
PlatformBlockState below = output.getRaw(localX, belowY, localZ);
|
||||
if (isUnsupportedSurfaceOre(surface, below)) {
|
||||
output.setRaw(localX, surfaceY, localZ, AIR);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
getEngine().getMetrics().getCarveApply().put(applyStopwatch.getMilliseconds());
|
||||
}
|
||||
} finally {
|
||||
getEngine().getMetrics().getCave().put(p.getMilliseconds());
|
||||
mc.release();
|
||||
getEngine().getMetrics().getCave().put(caveStopwatch.getMilliseconds());
|
||||
mantleChunk.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,6 +238,10 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
return y <= caveLavaHeight;
|
||||
}
|
||||
|
||||
static boolean isUnsupportedSurfaceOre(PlatformBlockState surface, PlatformBlockState below) {
|
||||
return B.isOre(surface) && !B.isSolid(below);
|
||||
}
|
||||
|
||||
static PlatformBlockState resolveExplicitCarveState(MatterCavern cavern, PlatformBlockState fluid,
|
||||
PlatformBlockState lava, PlatformBlockState air) {
|
||||
if (cavern == null) {
|
||||
@@ -233,9 +256,9 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
return cavern.getLiquid() == 3 ? air : null;
|
||||
}
|
||||
|
||||
private void addInternalWallsFromMasks(PackedWallBuffer walls, ColumnMask[] columnMasks) {
|
||||
private void addInternalWallsFromMasks(CarveWallBuffer walls, CarveColumnMask[] columnMasks) {
|
||||
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||
ColumnMask columnMask = columnMasks[columnIndex];
|
||||
CarveColumnMask columnMask = columnMasks[columnIndex];
|
||||
if (columnMask.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
@@ -261,9 +284,9 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
}
|
||||
}
|
||||
|
||||
private void addInternalWallsFromMantle(MantleChunk<Matter> mc, PackedWallBuffer walls, ColumnMask[] columnMasks) {
|
||||
private void addInternalWallsFromMantle(MantleChunk<Matter> mc, CarveWallBuffer walls, CarveColumnMask[] columnMasks) {
|
||||
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||
ColumnMask columnMask = columnMasks[columnIndex];
|
||||
CarveColumnMask columnMask = columnMasks[columnIndex];
|
||||
if (columnMask.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
@@ -295,8 +318,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
private void addCrossChunkBoundaryWalls(
|
||||
Mantle<Matter> mantle,
|
||||
MantleChunk<Matter> mc,
|
||||
PackedWallBuffer walls,
|
||||
ColumnMask[] boundaryMasks,
|
||||
CarveWallBuffer walls,
|
||||
CarveColumnMask[] boundaryMasks,
|
||||
MatterCavern[] boundaryCaverns,
|
||||
int chunkX,
|
||||
int chunkZ,
|
||||
@@ -344,8 +367,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
private void tryAddBoundaryWall(
|
||||
MantleChunk<Matter> mc,
|
||||
MantleChunk<Matter> neighborChunk,
|
||||
PackedWallBuffer walls,
|
||||
ColumnMask[] boundaryMasks,
|
||||
CarveWallBuffer walls,
|
||||
CarveColumnMask[] boundaryMasks,
|
||||
MatterCavern[] boundaryCaverns,
|
||||
int localX,
|
||||
int yy,
|
||||
@@ -382,7 +405,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
Hunk<PlatformBlockState> output,
|
||||
MantleChunk<Matter> mc,
|
||||
Mantle<Matter> mantle,
|
||||
ColumnMask columnMask,
|
||||
CarveColumnMask columnMask,
|
||||
int columnIndex,
|
||||
int chunkX,
|
||||
int chunkZ,
|
||||
@@ -408,16 +431,14 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
int y = firstHeight;
|
||||
|
||||
while (y >= 0) {
|
||||
if (y >= 0 && y <= getEngine().getHeight()) {
|
||||
if (y <= getEngine().getHeight()) {
|
||||
if (y == buf + 1) {
|
||||
buf = y;
|
||||
zone.ceiling = buf;
|
||||
} else if (zone.isValid(getEngine())) {
|
||||
processZone(output, mc, mantle, zone, rx, rz, worldX, worldZ, resolverState, caveBiomeCache);
|
||||
zone = new CaveZone();
|
||||
zone.setFloor(y);
|
||||
buf = y;
|
||||
} else {
|
||||
if (zone.isValid(getEngine())) {
|
||||
processZone(output, mc, mantle, zone, rx, rz, worldX, worldZ, resolverState, caveBiomeCache);
|
||||
}
|
||||
zone = new CaveZone();
|
||||
zone.setFloor(y);
|
||||
buf = y;
|
||||
@@ -434,7 +455,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
|
||||
private void processBoundaryColumnFromMask(
|
||||
Hunk<PlatformBlockState> output,
|
||||
ColumnMask boundaryMask,
|
||||
CarveColumnMask boundaryMask,
|
||||
MatterCavern cavern,
|
||||
int columnIndex,
|
||||
int chunkX,
|
||||
@@ -695,251 +716,6 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
return loaded;
|
||||
}
|
||||
|
||||
private static final class PackedWallBuffer {
|
||||
private static final int EMPTY_KEY = -1;
|
||||
private static final double LOAD_FACTOR = 0.75D;
|
||||
|
||||
private int[] keys;
|
||||
private MatterCavern[] values;
|
||||
private int mask;
|
||||
private int resizeAt;
|
||||
private int size;
|
||||
|
||||
private PackedWallBuffer(int expectedSize) {
|
||||
int capacity = 1;
|
||||
int minimumCapacity = Math.max(8, expectedSize);
|
||||
while (capacity < minimumCapacity) {
|
||||
capacity <<= 1;
|
||||
}
|
||||
|
||||
this.keys = new int[capacity];
|
||||
Arrays.fill(this.keys, EMPTY_KEY);
|
||||
this.values = new MatterCavern[capacity];
|
||||
this.mask = capacity - 1;
|
||||
this.resizeAt = Math.max(1, (int) (capacity * LOAD_FACTOR));
|
||||
}
|
||||
|
||||
private void put(int x, int y, int z, MatterCavern value) {
|
||||
int key = pack(x, y, z);
|
||||
int index = mix(key) & mask;
|
||||
|
||||
while (true) {
|
||||
int existingKey = keys[index];
|
||||
if (existingKey == EMPTY_KEY) {
|
||||
keys[index] = key;
|
||||
values[index] = value;
|
||||
size++;
|
||||
if (size >= resizeAt) {
|
||||
resize();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingKey == key) {
|
||||
values[index] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
index = (index + 1) & mask;
|
||||
}
|
||||
}
|
||||
|
||||
private void forEach(PackedWallConsumer consumer) {
|
||||
for (int index = 0; index < keys.length; index++) {
|
||||
int key = keys[index];
|
||||
if (key == EMPTY_KEY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MatterCavern cavern = values[index];
|
||||
if (cavern == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
consumer.accept(unpackX(key), unpackY(key), unpackZ(key), cavern);
|
||||
}
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
Arrays.fill(keys, EMPTY_KEY);
|
||||
Arrays.fill(values, null);
|
||||
size = 0;
|
||||
}
|
||||
|
||||
private void resize() {
|
||||
int[] oldKeys = keys;
|
||||
MatterCavern[] oldValues = values;
|
||||
int nextCapacity = oldKeys.length << 1;
|
||||
keys = new int[nextCapacity];
|
||||
Arrays.fill(keys, EMPTY_KEY);
|
||||
values = new MatterCavern[nextCapacity];
|
||||
mask = nextCapacity - 1;
|
||||
resizeAt = Math.max(1, (int) (nextCapacity * LOAD_FACTOR));
|
||||
size = 0;
|
||||
|
||||
for (int index = 0; index < oldKeys.length; index++) {
|
||||
int key = oldKeys[index];
|
||||
if (key == EMPTY_KEY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MatterCavern value = oldValues[index];
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
reinsert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void reinsert(int key, MatterCavern value) {
|
||||
int index = mix(key) & mask;
|
||||
while (keys[index] != EMPTY_KEY) {
|
||||
index = (index + 1) & mask;
|
||||
}
|
||||
|
||||
keys[index] = key;
|
||||
values[index] = value;
|
||||
size++;
|
||||
}
|
||||
|
||||
private int pack(int x, int y, int z) {
|
||||
return (y << 8) | PowerOfTwoCoordinates.packLocal16(x & 15, z & 15);
|
||||
}
|
||||
|
||||
private int unpackX(int key) {
|
||||
return PowerOfTwoCoordinates.unpackLocal16X(key & 255);
|
||||
}
|
||||
|
||||
private int unpackY(int key) {
|
||||
return key >> 8;
|
||||
}
|
||||
|
||||
private int unpackZ(int key) {
|
||||
return PowerOfTwoCoordinates.unpackLocal16Z(key);
|
||||
}
|
||||
|
||||
private int mix(int value) {
|
||||
int mixed = value * 0x9E3779B9;
|
||||
return mixed ^ (mixed >>> 16);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CarveScratch {
|
||||
private final ColumnMask[] columnMasks = new ColumnMask[256];
|
||||
private final ColumnMask[] boundaryMasks = new ColumnMask[256];
|
||||
private final MatterCavern[] boundaryCaverns = new MatterCavern[256];
|
||||
private final int[] surfaceHeights = new int[256];
|
||||
private final PackedWallBuffer walls = new PackedWallBuffer(512);
|
||||
private final Map<String, IrisBiome> customBiomeCache = new HashMap<>();
|
||||
private int[] upperSurfaceHeights;
|
||||
private boolean customCaveBiomePresent;
|
||||
|
||||
private CarveScratch() {
|
||||
for (int index = 0; index < columnMasks.length; index++) {
|
||||
columnMasks[index] = new ColumnMask();
|
||||
boundaryMasks[index] = new ColumnMask();
|
||||
}
|
||||
}
|
||||
|
||||
private int[] getOrCreateUpperSurfaceHeights() {
|
||||
if (upperSurfaceHeights == null) {
|
||||
upperSurfaceHeights = new int[256];
|
||||
}
|
||||
return upperSurfaceHeights;
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
for (int index = 0; index < columnMasks.length; index++) {
|
||||
columnMasks[index].clear();
|
||||
boundaryMasks[index].clear();
|
||||
boundaryCaverns[index] = null;
|
||||
}
|
||||
walls.clear();
|
||||
customBiomeCache.clear();
|
||||
customCaveBiomePresent = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ColumnMask {
|
||||
private long[] words = new long[8];
|
||||
private int maxWord = -1;
|
||||
|
||||
private void add(int y) {
|
||||
if (y < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int wordIndex = y >> 6;
|
||||
if (wordIndex >= words.length) {
|
||||
words = Arrays.copyOf(words, Math.max(words.length << 1, wordIndex + 1));
|
||||
}
|
||||
|
||||
words[wordIndex] |= 1L << (y & 63);
|
||||
if (wordIndex > maxWord) {
|
||||
maxWord = wordIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private int nextSetBit(int fromBit) {
|
||||
if (maxWord < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int startBit = Math.max(0, fromBit);
|
||||
int wordIndex = startBit >> 6;
|
||||
if (wordIndex > maxWord) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
long word = words[wordIndex] & (-1L << (startBit & 63));
|
||||
while (true) {
|
||||
if (word != 0L) {
|
||||
return (wordIndex << 6) + Long.numberOfTrailingZeros(word);
|
||||
}
|
||||
|
||||
wordIndex++;
|
||||
if (wordIndex > maxWord) {
|
||||
return -1;
|
||||
}
|
||||
word = words[wordIndex];
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEmpty() {
|
||||
return maxWord < 0;
|
||||
}
|
||||
|
||||
private boolean contains(int y) {
|
||||
if (y < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int wordIndex = y >> 6;
|
||||
if (wordIndex > maxWord) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (words[wordIndex] & (1L << (y & 63))) != 0L;
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
if (maxWord < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int index = 0; index <= maxWord; index++) {
|
||||
words[index] = 0L;
|
||||
}
|
||||
maxWord = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface PackedWallConsumer {
|
||||
void accept(int x, int y, int z, MatterCavern cavern);
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class CaveZone {
|
||||
private int ceiling = -1;
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* 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.modifier;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
final class IrisCarveScratch {
|
||||
final CarveColumnMask[] columnMasks = new CarveColumnMask[256];
|
||||
final CarveColumnMask[] boundaryMasks = new CarveColumnMask[256];
|
||||
final MatterCavern[] boundaryCaverns = new MatterCavern[256];
|
||||
final int[] surfaceHeights = new int[256];
|
||||
final CarveWallBuffer walls = new CarveWallBuffer(512);
|
||||
final Map<String, IrisBiome> customBiomeCache = new HashMap<>();
|
||||
int[] upperSurfaceHeights;
|
||||
boolean customCaveBiomePresent;
|
||||
|
||||
IrisCarveScratch() {
|
||||
for (int index = 0; index < columnMasks.length; index++) {
|
||||
columnMasks[index] = new CarveColumnMask();
|
||||
boundaryMasks[index] = new CarveColumnMask();
|
||||
}
|
||||
}
|
||||
|
||||
int[] getOrCreateUpperSurfaceHeights() {
|
||||
if (upperSurfaceHeights == null) {
|
||||
upperSurfaceHeights = new int[256];
|
||||
}
|
||||
return upperSurfaceHeights;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
for (int index = 0; index < columnMasks.length; index++) {
|
||||
columnMasks[index].clear();
|
||||
boundaryMasks[index].clear();
|
||||
boundaryCaverns[index] = null;
|
||||
}
|
||||
walls.clear();
|
||||
customBiomeCache.clear();
|
||||
customCaveBiomePresent = false;
|
||||
}
|
||||
}
|
||||
|
||||
final class CarveColumnMask {
|
||||
private long[] words = new long[8];
|
||||
private int maxWord = -1;
|
||||
|
||||
void add(int y) {
|
||||
if (y < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int wordIndex = y >> 6;
|
||||
if (wordIndex >= words.length) {
|
||||
words = Arrays.copyOf(words, Math.max(words.length << 1, wordIndex + 1));
|
||||
}
|
||||
|
||||
words[wordIndex] |= 1L << (y & 63);
|
||||
if (wordIndex > maxWord) {
|
||||
maxWord = wordIndex;
|
||||
}
|
||||
}
|
||||
|
||||
int nextSetBit(int fromBit) {
|
||||
if (maxWord < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int startBit = Math.max(0, fromBit);
|
||||
int wordIndex = startBit >> 6;
|
||||
if (wordIndex > maxWord) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
long word = words[wordIndex] & (-1L << (startBit & 63));
|
||||
while (true) {
|
||||
if (word != 0L) {
|
||||
return (wordIndex << 6) + Long.numberOfTrailingZeros(word);
|
||||
}
|
||||
|
||||
wordIndex++;
|
||||
if (wordIndex > maxWord) {
|
||||
return -1;
|
||||
}
|
||||
word = words[wordIndex];
|
||||
}
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return maxWord < 0;
|
||||
}
|
||||
|
||||
boolean contains(int y) {
|
||||
if (y < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int wordIndex = y >> 6;
|
||||
if (wordIndex > maxWord) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (words[wordIndex] & (1L << (y & 63))) != 0L;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
if (maxWord < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int index = 0; index <= maxWord; index++) {
|
||||
words[index] = 0L;
|
||||
}
|
||||
maxWord = -1;
|
||||
}
|
||||
}
|
||||
|
||||
final class CarveWallBuffer {
|
||||
private static final int EMPTY_KEY = -1;
|
||||
private static final double LOAD_FACTOR = 0.75D;
|
||||
|
||||
private int[] keys;
|
||||
private MatterCavern[] values;
|
||||
private int mask;
|
||||
private int resizeAt;
|
||||
private int size;
|
||||
|
||||
CarveWallBuffer(int expectedSize) {
|
||||
int capacity = 1;
|
||||
int minimumCapacity = Math.max(8, expectedSize);
|
||||
while (capacity < minimumCapacity) {
|
||||
capacity <<= 1;
|
||||
}
|
||||
|
||||
keys = new int[capacity];
|
||||
Arrays.fill(keys, EMPTY_KEY);
|
||||
values = new MatterCavern[capacity];
|
||||
mask = capacity - 1;
|
||||
resizeAt = Math.max(1, (int) (capacity * LOAD_FACTOR));
|
||||
}
|
||||
|
||||
void put(int x, int y, int z, MatterCavern value) {
|
||||
int key = pack(x, y, z);
|
||||
int index = mix(key) & mask;
|
||||
|
||||
while (true) {
|
||||
int existingKey = keys[index];
|
||||
if (existingKey == EMPTY_KEY) {
|
||||
keys[index] = key;
|
||||
values[index] = value;
|
||||
size++;
|
||||
if (size >= resizeAt) {
|
||||
resize();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingKey == key) {
|
||||
values[index] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
index = (index + 1) & mask;
|
||||
}
|
||||
}
|
||||
|
||||
void forEach(Consumer consumer) {
|
||||
for (int index = 0; index < keys.length; index++) {
|
||||
int key = keys[index];
|
||||
if (key == EMPTY_KEY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MatterCavern cavern = values[index];
|
||||
if (cavern != null) {
|
||||
consumer.accept(unpackX(key), unpackY(key), unpackZ(key), cavern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
Arrays.fill(keys, EMPTY_KEY);
|
||||
Arrays.fill(values, null);
|
||||
size = 0;
|
||||
}
|
||||
|
||||
private void resize() {
|
||||
int[] oldKeys = keys;
|
||||
MatterCavern[] oldValues = values;
|
||||
int nextCapacity = oldKeys.length << 1;
|
||||
keys = new int[nextCapacity];
|
||||
Arrays.fill(keys, EMPTY_KEY);
|
||||
values = new MatterCavern[nextCapacity];
|
||||
mask = nextCapacity - 1;
|
||||
resizeAt = Math.max(1, (int) (nextCapacity * LOAD_FACTOR));
|
||||
size = 0;
|
||||
|
||||
for (int index = 0; index < oldKeys.length; index++) {
|
||||
int key = oldKeys[index];
|
||||
MatterCavern value = oldValues[index];
|
||||
if (key != EMPTY_KEY && value != null) {
|
||||
reinsert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reinsert(int key, MatterCavern value) {
|
||||
int index = mix(key) & mask;
|
||||
while (keys[index] != EMPTY_KEY) {
|
||||
index = (index + 1) & mask;
|
||||
}
|
||||
|
||||
keys[index] = key;
|
||||
values[index] = value;
|
||||
size++;
|
||||
}
|
||||
|
||||
private int pack(int x, int y, int z) {
|
||||
return (y << 8) | PowerOfTwoCoordinates.packLocal16(x & 15, z & 15);
|
||||
}
|
||||
|
||||
private int unpackX(int key) {
|
||||
return PowerOfTwoCoordinates.unpackLocal16X(key & 255);
|
||||
}
|
||||
|
||||
private int unpackY(int key) {
|
||||
return key >> 8;
|
||||
}
|
||||
|
||||
private int unpackZ(int key) {
|
||||
return PowerOfTwoCoordinates.unpackLocal16Z(key);
|
||||
}
|
||||
|
||||
private int mix(int value) {
|
||||
int mixed = value * 0x9E3779B9;
|
||||
return mixed ^ (mixed >>> 16);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface Consumer {
|
||||
void accept(int x, int y, int z, MatterCavern cavern);
|
||||
}
|
||||
}
|
||||
@@ -161,10 +161,10 @@ public class IrisBiome extends IrisRegistrant implements IRare {
|
||||
private IrisGeneratorStyle childStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@ArrayType(min = 1, type = String.class)
|
||||
@Desc("List any biome names (file names without.json) here as children. Portions of this biome can sometimes morph into their children. Iris supports cyclic relationships such as A > B > A > B. Iris will stop checking 9 biomes down the tree.")
|
||||
@Desc("List any biome names (file names without.json) here as children. Portions of this biome can sometimes morph into their children. Iris supports cyclic relationships such as A > B > A > B. Iris will stop checking 4 biomes down the tree.")
|
||||
private KList<String> children = new KList<>();
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@Desc("The carving biome. If specified the biome will be used when under a carving instead of this current biome.")
|
||||
@Desc("Registers the referenced biome with this pack's reachable biome set. It is NOT applied as a substitute under carvings; cave biomes come from the region's caveBiomes list.")
|
||||
private String carvingBiome = "";
|
||||
@MinNumber(0)
|
||||
@MaxNumber(256)
|
||||
@@ -179,10 +179,10 @@ public class IrisBiome extends IrisRegistrant implements IRare {
|
||||
@Desc("This defines the layers of materials in this biome. Each layer has a palette and min/max height and some other properties. Usually a grassy/sandy layer then a dirt layer then a stone layer. Iris will fill in the remaining blocks below your layers with stone.")
|
||||
private KList<IrisBiomePaletteLayer> layers = new KList<IrisBiomePaletteLayer>().qadd(new IrisBiomePaletteLayer());
|
||||
@ArrayType(type = IrisBiomePaletteLayer.class)
|
||||
@Desc("This defines the layers of materials in this biome. Each layer has a palette and min/max height and some other properties. Usually a grassy/sandy layer then a dirt layer then a stone layer. Iris will fill in the remaining blocks below your layers with stone.")
|
||||
@Desc("Layers of materials placed on cave ceilings in this biome, indexed upward from the ceiling surface. Must not have more entries than layers, whose height generators it reuses.")
|
||||
private KList<IrisBiomePaletteLayer> caveCeilingLayers = new KList<IrisBiomePaletteLayer>().qadd(new IrisBiomePaletteLayer());
|
||||
@ArrayType(type = IrisBiomePaletteLayer.class)
|
||||
@Desc("This defines the layers of materials in this biome. Each layer has a palette and min/max height and some other properties. Usually a grassy/sandy layer then a dirt layer then a stone layer. Iris will fill in the remaining blocks below your layers with stone.")
|
||||
@Desc("Layers of materials filling the water column of sea biomes, indexed downward from the water surface. Anything below the last layer is filled with the dimension fluid palette, not stone.")
|
||||
private KList<IrisBiomePaletteLayer> seaLayers = new KList<>();
|
||||
@ArrayType(min = 1, type = IrisDecorator.class)
|
||||
@Desc("Decorators are used for things like tall grass, bisected flowers, and even kelp or cactus (random heights)")
|
||||
|
||||
@@ -22,21 +22,54 @@ import art.arcane.iris.engine.object.annotations.Desc;
|
||||
|
||||
@Desc("The custom biome category. Vanilla asks for this, basically what represents your biome closest?")
|
||||
public enum IrisBiomeCustomCategory {
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'beach' (shoreline biomes).")
|
||||
beach,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'desert' (hot, dry sand biomes).")
|
||||
desert,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'extreme_hills' (mountain biomes).")
|
||||
extreme_hills,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'forest' (tree-dense temperate biomes).")
|
||||
forest,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'icy' (snow and ice biomes).")
|
||||
icy,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'jungle'.")
|
||||
jungle,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'mesa' (badlands).")
|
||||
mesa,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'mushroom' (mushroom fields).")
|
||||
mushroom,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'nether'.")
|
||||
nether,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'none' (no classification).")
|
||||
none,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'ocean'.")
|
||||
ocean,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'plains'. This is the default when category is omitted.")
|
||||
plains,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'river'.")
|
||||
river,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'savanna'.")
|
||||
savanna,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'swamp'.")
|
||||
swamp,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'taiga'.")
|
||||
taiga,
|
||||
|
||||
@Desc("Tags the generated datapack biome as vanilla category 'the_end'.")
|
||||
the_end
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public class IrisBiomePaletteLayer {
|
||||
|
||||
@Desc("The max thickness of this layer")
|
||||
private int maxHeight = 1;
|
||||
@Desc("If set, this layer will change size depending on the slope. If in bounds, the layer will get larger (taller) the closer to the center of this slope clip it is. If outside of the slipe's bounds, this layer will not show.")
|
||||
@Desc("If set, this layer only shows where the terrain slope is within these bounds; outside them the layer is skipped entirely. Thickness is not scaled by slope.")
|
||||
private IrisSlopeClip slopeCondition = new IrisSlopeClip();
|
||||
@MinNumber(0.0001)
|
||||
@Desc("The terrain zoom mostly for zooming in on a wispy palette")
|
||||
|
||||
@@ -28,7 +28,7 @@ public class IrisCaveFieldModule {
|
||||
@Desc("Threshold offset applied to this layer before blending.")
|
||||
private double threshold = 0;
|
||||
|
||||
@Desc("Vertical bounds where this module can contribute.")
|
||||
@Desc("Vertical bounds where this module can contribute, in engine-local Y where 0 is the bottom of the dimension, not world Y.")
|
||||
private IrisRange verticalRange = new IrisRange(0, 384);
|
||||
|
||||
@Desc("Invert this module before weighting.")
|
||||
|
||||
@@ -21,7 +21,7 @@ public class IrisCaveProfile {
|
||||
@Desc("Enable profile-driven cave carving.")
|
||||
private boolean enabled = false;
|
||||
|
||||
@Desc("Global vertical bounds for profile cave carving.")
|
||||
@Desc("Global vertical bounds for profile cave carving, in engine-local Y where 0 is the bottom of the dimension, not world Y.")
|
||||
private IrisRange verticalRange = new IrisRange(0, 384);
|
||||
|
||||
@MinNumber(0)
|
||||
@@ -73,7 +73,7 @@ public class IrisCaveProfile {
|
||||
|
||||
@MinNumber(2)
|
||||
@MaxNumber(4)
|
||||
@Desc("Horizontal adaptive predictor grid step used while classifying cave density planes.")
|
||||
@Desc("Horizontal adaptive predictor tuning. The carver always samples the predictor grid at a fixed step of 8; values below 8 only widen the ambiguity margin slightly and do not tighten the grid.")
|
||||
private int adaptiveSampleStep = 2;
|
||||
|
||||
@MinNumber(0)
|
||||
@@ -119,7 +119,7 @@ public class IrisCaveProfile {
|
||||
@Desc("Default cave anchor mode for cave-only object placement.")
|
||||
private IrisCaveAnchorMode defaultObjectAnchor = IrisCaveAnchorMode.FLOOR;
|
||||
|
||||
@Desc("Default placement mode for cave objects. Stilt modes tile the object base block down to the cave floor surface. FAST_MIN_STILT is recommended for cave objects to prevent floating.")
|
||||
@Desc("Default placement mode for cave objects. Only applies to placements that are still on the default CENTER_HEIGHT mode; an explicit mode on the placement always wins. Stilt modes tile the object base block down to the cave floor surface. FAST_MIN_STILT is recommended for cave objects to prevent floating.")
|
||||
private ObjectPlaceMode defaultObjectPlaceMode = null;
|
||||
|
||||
@MinNumber(1)
|
||||
|
||||
@@ -23,6 +23,7 @@ import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
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.RegistryListBlockType;
|
||||
import art.arcane.iris.engine.object.annotations.Required;
|
||||
import art.arcane.iris.engine.object.annotations.Snippet;
|
||||
import art.arcane.iris.engine.object.coral.CoralGenerator;
|
||||
@@ -94,12 +95,14 @@ public class IrisCoral implements IrisProceduralPlacement {
|
||||
|
||||
@Required
|
||||
@Desc("The structural coral block (the living wood of the reef), e.g. minecraft:tube_coral_block. Ignored when blockPalette is set.")
|
||||
@RegistryListBlockType
|
||||
private String block = "minecraft:tube_coral_block";
|
||||
|
||||
@Desc("A noise-driven palette for the structural coral block. When set this overrides the single block, letting the reef mix tube/brain/bubble/fire/horn coral_block tones across its body.")
|
||||
private IrisMaterialPalette blockPalette = null;
|
||||
|
||||
@Desc("Optional tip block placed at branch tips and at the very top of the structure, e.g. coral fans or minecraft:sea_pickle. Ignored when tipPalette is set. Null disables tips.")
|
||||
@RegistryListBlockType
|
||||
private String tipBlock = null;
|
||||
|
||||
@Desc("A noise-driven palette for the tip block. When set this overrides the single tipBlock, letting tips mix fan / pickle decorations.")
|
||||
|
||||
@@ -23,6 +23,7 @@ import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
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.RegistryListBlockType;
|
||||
import art.arcane.iris.engine.object.annotations.Required;
|
||||
import art.arcane.iris.engine.object.annotations.Snippet;
|
||||
import art.arcane.iris.engine.object.crystal.CrystalGenerator;
|
||||
@@ -91,12 +92,14 @@ public class IrisCrystal implements IrisProceduralPlacement {
|
||||
|
||||
@Required
|
||||
@Desc("The primary crystal shard block, e.g. minecraft:amethyst_block. Ignored when blockPalette is set.")
|
||||
@RegistryListBlockType
|
||||
private String block = "minecraft:amethyst_block";
|
||||
|
||||
@Desc("A noise-driven palette for the crystal shards (a prismatic mix, e.g. amethyst_block, calcite, tinted_glass). When set this overrides the single block, letting the shards mix blocks by noise. Palette wins.")
|
||||
private IrisMaterialPalette blockPalette = null;
|
||||
|
||||
@Desc("Optional block placed at the very tip of each shard for a different colored or sparkling point (e.g. minecraft:amethyst_cluster or glowstone). Ignored when tipPalette is set. If unset and glow is true, a light-emitting block is sprinkled among the tips instead.")
|
||||
@RegistryListBlockType
|
||||
private String tipBlock = null;
|
||||
|
||||
@Desc("A noise-driven palette for the shard tips. When set this overrides the single tipBlock. Palette wins.")
|
||||
@@ -111,9 +114,11 @@ public class IrisCrystal implements IrisProceduralPlacement {
|
||||
private boolean glow = false;
|
||||
|
||||
@Desc("The light-emitting block sprinkled among the tips when glow is true and no tip block is configured.")
|
||||
@RegistryListBlockType
|
||||
private String glowBlock = "minecraft:glowstone";
|
||||
|
||||
@Desc("Optional block for the budding base blob the shards grow from, e.g. minecraft:budding_amethyst or calcite. Ignored when basePalette is set. If unset, the base is built from the primary shard block.")
|
||||
@RegistryListBlockType
|
||||
private String baseBlock = "minecraft:budding_amethyst";
|
||||
|
||||
@Desc("A noise-driven palette for the budding base blob. When set this overrides the single baseBlock. Palette wins.")
|
||||
|
||||
@@ -55,12 +55,12 @@ public class IrisDepositGenerator {
|
||||
@Required
|
||||
@MinNumber(0)
|
||||
@MaxNumber(8192)
|
||||
@Desc("The minimum height this deposit can generate at")
|
||||
@Desc("The minimum height this deposit can generate at, in engine-local Y where 0 is the bottom of the dimension, not world Y.")
|
||||
private int minHeight = 1;
|
||||
@Required
|
||||
@MinNumber(0)
|
||||
@MaxNumber(8192)
|
||||
@Desc("The maximum height this deposit can generate at")
|
||||
@Desc("The maximum height this deposit can generate at, in engine-local Y where 0 is the bottom of the dimension, not world Y. Clump centers are additionally clamped to stay below the terrain surface.")
|
||||
private int maxHeight = 75;
|
||||
@Required
|
||||
@MinNumber(0)
|
||||
@@ -84,15 +84,15 @@ public class IrisDepositGenerator {
|
||||
private int minPerChunk = 0;
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("The change of the deposit spawning in a chunk")
|
||||
@Desc("The chance of the deposit spawning in a chunk")
|
||||
private double spawnChance = 1;
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("The change of the a clump spawning in a chunk")
|
||||
@Desc("The chance of each individual clump spawning in a chunk")
|
||||
private double perClumpSpawnChance = 1;
|
||||
@Required
|
||||
@ArrayType(min = 1, type = IrisBlockData.class)
|
||||
@Desc("The palette of blocks to be used in this deposit generator")
|
||||
@Desc("The palette of blocks to be used in this deposit generator. Each entry is picked uniformly; the per-entry weight field is ignored here.")
|
||||
private KList<IrisBlockData> palette = new KList<>();
|
||||
@MinNumber(1)
|
||||
@MaxNumber(64)
|
||||
|
||||
@@ -101,7 +101,7 @@ public class IrisDimension extends IrisRegistrant {
|
||||
private int logicalHeight = 256;
|
||||
@Desc("If set to true, Iris will remove chunks to allow visualizing cross sections of chunks easily")
|
||||
private boolean debugChunkCrossSections = false;
|
||||
@Desc("Vertically split up the biome palettes with 3 air blocks in between to visualize them")
|
||||
@Desc("Vertically split up the biome palettes with barrier blocks in between to visualize them. The gap size comes from explodeBiomePaletteSize.")
|
||||
private boolean explodeBiomePalettes = false;
|
||||
@Desc("Studio Mode for testing different parts of the world")
|
||||
private StudioMode studioMode = StudioMode.NORMAL;
|
||||
@@ -137,13 +137,13 @@ public class IrisDimension extends IrisRegistrant {
|
||||
private IrisGeneratorStyle regionStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("The placement style of land/sea")
|
||||
private IrisGeneratorStyle continentalStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("The placement style of biomes")
|
||||
@Desc("The noise style used to select land biomes within a region")
|
||||
private IrisGeneratorStyle landBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("The placement style of biomes")
|
||||
@Desc("The noise style used to select shore biomes within a region")
|
||||
private IrisGeneratorStyle shoreBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("The placement style of biomes")
|
||||
@Desc("The noise style used to select sea biomes within a region")
|
||||
private IrisGeneratorStyle seaBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("The placement style of biomes")
|
||||
@Desc("The noise style used to select cave biomes within a region")
|
||||
private IrisGeneratorStyle caveBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
|
||||
@Desc("Instead of filling objects with air, fills them with cobweb so you can see them")
|
||||
private boolean debugSmartBore = false;
|
||||
@@ -170,7 +170,7 @@ public class IrisDimension extends IrisRegistrant {
|
||||
@MaxNumber(16)
|
||||
@Desc("Minimum surface-support buffer, in blocks, applied to every surface object placement in this dimension. A placement may ask for more but never less.")
|
||||
private int objectSurfaceSupportBuffer = 2;
|
||||
@Desc("forceConvertTo320Height")
|
||||
@Desc("Unused. This field is not read by the engine.")
|
||||
private Boolean forceConvertTo320Height = false;
|
||||
@Desc("The world environment")
|
||||
private IrisEnvironment environment = IrisEnvironment.NORMAL;
|
||||
@@ -182,9 +182,9 @@ public class IrisDimension extends IrisRegistrant {
|
||||
@Required
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1024)
|
||||
@Desc("The fluid height for this dimension")
|
||||
@Desc("The fluid (sea level) height for this dimension, in world Y. Water fills every sea column up to this height, so 63 puts the water surface at world Y 63.")
|
||||
private int fluidHeight = 63;
|
||||
@Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.")
|
||||
@Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nfluidHeight stays in world Y: the engine converts it internally by subtracting the min height, so a fluid height of 63 shows up in the world at Y 63.")
|
||||
private IrisRange dimensionHeight = new IrisRange(-64, 320);
|
||||
@Desc("Define options for this dimension")
|
||||
private IrisDimensionTypeOptions dimensionOptions = new IrisDimensionTypeOptions();
|
||||
@@ -259,13 +259,13 @@ public class IrisDimension extends IrisRegistrant {
|
||||
private KList<IrisShapedGeneratorStyle> overlayNoise = new KList<>();
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(512)
|
||||
@Desc("The rock zoom mostly for zooming in on a wispy palette")
|
||||
@Desc("Unused. This field is not read by the engine; rock palette styling comes from rockPalette itself.")
|
||||
private double rockZoom = 5;
|
||||
@Desc("The palette of blocks for 'stone'")
|
||||
private IrisMaterialPalette rockPalette = new IrisMaterialPalette().qclear().qadd("stone");
|
||||
@Desc("The dimension fluid block palette used for ocean columns and cave aquifers.")
|
||||
private IrisMaterialPalette fluidPalette = new IrisMaterialPalette().qclear().qadd("water");
|
||||
@Desc("Prevent cartographers to generate explorer maps (Iris worlds only)\nONLY TOUCH IF YOUR SERVER CRASHES WHILE GENERATING EXPLORER MAPS")
|
||||
@Desc("Unused. This field is not read by the engine and no longer affects explorer maps.")
|
||||
private boolean disableExplorerMaps = false;
|
||||
@Desc("Collection of ores to be generated")
|
||||
@ArrayType(type = IrisOreGenerator.class, min = 1)
|
||||
@@ -282,7 +282,7 @@ public class IrisDimension extends IrisRegistrant {
|
||||
private KList<String> datapackImports = new KList<>();
|
||||
@MinNumber(0)
|
||||
@MaxNumber(318)
|
||||
@Desc("The Subterrain Fluid Layer Height")
|
||||
@Desc("The subterranean fluid (cave lava) layer height, in engine-local Y where 0 is the bottom of the dimension, not world Y. With the default -64..320 height, 8 means world Y -56.")
|
||||
private int caveLavaHeight = 8;
|
||||
@RegistryListFunction(ComponentFlagFunction.class)
|
||||
@ArrayType(type = String.class)
|
||||
|
||||
@@ -149,7 +149,7 @@ public class IrisEntity extends IrisRegistrant {
|
||||
@ArrayType(min = 1, type = IrisAttributeModifier.class)
|
||||
private KList<IrisAttributeModifier> attributes = new KList<>();
|
||||
|
||||
@Desc("Loot tables for drops")
|
||||
@Desc("Loot tables for drops. Only the tables list is honored for entities; mode and multiplier are ignored, and the tables replace vanilla drops entirely.")
|
||||
private IrisLootReference loot = new IrisLootReference();
|
||||
|
||||
@Desc("If specified, this entity will be leashed by this entity. I.e. THIS ENTITY Leashed by SPECIFIED. This has no effect on EnderDragons, Withers, Players, or Bats.Non-living entities excluding leashes will not persist as leashholders.")
|
||||
|
||||
@@ -25,6 +25,7 @@ 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.RegistryListBlockType;
|
||||
import art.arcane.iris.engine.object.annotations.RegistryListResource;
|
||||
import art.arcane.iris.engine.object.annotations.Snippet;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
@@ -270,6 +271,7 @@ public class IrisFloatingChildBiomes implements IRare {
|
||||
private Integer localFluidHeight = null;
|
||||
|
||||
@Desc("Block used for the internal water pool when localFluidHeight is positive.")
|
||||
@RegistryListBlockType
|
||||
private String fluidBlock = "minecraft:water";
|
||||
|
||||
@Desc("When true, the target biome's decorators apply to the island's top surface.")
|
||||
|
||||
@@ -23,6 +23,7 @@ import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
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.RegistryListBlockType;
|
||||
import art.arcane.iris.engine.object.annotations.Required;
|
||||
import art.arcane.iris.engine.object.annotations.Snippet;
|
||||
import art.arcane.iris.engine.object.formation.FormationGenerator;
|
||||
@@ -96,12 +97,14 @@ public class IrisFormation implements IrisProceduralPlacement {
|
||||
|
||||
@Required
|
||||
@Desc("The main rock block, e.g. minecraft:stone. Ignored when blockPalette is set.")
|
||||
@RegistryListBlockType
|
||||
private String block = "minecraft:stone";
|
||||
|
||||
@Desc("A noise-driven palette for the main rock body. When set this overrides the single block, letting the body mix blocks by noise.")
|
||||
private IrisMaterialPalette blockPalette = null;
|
||||
|
||||
@Desc("Optional caprock block placed on the top crown of the formation (and the wide overhanging cap for HOODOO). Ignored when capPalette is set. When null and capPalette is unset, the formation uses its main rock everywhere.")
|
||||
@RegistryListBlockType
|
||||
private String capBlock = null;
|
||||
|
||||
@Desc("A noise-driven palette for the caprock. When set this overrides the single capBlock.")
|
||||
|
||||
@@ -23,6 +23,7 @@ import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
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.RegistryListBlockType;
|
||||
import art.arcane.iris.engine.object.annotations.Required;
|
||||
import art.arcane.iris.engine.object.annotations.Snippet;
|
||||
import art.arcane.iris.engine.object.fungi.FungusGenerator;
|
||||
@@ -88,6 +89,7 @@ public class IrisFungus implements IrisProceduralPlacement {
|
||||
|
||||
@Required
|
||||
@Desc("The stem block, e.g. minecraft:mushroom_stem. Ignored when stemPalette is set.")
|
||||
@RegistryListBlockType
|
||||
private String stem = "minecraft:mushroom_stem";
|
||||
|
||||
@Desc("A noise-driven palette for the stem. When set this overrides the single stem block, letting the stem mix blocks by noise.")
|
||||
@@ -95,6 +97,7 @@ public class IrisFungus implements IrisProceduralPlacement {
|
||||
|
||||
@Required
|
||||
@Desc("The cap block, e.g. minecraft:red_mushroom_block. Ignored when capPalette is set.")
|
||||
@RegistryListBlockType
|
||||
private String cap = "minecraft:red_mushroom_block";
|
||||
|
||||
@Desc("A noise-driven palette for the cap. When set this overrides the single cap block, letting the cap mix blocks by noise.")
|
||||
@@ -157,6 +160,7 @@ public class IrisFungus implements IrisProceduralPlacement {
|
||||
private double capOverhang = 2;
|
||||
|
||||
@Desc("Optional block forming the gill layer on the underside of the cap (gills or a glow layer), e.g. minecraft:brown_mushroom_block or minecraft:shroomlight. Ignored when gillPalette is set or left null.")
|
||||
@RegistryListBlockType
|
||||
private String gillBlock = null;
|
||||
|
||||
@Desc("A noise-driven palette for the underside gill layer. When set this overrides the single gillBlock.")
|
||||
@@ -168,6 +172,7 @@ public class IrisFungus implements IrisProceduralPlacement {
|
||||
private double gillChance = 0.85;
|
||||
|
||||
@Desc("Optional block speckled across the top of the cap by noise (white toadstool dots, warts, glowing spots), e.g. minecraft:bone_block or minecraft:white_concrete. Ignored when spotPalette is set or left null.")
|
||||
@RegistryListBlockType
|
||||
private String spotBlock = null;
|
||||
|
||||
@Desc("A noise-driven palette for the cap top spots. When set this overrides the single spotBlock.")
|
||||
|
||||
@@ -58,7 +58,7 @@ public class IrisGeneratorStyle {
|
||||
new ConcurrentLinkedHashMap.Builder<GeneratorCacheKey, CNG>()
|
||||
.maximumWeightedCapacity(GENERATOR_CACHE_SIZE)
|
||||
.build();
|
||||
@Desc("The chance is 1 in CHANCE per interval")
|
||||
@Desc("The base noise style. Used when neither expression nor imageMap is set; a failed expression also falls back to this style.")
|
||||
private NoiseStyle style = NoiseStyle.FLAT;
|
||||
|
||||
@Desc("If set above 0, this style will be cellularized")
|
||||
|
||||
@@ -4,12 +4,27 @@ import art.arcane.iris.engine.object.annotations.Desc;
|
||||
|
||||
@Desc("Controls whether a configured native jigsaw projects its start onto a Minecraft heightmap.")
|
||||
public enum IrisJigsawHeightmap {
|
||||
@Desc("Keeps the registered structure's own heightmap projection exactly as the vanilla, datapack, or mod definition declares it.")
|
||||
SOURCE,
|
||||
|
||||
@Desc("Removes heightmap projection entirely, so the start piece lands at the structure's literal start height instead of following terrain.")
|
||||
NONE,
|
||||
|
||||
@Desc("Drops the start piece onto the terrain top counting water as solid, so ocean starts sit on the water surface.")
|
||||
WORLD_SURFACE_WG,
|
||||
|
||||
@Desc("Identical to WORLD_SURFACE_WG in Iris worlds: the start piece rides the terrain top including water.")
|
||||
WORLD_SURFACE,
|
||||
|
||||
@Desc("Drops the start piece onto solid terrain, ignoring water, so ocean starts sit on the seabed.")
|
||||
OCEAN_FLOOR_WG,
|
||||
|
||||
@Desc("Identical to OCEAN_FLOOR_WG in Iris worlds: the start piece sits on solid ground beneath any water.")
|
||||
OCEAN_FLOOR,
|
||||
|
||||
@Desc("Behaves like WORLD_SURFACE in Iris worlds; the start piece sits on the terrain top including water.")
|
||||
MOTION_BLOCKING,
|
||||
|
||||
@Desc("Behaves like WORLD_SURFACE in Iris worlds; Iris height queries have no leaf layer to skip.")
|
||||
MOTION_BLOCKING_NO_LEAVES
|
||||
}
|
||||
|
||||
@@ -4,7 +4,12 @@ import art.arcane.iris.engine.object.annotations.Desc;
|
||||
|
||||
@Desc("Controls waterlogging behavior for a configured native jigsaw.")
|
||||
public enum IrisJigsawLiquidSettings {
|
||||
@Desc("Keeps the registered structure's own liquid setting; vanilla defaults to applying waterlogging when the definition omits it.")
|
||||
SOURCE,
|
||||
|
||||
@Desc("Places pieces dry: existing water is displaced and waterloggable blocks stay unwaterlogged, leaving air-filled interiors underwater.")
|
||||
IGNORE_WATERLOGGING,
|
||||
|
||||
@Desc("Re-applies pre-existing water into placed waterloggable blocks and floods from neighboring sources, so underwater pieces come out waterlogged.")
|
||||
APPLY_WATERLOGGING
|
||||
}
|
||||
|
||||
@@ -4,11 +4,22 @@ import art.arcane.iris.engine.object.annotations.Desc;
|
||||
|
||||
@Desc("The orientation-independent connector shape represented by a planar jigsaw workcell.")
|
||||
public enum IrisJigsawWorkcellArchetype {
|
||||
@Desc("Pieces with no horizontal connectors: sealed filler cells that terminate nothing and are excluded from the default piece pool.")
|
||||
BLANK(0),
|
||||
|
||||
@Desc("One-connector dead-end pieces; the Studio bay seeds a single north connector and new projects mark these as terminal caps.")
|
||||
END(1),
|
||||
|
||||
@Desc("Two-connector pass-through pieces with openings on opposite sides; the Studio bay seeds north and south connectors.")
|
||||
STRAIGHT(5),
|
||||
|
||||
@Desc("Two-connector right-angle pieces with openings on adjacent sides; the Studio bay seeds north and east connectors.")
|
||||
CORNER(3),
|
||||
|
||||
@Desc("Three-connector junction pieces; the Studio bay seeds north, east, and west connectors.")
|
||||
TEE(11),
|
||||
|
||||
@Desc("Four-connector pieces open on all sides; new projects use this archetype as the structure's start piece.")
|
||||
CROSS(15);
|
||||
|
||||
private final int canonicalMask;
|
||||
|
||||
@@ -71,7 +71,7 @@ public class IrisLoot {
|
||||
public static final int MAX_AMOUNT = 64;
|
||||
|
||||
private final transient AtomicCache<DyeColor> dyeColorResolved = new AtomicCache<>();
|
||||
@Desc("The target inventory slot types to fill this loot with")
|
||||
@Desc("The target inventory slot types to fill this loot with. World generation only ever fills STORAGE; the other values are inert during worldgen.")
|
||||
private InventorySlotType slotTypes = InventorySlotType.STORAGE;
|
||||
@MinNumber(1)
|
||||
@Desc("The sub rarity of this loot. Calculated after this loot table has been picked.")
|
||||
|
||||
@@ -42,7 +42,7 @@ public class IrisLootReference {
|
||||
public static final double MAX_MULTIPLIER = 16D;
|
||||
|
||||
private final transient AtomicCache<KList<IrisLootTable>> tt = new AtomicCache<>();
|
||||
@Desc("Add = add on top of parent tables, Replace = clear first then add these. Clear = Remove all and dont add loot from this or parent.")
|
||||
@Desc("ADD = add on top of parent tables. REPLACE and CLEAR both clear parent tables first, then add these. FALLBACK = only used when nothing else defined a table.")
|
||||
private IrisLootMode mode = IrisLootMode.ADD;
|
||||
@RegistryListResource(IrisLootTable.class)
|
||||
@ArrayType(min = 1, type = String.class)
|
||||
|
||||
@@ -151,7 +151,7 @@ public class IrisObjectPlacement {
|
||||
private boolean bore = false;
|
||||
@Desc("Use a generator to warp the field of coordinates. Using simplex for example would make a square placement warp like a flag")
|
||||
private IrisGeneratorStyle warp = new IrisGeneratorStyle(NoiseStyle.FLAT);
|
||||
@Desc("If the place mode is set to CENTER_HEIGHT_RIGID and you have an X/Z translation, Turning on translate center will also translate the center height check.")
|
||||
@Desc("Unused. This field is not read by the placement engine.")
|
||||
private boolean translateCenter = false;
|
||||
@Desc("The placement mode")
|
||||
private ObjectPlaceMode mode = ObjectPlaceMode.CENTER_HEIGHT;
|
||||
|
||||
@@ -21,6 +21,8 @@ package art.arcane.iris.engine.object;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
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.volmlib.util.math.RNG;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -32,18 +34,20 @@ import lombok.experimental.Accessors;
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Desc("Ore Layer")
|
||||
@Desc("Scatters single ore blocks through terrain by sampling a noise field at every block in a vertical band. Defined on dimensions, regions, or biomes; the most specific level wins.")
|
||||
@Data
|
||||
public class IrisOreGenerator {
|
||||
@Desc("The palette of 'ore' generated")
|
||||
@Desc("The ore blocks to place. An empty palette generates nothing, so this must be set for the generator to have any effect.")
|
||||
private IrisMaterialPalette palette = new IrisMaterialPalette().qclear();
|
||||
@Desc("The generator style for the 'ore'")
|
||||
@Desc("The noise style sampled per block to decide where ore appears. STATIC gives independent random speckle; smoother styles give veiny clusters.")
|
||||
private IrisGeneratorStyle chanceStyle = new IrisGeneratorStyle(NoiseStyle.STATIC);
|
||||
@Desc("Will ores generate on the surface of the terrain layer")
|
||||
@Desc("When true this generator also replaces surface-layer blocks (it runs before layers and fluid); when false it only replaces underground rock.")
|
||||
private boolean generateSurface = false;
|
||||
@Desc("Threshold for rate of generation")
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("Noise cutoff: a block becomes ore when the sampled noise is at or below this value, so higher means more ore. 0 disables, 1 replaces everything in range.")
|
||||
private double threshold = 0.5;
|
||||
@Desc("Height limit (min, max)")
|
||||
@Desc("Vertical band (min, max) this ore can generate in, in engine-local Y where 0 is the bottom of the dimension, not world Y.")
|
||||
private IrisRange range = new IrisRange(30, 80);
|
||||
|
||||
private transient AtomicCache<CNG> chanceCache = new AtomicCache<>();
|
||||
|
||||
@@ -24,6 +24,7 @@ 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.RegistryListBlockType;
|
||||
import art.arcane.iris.engine.object.annotations.Required;
|
||||
import art.arcane.iris.engine.object.annotations.Snippet;
|
||||
import art.arcane.iris.engine.object.tree.ProceduralTreeGenerator;
|
||||
@@ -93,6 +94,7 @@ public class IrisProceduralTree implements IrisProceduralPlacement {
|
||||
|
||||
@Required
|
||||
@Desc("The trunk (log) block, e.g. minecraft:oak_log. Ignored when trunkPalette is set.")
|
||||
@RegistryListBlockType
|
||||
private String trunk = "minecraft:oak_log";
|
||||
|
||||
@Desc("A noise-driven palette for the trunk. When set this overrides the single trunk block, letting the trunk mix blocks by noise.")
|
||||
@@ -100,6 +102,7 @@ public class IrisProceduralTree implements IrisProceduralPlacement {
|
||||
|
||||
@Required
|
||||
@Desc("The leaf block, e.g. minecraft:oak_leaves. Ignored when leavesPalette is set.")
|
||||
@RegistryListBlockType
|
||||
private String leaves = "minecraft:oak_leaves";
|
||||
|
||||
@Desc("A noise-driven palette for the leaves. When set this overrides the single leaf block, letting the canopy mix leaf (or other) blocks by noise. Only blocks that are actually leaves get vanilla decay distances.")
|
||||
@@ -188,6 +191,7 @@ public class IrisProceduralTree implements IrisProceduralPlacement {
|
||||
private int azimuthWhorlCount = 5;
|
||||
|
||||
@Desc("Optional accent leaf block scattered through the canopy (blossoms, shroomlight).")
|
||||
@RegistryListBlockType
|
||||
private String secondaryLeaves = null;
|
||||
|
||||
@MinNumber(0)
|
||||
@@ -203,6 +207,7 @@ public class IrisProceduralTree implements IrisProceduralPlacement {
|
||||
private IrisMaterialPalette secondaryLeavesPalette = null;
|
||||
|
||||
@Desc("Optional secondary trunk block used over a height band (for color-banded trunks). Ignored when secondaryTrunkPalette is set.")
|
||||
@RegistryListBlockType
|
||||
private String secondaryTrunk = null;
|
||||
|
||||
@Desc("A noise-driven palette for the secondary trunk band. When set this overrides the single secondaryTrunk block.")
|
||||
|
||||
@@ -90,7 +90,6 @@ public class IrisRegion extends IrisRegistrant implements IRare {
|
||||
@ArrayType(min = 1, type = IrisBlockDrops.class)
|
||||
@Desc("Define custom block drops for this region")
|
||||
private KList<IrisBlockDrops> blockDrops = new KList<>();
|
||||
@RegistryListResource(IrisSpawner.class)
|
||||
@ArrayType(min = 1, type = IrisObjectPlacement.class)
|
||||
@Desc("Objects define what schematics (iob files) iris will place in this region")
|
||||
private KList<IrisObjectPlacement> objects = new KList<>();
|
||||
@@ -139,7 +138,7 @@ public class IrisRegion extends IrisRegistrant implements IRare {
|
||||
private KList<String> shoreBiomes = new KList<>();
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@ArrayType(min = 1, type = String.class)
|
||||
@Desc("A list of root-level biomes in this region. Don't specify child biomes of other biomes here. Just the root parents.")
|
||||
@Desc("A list of root-level cave biomes in this region, used for carved cave interiors. Don't specify child biomes of other biomes here. Just the root parents.")
|
||||
private KList<String> caveBiomes = new KList<>();
|
||||
@ArrayType(min = 1, type = IrisDepositGenerator.class)
|
||||
@Desc("Define regional deposit generators that add onto the global deposit generators")
|
||||
@@ -147,9 +146,9 @@ public class IrisRegion extends IrisRegistrant implements IRare {
|
||||
@ArrayType(min = 1, type = IrisDepositVariant.class)
|
||||
@Desc("Deposit ore remap rules scoped to this region. Each entry declares a vertical band and a source->replacement block id map. Applied after biome rules but before dimension rules; first matching region rule wins.")
|
||||
private KList<IrisDepositVariant> depositVariants = new KList<>();
|
||||
@Desc("The style of rivers")
|
||||
@Desc("Unused. This field is not read by the engine; rivers are not generated from it.")
|
||||
private IrisGeneratorStyle riverStyle = NoiseStyle.VASCULAR_THIN.style().zoomed(7.77);
|
||||
@Desc("The style of lakes")
|
||||
@Desc("Unused. This field is not read by the engine; lakes are not generated from it.")
|
||||
private IrisGeneratorStyle lakeStyle = NoiseStyle.CELLULAR_IRIS_THICK.style();
|
||||
@Desc("A color for visualizing this region with a color. I.e. #F13AF5. This will show up on the map.")
|
||||
private String color = null;
|
||||
|
||||
@@ -24,6 +24,7 @@ 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.RegistryListBlockType;
|
||||
import art.arcane.iris.engine.object.annotations.Required;
|
||||
import art.arcane.iris.engine.object.annotations.Snippet;
|
||||
import art.arcane.iris.engine.object.ruin.RuinGenerator;
|
||||
@@ -89,6 +90,7 @@ public class IrisRuin implements IrisProceduralPlacement {
|
||||
|
||||
@Required
|
||||
@Desc("The primary structural block, e.g. minecraft:cobblestone or minecraft:stone_bricks. Ignored when blockPalette is set. This is the bulk material of the ruin before weathering swaps some of it out.")
|
||||
@RegistryListBlockType
|
||||
private String block = "minecraft:cobblestone";
|
||||
|
||||
@Desc("A noise-driven palette for the primary block. When set this overrides the single block, letting the structure mix materials by noise. Palette wins over the block string via IrisProceduralBlocks.resolve.")
|
||||
@@ -125,6 +127,7 @@ public class IrisRuin implements IrisProceduralPlacement {
|
||||
private IrisMaterialPalette weatheringPalette = null;
|
||||
|
||||
@Desc("A single weathered block id used when weatheringPalette is not set, e.g. minecraft:mossy_cobblestone. Applied to a noise-selected fraction of the structure, biased toward the lower rows.")
|
||||
@RegistryListBlockType
|
||||
private String weatheredBlock = "minecraft:mossy_cobblestone";
|
||||
|
||||
@MinNumber(0)
|
||||
|
||||
@@ -21,6 +21,7 @@ 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.RegistryListBlockType;
|
||||
import art.arcane.iris.engine.object.annotations.Required;
|
||||
import art.arcane.iris.engine.object.annotations.Snippet;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -40,6 +41,7 @@ public class IrisRuinDecorator {
|
||||
|
||||
@Required
|
||||
@Desc("The block id to place, e.g. minecraft:moss_carpet or minecraft:vine. Ignored when palette is set.")
|
||||
@RegistryListBlockType
|
||||
private String block = "";
|
||||
|
||||
@Desc("A noise-driven palette for this decorator. When set this overrides the single block, letting the accent mix blocks by noise. Resolved through IrisProceduralBlocks.resolve so the palette wins over the string.")
|
||||
|
||||
@@ -54,7 +54,7 @@ public class IrisSpawner extends IrisRegistrant {
|
||||
@Desc("The block of 24 hour time to contain this spawn in.")
|
||||
private IrisTimeBlock timeBlock = new IrisTimeBlock();
|
||||
|
||||
@Desc("The block of 24 hour time to contain this spawn in.")
|
||||
@Desc("The weather condition required for this spawner to fire.")
|
||||
private IrisWeather weather = IrisWeather.ANY;
|
||||
|
||||
@Desc("The maximum rate this spawner can fire")
|
||||
|
||||
@@ -24,6 +24,7 @@ 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.RegistryListResource;
|
||||
import art.arcane.iris.engine.object.annotations.RegistryListVanillaStructure;
|
||||
import art.arcane.iris.engine.object.annotations.Required;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -92,6 +93,7 @@ public class IrisStructure extends IrisRegistrant {
|
||||
@Desc("Loot tables applied to containers placed by this structure's pieces.")
|
||||
private KList<String> loot = new KList<>();
|
||||
|
||||
@RegistryListVanillaStructure
|
||||
@Desc("If this structure was generated by importing a vanilla or datapack structure, this is that structure's key (provenance). Empty for hand-authored structures.")
|
||||
private String vanillaSource = "";
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ 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.RegistryListResource;
|
||||
import art.arcane.iris.engine.object.annotations.RegistryListStructure;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -94,8 +95,9 @@ public class IrisStructurePlacement {
|
||||
@Desc("Vertical anchor policy. LEGACY preserves the underground boolean. Cave modes search existing carved-space data and implicitly behave as underground placements.")
|
||||
private IrisStructureAnchorMode anchor = IrisStructureAnchorMode.LEGACY;
|
||||
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@ArrayType(type = String.class, min = 1)
|
||||
@Desc("Optional cave-biome allowlist for cave anchor modes. Empty accepts the cave biome resolved at the selected anchor.")
|
||||
@Desc("Optional cave-biome allowlist for cave anchor modes. Empty accepts the cave biome resolved at the selected anchor. Entries match by load key, including prefix matches.")
|
||||
private KList<String> caveBiomes = new KList<>();
|
||||
|
||||
@MinNumber(1)
|
||||
|
||||
@@ -4,9 +4,16 @@ import art.arcane.iris.engine.object.annotations.Desc;
|
||||
|
||||
@Desc("Controls how terrain is integrated with a structure.")
|
||||
public enum IrisStructureTerrainMode {
|
||||
@Desc("Replays the registered native structure's own authored terrain adaptation (beard surface fitting, BURY/ENCAPSULATE fill, legacy template-air clearing). Does nothing for editable Iris structures, which have no source adaptation to replay.")
|
||||
SOURCE,
|
||||
|
||||
@Desc("Disables all terrain integration. Pieces are placed into completely unmodified terrain and may end up embedded in solid rock or floating over dips.")
|
||||
PRESERVE,
|
||||
|
||||
@Desc("Clears one straight axis-aligned box around the assembled pieces, expanded by horizontalPadding, ceilingPadding, and floorPadding. Ignores the carve shape and erosion settings.")
|
||||
BORE,
|
||||
|
||||
@Desc("Clears the padded envelope using the configured carve shape (BOX, ROUNDED, or noise-eroded ERODED) so the cavity hugs the assembled pieces instead of a straight box.")
|
||||
FORCE_CARVE,
|
||||
|
||||
@Desc("Raises surface terrain from processed solid rigid-template foundations at or below each authored ground plane with a 12-block falloff, even when the registered structure has no terrain adaptation. Existing higher terrain and authored air remain untouched.")
|
||||
|
||||
@@ -41,7 +41,7 @@ public class IrisTree {
|
||||
@ArrayType(min = 1, type = String.class)
|
||||
private KList<String> treeTypes = new KList<>();
|
||||
|
||||
@Desc("If enabled, overrides any TreeType")
|
||||
@Desc("Unused. This flag is not read by the tree matcher; use treeTypes to control which TreeTypes match.")
|
||||
private boolean anyTree = false;
|
||||
|
||||
@Required
|
||||
@@ -49,7 +49,7 @@ public class IrisTree {
|
||||
@ArrayType(min = 1, type = IrisTreeSize.class)
|
||||
private KList<IrisTreeSize> sizes = new KList<>();
|
||||
|
||||
@Desc("If enabled, overrides trees of any size")
|
||||
@Desc("Unused. This flag is not read by the tree matcher; use sizes to control which sapling sizes match.")
|
||||
private boolean anySize;
|
||||
|
||||
public boolean matches(IrisTreeSize size, TreeType type) {
|
||||
|
||||
@@ -21,6 +21,7 @@ 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.RegistryListBlockType;
|
||||
import art.arcane.iris.engine.object.annotations.Required;
|
||||
import art.arcane.iris.engine.object.annotations.Snippet;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -40,6 +41,7 @@ public class IrisTreeDecorator {
|
||||
|
||||
@Required
|
||||
@Desc("The block id to place, e.g. minecraft:magma_block. Ignored when palette is set.")
|
||||
@RegistryListBlockType
|
||||
private String block = "";
|
||||
|
||||
@Desc("A noise-driven palette for this decorator. When set this overrides the single block.")
|
||||
|
||||
@@ -22,7 +22,7 @@ import art.arcane.iris.engine.object.annotations.Desc;
|
||||
|
||||
@Desc("Sapling override object picking options")
|
||||
public enum IrisTreeModes {
|
||||
@Desc("Check biome, then region, then dimension, pick the first one that has options")
|
||||
@Desc("Check biome, then region, pick the first one that has options. Dimension placements are not consulted.")
|
||||
FIRST,
|
||||
|
||||
@Desc("Check biome, regions, and dimensions, and pick any option from the total list")
|
||||
|
||||
@@ -20,6 +20,7 @@ package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import art.arcane.iris.engine.object.annotations.MinNumber;
|
||||
import art.arcane.iris.engine.object.annotations.RegistryListBlockType;
|
||||
import art.arcane.iris.engine.object.annotations.Required;
|
||||
import art.arcane.iris.engine.object.annotations.Snippet;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -36,6 +37,7 @@ import lombok.experimental.Accessors;
|
||||
public class IrisTreeSecondaryLeaf {
|
||||
@Required
|
||||
@Desc("The block id to scatter, e.g. minecraft:shroomlight")
|
||||
@RegistryListBlockType
|
||||
private String block = "";
|
||||
|
||||
@MinNumber(0)
|
||||
|
||||
@@ -25,14 +25,31 @@ import art.arcane.iris.engine.platform.studio.generators.ObjectStudioGenerator;
|
||||
|
||||
@Desc("Represents a studio mode")
|
||||
public enum StudioMode {
|
||||
@Desc("Installs no studio generator; the dimension generates normally.")
|
||||
NORMAL,
|
||||
|
||||
@Desc("Debug layout: every biome in the dimension on a square grid, one chunk per biome cell, barrier floor past the last biome. Bukkit studio worlds only.")
|
||||
BIOME_BUFFET_1x1,
|
||||
|
||||
@Desc("Debug layout: every biome on a square grid with 3x3-chunk cells per biome, barrier floor past the last biome. Bukkit studio worlds only.")
|
||||
BIOME_BUFFET_3x3,
|
||||
|
||||
@Desc("Debug layout: every biome on a square grid with 5x5-chunk cells per biome, barrier floor past the last biome. Bukkit studio worlds only.")
|
||||
BIOME_BUFFET_5x5,
|
||||
|
||||
@Desc("Debug layout: every biome on a square grid with 9x9-chunk cells per biome, barrier floor past the last biome. Bukkit studio worlds only.")
|
||||
BIOME_BUFFET_9x9,
|
||||
|
||||
@Desc("Debug layout: every biome on a square grid with 18x18-chunk cells per biome, barrier floor past the last biome. Bukkit studio worlds only.")
|
||||
BIOME_BUFFET_18x18,
|
||||
|
||||
@Desc("Debug layout: every biome on a square grid with 36x36-chunk cells per biome, barrier floor past the last biome. Bukkit studio worlds only.")
|
||||
BIOME_BUFFET_36x36,
|
||||
|
||||
@Desc("Not implemented: currently generates exactly like NORMAL.")
|
||||
REGION_BUFFET,
|
||||
|
||||
@Desc("Replaces terrain with the object studio: a flat polished-deepslate floor laying every pack object out on framed, end-rod-marked grid plinths. Bukkit studio worlds only.")
|
||||
OBJECT_BUFFET;
|
||||
|
||||
public void inject(BukkitChunkGenerator c) {
|
||||
|
||||
+16
@@ -43,4 +43,20 @@ public class IrisCarveModifierFluidIntentTest {
|
||||
assertTrue(IrisCarveModifier.usesDefaultLava(18, 18));
|
||||
assertFalse(IrisCarveModifier.usesDefaultLava(18, 19));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unsupportedSurfaceOreIsRemovedOnlyOverCarvedAir() {
|
||||
PlatformBlockState ore = mock(PlatformBlockState.class);
|
||||
PlatformBlockState ordinarySurface = mock(PlatformBlockState.class);
|
||||
PlatformBlockState air = mock(PlatformBlockState.class);
|
||||
PlatformBlockState solid = mock(PlatformBlockState.class);
|
||||
when(ore.isOre()).thenReturn(true);
|
||||
when(ordinarySurface.isOre()).thenReturn(false);
|
||||
when(air.isSolid()).thenReturn(false);
|
||||
when(solid.isSolid()).thenReturn(true);
|
||||
|
||||
assertTrue(IrisCarveModifier.isUnsupportedSurfaceOre(ore, air));
|
||||
assertFalse(IrisCarveModifier.isUnsupportedSurfaceOre(ore, solid));
|
||||
assertFalse(IrisCarveModifier.isUnsupportedSurfaceOre(ordinarySurface, air));
|
||||
}
|
||||
}
|
||||
|
||||
+11
-36
@@ -1,10 +1,7 @@
|
||||
package art.arcane.iris.engine.modifier;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
@@ -15,32 +12,14 @@ import java.util.Set;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class IrisCarveModifierZoneParityTest {
|
||||
private static Constructor<?> columnMaskConstructor;
|
||||
private static Method addMethod;
|
||||
private static Method nextSetBitMethod;
|
||||
private static Method clearMethod;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() throws Exception {
|
||||
Class<?> columnMaskClass = Class.forName("art.arcane.iris.engine.modifier.IrisCarveModifier$ColumnMask");
|
||||
columnMaskConstructor = columnMaskClass.getDeclaredConstructor();
|
||||
addMethod = columnMaskClass.getDeclaredMethod("add", int.class);
|
||||
nextSetBitMethod = columnMaskClass.getDeclaredMethod("nextSetBit", int.class);
|
||||
clearMethod = columnMaskClass.getDeclaredMethod("clear");
|
||||
columnMaskConstructor.setAccessible(true);
|
||||
addMethod.setAccessible(true);
|
||||
nextSetBitMethod.setAccessible(true);
|
||||
clearMethod.setAccessible(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void randomColumnZonesMatchLegacySortedResolver() throws Exception {
|
||||
Object columnMask = columnMaskConstructor.newInstance();
|
||||
public void randomColumnZonesMatchLegacySortedResolver() {
|
||||
CarveColumnMask columnMask = new CarveColumnMask();
|
||||
Random random = new Random(913_447L);
|
||||
int maxHeight = 320;
|
||||
|
||||
for (int scenario = 0; scenario < 400; scenario++) {
|
||||
clearMethod.invoke(columnMask);
|
||||
columnMask.clear();
|
||||
|
||||
int sampleSize = 1 + random.nextInt(180);
|
||||
Set<Integer> uniqueHeights = new HashSet<>();
|
||||
@@ -50,7 +29,7 @@ public class IrisCarveModifierZoneParityTest {
|
||||
|
||||
int[] heights = toIntArray(uniqueHeights);
|
||||
for (int index = 0; index < heights.length; index++) {
|
||||
addMethod.invoke(columnMask, heights[index]);
|
||||
columnMask.add(heights[index]);
|
||||
}
|
||||
|
||||
List<String> expectedZones = legacyZones(heights, maxHeight);
|
||||
@@ -60,8 +39,8 @@ public class IrisCarveModifierZoneParityTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void edgeColumnsMatchLegacySortedResolver() throws Exception {
|
||||
Object columnMask = columnMaskConstructor.newInstance();
|
||||
public void edgeColumnsMatchLegacySortedResolver() {
|
||||
CarveColumnMask columnMask = new CarveColumnMask();
|
||||
int maxHeight = 320;
|
||||
int[][] scenarios = new int[][]{
|
||||
{-10, -1, 0, 1, 2, 5, 6, 9, 10, 11, 12, 200, 201, 205},
|
||||
@@ -71,10 +50,10 @@ public class IrisCarveModifierZoneParityTest {
|
||||
};
|
||||
|
||||
for (int scenario = 0; scenario < scenarios.length; scenario++) {
|
||||
clearMethod.invoke(columnMask);
|
||||
columnMask.clear();
|
||||
int[] heights = Arrays.copyOf(scenarios[scenario], scenarios[scenario].length);
|
||||
for (int index = 0; index < heights.length; index++) {
|
||||
addMethod.invoke(columnMask, heights[index]);
|
||||
columnMask.add(heights[index]);
|
||||
}
|
||||
|
||||
List<String> expectedZones = legacyZones(heights, maxHeight);
|
||||
@@ -131,9 +110,9 @@ public class IrisCarveModifierZoneParityTest {
|
||||
return zones;
|
||||
}
|
||||
|
||||
private List<String> bitsetZones(Object columnMask, int maxHeight) throws Exception {
|
||||
private List<String> bitsetZones(CarveColumnMask columnMask, int maxHeight) {
|
||||
List<String> zones = new ArrayList<>();
|
||||
int firstHeight = nextSetBit(columnMask, 0);
|
||||
int firstHeight = columnMask.nextSetBit(0);
|
||||
if (firstHeight < 0) {
|
||||
return zones;
|
||||
}
|
||||
@@ -159,7 +138,7 @@ public class IrisCarveModifierZoneParityTest {
|
||||
}
|
||||
}
|
||||
|
||||
y = nextSetBit(columnMask, y + 1);
|
||||
y = columnMask.nextSetBit(y + 1);
|
||||
}
|
||||
|
||||
if (isValidZone(floor, ceiling, maxHeight)) {
|
||||
@@ -169,10 +148,6 @@ public class IrisCarveModifierZoneParityTest {
|
||||
return zones;
|
||||
}
|
||||
|
||||
private int nextSetBit(Object columnMask, int fromBit) throws Exception {
|
||||
return (Integer) nextSetBitMethod.invoke(columnMask, fromBit);
|
||||
}
|
||||
|
||||
private boolean isValidZone(int floor, int ceiling, int maxHeight) {
|
||||
return floor < ceiling
|
||||
&& floor >= 0
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package art.arcane.iris.engine.modifier;
|
||||
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisCarveScratchTest {
|
||||
@Test
|
||||
public void wallBufferResizesAndReplacesWithoutLosingEntries() {
|
||||
CarveWallBuffer buffer = new CarveWallBuffer(2);
|
||||
Map<String, MatterCavern> expected = new HashMap<>();
|
||||
for (int index = 0; index < 24; index++) {
|
||||
int x = index & 15;
|
||||
int y = 20 + index;
|
||||
int z = (index * 3) & 15;
|
||||
MatterCavern cavern = new MatterCavern(true, "cave-" + index, (byte) 0);
|
||||
buffer.put(x, y, z, cavern);
|
||||
expected.put(key(x, y, z), cavern);
|
||||
}
|
||||
|
||||
MatterCavern replacement = new MatterCavern(true, "replacement", (byte) 0);
|
||||
buffer.put(5, 25, 15, replacement);
|
||||
expected.put(key(5, 25, 15), replacement);
|
||||
|
||||
Map<String, MatterCavern> actual = new HashMap<>();
|
||||
buffer.forEach((x, y, z, cavern) -> actual.put(key(x, y, z), cavern));
|
||||
assertEquals(expected.keySet(), actual.keySet());
|
||||
for (Map.Entry<String, MatterCavern> entry : expected.entrySet()) {
|
||||
assertSame(entry.getValue(), actual.get(entry.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resetClearsReusableState() {
|
||||
IrisCarveScratch scratch = new IrisCarveScratch();
|
||||
MatterCavern cavern = new MatterCavern(true, "cave", (byte) 0);
|
||||
scratch.columnMasks[0].add(12);
|
||||
scratch.boundaryMasks[0].add(13);
|
||||
scratch.boundaryCaverns[0] = cavern;
|
||||
scratch.walls.put(1, 12, 2, cavern);
|
||||
scratch.customBiomeCache.put("cave", null);
|
||||
scratch.customCaveBiomePresent = true;
|
||||
|
||||
scratch.reset();
|
||||
|
||||
assertTrue(scratch.columnMasks[0].isEmpty());
|
||||
assertTrue(scratch.boundaryMasks[0].isEmpty());
|
||||
assertNull(scratch.boundaryCaverns[0]);
|
||||
assertTrue(scratch.customBiomeCache.isEmpty());
|
||||
assertFalse(scratch.customCaveBiomePresent);
|
||||
int[] wallCount = new int[1];
|
||||
scratch.walls.forEach((x, y, z, value) -> wallCount[0]++);
|
||||
assertEquals(0, wallCount[0]);
|
||||
}
|
||||
|
||||
private static String key(int x, int y, int z) {
|
||||
return x + ":" + y + ":" + z;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user