mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-05-19 08:00:18 +00:00
floatt
This commit is contained in:
@@ -46,7 +46,6 @@ import org.bukkit.inventory.ItemStack;
|
|||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
public interface INMSBinding {
|
public interface INMSBinding {
|
||||||
@@ -162,10 +161,6 @@ public interface INMSBinding {
|
|||||||
|
|
||||||
KMap<Material, List<BlockProperty>> getBlockProperties();
|
KMap<Material, List<BlockProperty>> getBlockProperties();
|
||||||
|
|
||||||
default Map<String, byte[]> extractVanillaDatapack() {
|
|
||||||
return Map.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateDimensionTypes(WorldCreator c) {
|
private void validateDimensionTypes(WorldCreator c) {
|
||||||
if (c.generator() instanceof PlatformChunkGenerator gen
|
if (c.generator() instanceof PlatformChunkGenerator gen
|
||||||
&& missingDimensionTypes(gen.getTarget().getDimension().getDimensionTypeKey())) {
|
&& missingDimensionTypes(gen.getTarget().getDimension().getDimensionTypeKey())) {
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* 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.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<>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.engine.framework.Engine;
|
|
||||||
import art.arcane.iris.engine.object.IrisDecorator;
|
|
||||||
|
|
||||||
public class IrisFloatingSurfaceDecorator extends IrisSurfaceDecorator {
|
|
||||||
public IrisFloatingSurfaceDecorator(Engine engine) {
|
|
||||||
super(engine, "Floating Surface");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isSlopeValid(IrisDecorator decorator, int realX, int realZ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||||
|
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package art.arcane.iris.engine.mantle.components;
|
||||||
|
|
||||||
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.mantle.MantleWriter;
|
||||||
|
import art.arcane.iris.engine.object.FloatingIslandSample;
|
||||||
|
import art.arcane.iris.engine.object.IObjectPlacer;
|
||||||
|
import art.arcane.iris.engine.object.TileData;
|
||||||
|
import art.arcane.iris.util.common.data.B;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class IslandObjectPlacer implements IObjectPlacer {
|
||||||
|
private static final int OVERHANG_RADIUS = 2;
|
||||||
|
|
||||||
|
private final MantleWriter wrapped;
|
||||||
|
private final FloatingIslandSample[] samples;
|
||||||
|
private final int minX;
|
||||||
|
private final int minZ;
|
||||||
|
private final int chunkMaxIslandTopY;
|
||||||
|
private final int anchorTopY;
|
||||||
|
private int writesAttempted;
|
||||||
|
private int writesDroppedBelow;
|
||||||
|
private int writesDroppedOverhang;
|
||||||
|
|
||||||
|
public IslandObjectPlacer(MantleWriter wrapped, FloatingIslandSample[] samples, int minX, int minZ, int anchorTopY) {
|
||||||
|
this.wrapped = wrapped;
|
||||||
|
this.samples = samples;
|
||||||
|
this.minX = minX;
|
||||||
|
this.minZ = minZ;
|
||||||
|
this.anchorTopY = anchorTopY;
|
||||||
|
int maxY = -1;
|
||||||
|
for (FloatingIslandSample s : samples) {
|
||||||
|
if (s != null) {
|
||||||
|
int ty = s.topY();
|
||||||
|
if (ty > maxY) {
|
||||||
|
maxY = ty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.chunkMaxIslandTopY = maxY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWritesAttempted() {
|
||||||
|
return writesAttempted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWritesDroppedBelow() {
|
||||||
|
return writesDroppedBelow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWritesDroppedOverhang() {
|
||||||
|
return writesDroppedOverhang;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldSkipAirColumn(int x, int y, int z) {
|
||||||
|
writesAttempted++;
|
||||||
|
if (sampleAt(x, z) != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (y <= anchorTopY) {
|
||||||
|
writesDroppedBelow++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!hasIslandNeighborWithin(x, z, OVERHANG_RADIUS)) {
|
||||||
|
writesDroppedOverhang++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasIslandNeighborWithin(int x, int z, int radius) {
|
||||||
|
int xf = x - minX;
|
||||||
|
int zf = z - minZ;
|
||||||
|
boolean touchedChunkEdge = false;
|
||||||
|
for (int dx = -radius; dx <= radius; dx++) {
|
||||||
|
int nxf = xf + dx;
|
||||||
|
for (int dz = -radius; dz <= radius; dz++) {
|
||||||
|
int nzf = zf + dz;
|
||||||
|
if (nxf < 0 || nxf >= 16 || nzf < 0 || nzf >= 16) {
|
||||||
|
touchedChunkEdge = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (samples[(nzf << 4) | nxf] != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return touchedChunkEdge;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable FloatingIslandSample sampleAt(int x, int z) {
|
||||||
|
int xf = x - minX;
|
||||||
|
int zf = z - minZ;
|
||||||
|
if (xf < 0 || xf >= 16 || zf < 0 || zf >= 16) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return samples[(zf << 4) | xf];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHighest(int x, int z, IrisData data) {
|
||||||
|
FloatingIslandSample s = sampleAt(x, z);
|
||||||
|
if (s != null) {
|
||||||
|
return s.topY();
|
||||||
|
}
|
||||||
|
return chunkMaxIslandTopY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHighest(int x, int z, IrisData data, boolean ignoreFluid) {
|
||||||
|
FloatingIslandSample s = sampleAt(x, z);
|
||||||
|
if (s != null) {
|
||||||
|
return s.topY();
|
||||||
|
}
|
||||||
|
return chunkMaxIslandTopY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUnderwater(int x, int z) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSolid(int x, int y, int z) {
|
||||||
|
FloatingIslandSample s = sampleAt(x, z);
|
||||||
|
if (s != null) {
|
||||||
|
int idx = y - s.islandBaseY;
|
||||||
|
if (idx >= 0 && idx < s.solidMask.length) {
|
||||||
|
return s.solidMask[idx];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return wrapped.isSolid(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCarved(int x, int y, int z) {
|
||||||
|
return wrapped.isCarved(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(int x, int y, int z, BlockData d) {
|
||||||
|
if (shouldSkipAirColumn(x, y, z)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wrapped.set(x, y, z, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockData get(int x, int y, int z) {
|
||||||
|
return wrapped.get(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPreventingDecay() {
|
||||||
|
return wrapped.isPreventingDecay();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFluidHeight() {
|
||||||
|
return wrapped.getFluidHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDebugSmartBore() {
|
||||||
|
return wrapped.isDebugSmartBore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTile(int xx, int yy, int zz, TileData tile) {
|
||||||
|
if (shouldSkipAirColumn(xx, yy, zz)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wrapped.setTile(xx, yy, zz, tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void setData(int xx, int yy, int zz, T data) {
|
||||||
|
if (shouldSkipAirColumn(xx, yy, zz)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wrapped.setData(xx, yy, zz, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> @Nullable T getData(int xx, int yy, int zz, Class<T> t) {
|
||||||
|
return wrapped.getData(xx, yy, zz, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Engine getEngine() {
|
||||||
|
return wrapped.getEngine();
|
||||||
|
}
|
||||||
|
}
|
||||||
+240
-26
@@ -27,12 +27,14 @@ import art.arcane.iris.engine.mantle.EngineMantle;
|
|||||||
import art.arcane.iris.engine.mantle.IrisMantleComponent;
|
import art.arcane.iris.engine.mantle.IrisMantleComponent;
|
||||||
import art.arcane.iris.engine.mantle.MantleWriter;
|
import art.arcane.iris.engine.mantle.MantleWriter;
|
||||||
import art.arcane.iris.engine.modifier.IrisFloatingChildBiomeModifier;
|
import art.arcane.iris.engine.modifier.IrisFloatingChildBiomeModifier;
|
||||||
import art.arcane.iris.engine.object.CarvingMode;
|
|
||||||
import art.arcane.iris.engine.object.FloatingIslandSample;
|
import art.arcane.iris.engine.object.FloatingIslandSample;
|
||||||
|
import art.arcane.iris.engine.object.FloatingObjectFootprint;
|
||||||
import art.arcane.iris.engine.object.IrisBiome;
|
import art.arcane.iris.engine.object.IrisBiome;
|
||||||
import art.arcane.iris.engine.object.IrisFloatingChildBiomes;
|
import art.arcane.iris.engine.object.IrisFloatingChildBiomes;
|
||||||
import art.arcane.iris.engine.object.IrisObject;
|
import art.arcane.iris.engine.object.IrisObject;
|
||||||
import art.arcane.iris.engine.object.IrisObjectPlacement;
|
import art.arcane.iris.engine.object.IrisObjectPlacement;
|
||||||
|
import art.arcane.iris.engine.object.IrisObjectRotation;
|
||||||
|
import art.arcane.iris.engine.object.IrisObjectTranslate;
|
||||||
import art.arcane.iris.engine.object.ObjectPlaceMode;
|
import art.arcane.iris.engine.object.ObjectPlaceMode;
|
||||||
import art.arcane.iris.util.project.context.ChunkContext;
|
import art.arcane.iris.util.project.context.ChunkContext;
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
@@ -40,13 +42,108 @@ import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
|||||||
import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
|
import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
@ComponentFlag(ReservedFlag.FLOATING_OBJECT)
|
@ComponentFlag(ReservedFlag.FLOATING_OBJECT)
|
||||||
public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||||
|
public static final AtomicLong objectsAttempted = new AtomicLong();
|
||||||
|
public static final AtomicLong objectsPlaced = new AtomicLong();
|
||||||
|
public static final AtomicLong objectsSkippedNoFlat = new AtomicLong();
|
||||||
|
public static final AtomicLong objectsSkippedNoInterior = new AtomicLong();
|
||||||
|
public static final AtomicLong objectsRelaxed = new AtomicLong();
|
||||||
|
public static final AtomicLong objectsSkippedShrink = new AtomicLong();
|
||||||
|
public static final AtomicLong objectsSkippedNullObj = new AtomicLong();
|
||||||
|
public static final AtomicLong terrainMismatchWarnings = new AtomicLong();
|
||||||
|
public static final AtomicLong writesAttemptedTotal = new AtomicLong();
|
||||||
|
public static final AtomicLong writesDroppedBelowTotal = new AtomicLong();
|
||||||
|
public static final AtomicLong writesDroppedOverhangTotal = new AtomicLong();
|
||||||
|
private static final int TERRAIN_MISMATCH_WARNING_CAP = 200;
|
||||||
|
private static final AtomicLong heavyClipWarnings = new AtomicLong();
|
||||||
|
private static final int HEAVY_CLIP_WARNING_CAP = 30;
|
||||||
|
private static final double HEAVY_CLIP_RATIO = 0.5;
|
||||||
|
private static final int MIN_FOOTPRINT_CELLS_CHECKED = 3;
|
||||||
|
public static final java.util.concurrent.ConcurrentHashMap<String, AtomicLong> anchorYHisto = new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
|
||||||
public MantleFloatingObjectComponent(EngineMantle engineMantle) {
|
public MantleFloatingObjectComponent(EngineMantle engineMantle) {
|
||||||
super(engineMantle, ReservedFlag.FLOATING_OBJECT, 2);
|
super(engineMantle, ReservedFlag.FLOATING_OBJECT, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void resetObjectCounters() {
|
||||||
|
objectsAttempted.set(0);
|
||||||
|
objectsPlaced.set(0);
|
||||||
|
objectsSkippedNoFlat.set(0);
|
||||||
|
objectsSkippedNoInterior.set(0);
|
||||||
|
objectsRelaxed.set(0);
|
||||||
|
objectsSkippedShrink.set(0);
|
||||||
|
objectsSkippedNullObj.set(0);
|
||||||
|
terrainMismatchWarnings.set(0);
|
||||||
|
writesAttemptedTotal.set(0);
|
||||||
|
writesDroppedBelowTotal.set(0);
|
||||||
|
writesDroppedOverhangTotal.set(0);
|
||||||
|
heavyClipWarnings.set(0);
|
||||||
|
anchorYHisto.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void recordWriteStats(IrisObject obj, int wx, int wz, int pickTopY, IslandObjectPlacer islandPlacer) {
|
||||||
|
int attempted = islandPlacer.getWritesAttempted();
|
||||||
|
int below = islandPlacer.getWritesDroppedBelow();
|
||||||
|
int overhang = islandPlacer.getWritesDroppedOverhang();
|
||||||
|
writesAttemptedTotal.addAndGet(attempted);
|
||||||
|
writesDroppedBelowTotal.addAndGet(below);
|
||||||
|
writesDroppedOverhangTotal.addAndGet(overhang);
|
||||||
|
int dropped = below + overhang;
|
||||||
|
if (attempted >= 32 && dropped >= attempted * HEAVY_CLIP_RATIO) {
|
||||||
|
long warned = heavyClipWarnings.get();
|
||||||
|
if (warned < HEAVY_CLIP_WARNING_CAP && heavyClipWarnings.incrementAndGet() <= HEAVY_CLIP_WARNING_CAP) {
|
||||||
|
String objKey = obj == null ? "<null>" : obj.getLoadKey();
|
||||||
|
Iris.warn("[FloatingWriteClip] object=" + objKey
|
||||||
|
+ " at=(" + wx + "," + (pickTopY + 1) + "," + wz + ")"
|
||||||
|
+ " attempted=" + attempted
|
||||||
|
+ " droppedBelow=" + below
|
||||||
|
+ " droppedOverhang=" + overhang
|
||||||
|
+ " written=" + (attempted - dropped));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void verifyTerrainBelowObject(MantleWriter writer, IrisObject obj, int wx, int wz, int pickTopY, FloatingIslandSample sample) {
|
||||||
|
long warned = terrainMismatchWarnings.get();
|
||||||
|
if (warned >= TERRAIN_MISMATCH_WARNING_CAP) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean mantleSolid;
|
||||||
|
try {
|
||||||
|
mantleSolid = writer.isSolid(wx, pickTopY, wz);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
mantleSolid = false;
|
||||||
|
}
|
||||||
|
boolean sampleSolid = sample != null
|
||||||
|
&& sample.solidMask != null
|
||||||
|
&& sample.topIdx >= 0
|
||||||
|
&& sample.topIdx < sample.solidMask.length
|
||||||
|
&& sample.solidMask[sample.topIdx];
|
||||||
|
if (mantleSolid && sampleSolid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (terrainMismatchWarnings.incrementAndGet() > TERRAIN_MISMATCH_WARNING_CAP) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String objKey = obj == null ? "<null>" : obj.getLoadKey();
|
||||||
|
String sampleTop = sample == null ? "null" : String.valueOf(sample.topY());
|
||||||
|
String sampleBase = sample == null ? "null" : String.valueOf(sample.islandBaseY);
|
||||||
|
String sampleTopIdx = sample == null ? "null" : String.valueOf(sample.topIdx);
|
||||||
|
String sampleMaskLen = sample == null || sample.solidMask == null ? "null" : String.valueOf(sample.solidMask.length);
|
||||||
|
Iris.warn("[FloatingTerrainCheck] object=" + objKey
|
||||||
|
+ " at=(" + wx + "," + (pickTopY + 1) + "," + wz + ")"
|
||||||
|
+ " expected solid below at y=" + pickTopY
|
||||||
|
+ " mantleSolid=" + mantleSolid
|
||||||
|
+ " sampleSolid=" + sampleSolid
|
||||||
|
+ " sampleTopY=" + sampleTop
|
||||||
|
+ " sampleBaseY=" + sampleBase
|
||||||
|
+ " sampleTopIdx=" + sampleTopIdx
|
||||||
|
+ " sampleMaskLen=" + sampleMaskLen);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
|
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
|
||||||
IrisComplex complex = context.getComplex();
|
IrisComplex complex = context.getComplex();
|
||||||
@@ -68,7 +165,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
if (parent == null || parent.getFloatingChildBiomes() == null || parent.getFloatingChildBiomes().isEmpty()) {
|
if (parent == null || parent.getFloatingChildBiomes() == null || parent.getFloatingChildBiomes().isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
FloatingIslandSample sample = FloatingIslandSample.sampleMemoized(parent, wx, wz, chunkHeight, baseSeed, data, complex, getEngineMantle().getEngine());
|
FloatingIslandSample sample = FloatingIslandSample.sampleMemoized(parent, wx, wz, chunkHeight, baseSeed, data, getEngineMantle().getEngine());
|
||||||
if (sample != null) {
|
if (sample != null) {
|
||||||
samples[(zf << 4) | xf] = sample;
|
samples[(zf << 4) | xf] = sample;
|
||||||
}
|
}
|
||||||
@@ -103,14 +200,14 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
|
|
||||||
if (entry.isInheritObjects() && target != null) {
|
if (entry.isInheritObjects() && target != null) {
|
||||||
for (IrisObjectPlacement placement : target.getSurfaceObjects()) {
|
for (IrisObjectPlacement placement : target.getSurfaceObjects()) {
|
||||||
tryPlaceAnchoredChunk(writer, complex, chunkRng, data, placement, samples, columns, minX, minZ, entry);
|
tryPlaceAnchoredChunk(writer, complex, chunkRng, data, placement, samples, columns, minX, minZ, entry, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KList<IrisObjectPlacement> extras = entry.getExtraObjects();
|
KList<IrisObjectPlacement> extras = entry.getExtraObjects();
|
||||||
if (extras != null && !extras.isEmpty()) {
|
if (extras != null && !extras.isEmpty()) {
|
||||||
for (IrisObjectPlacement placement : extras) {
|
for (IrisObjectPlacement placement : extras) {
|
||||||
tryPlaceAnchoredChunk(writer, complex, chunkRng, data, placement, samples, columns, minX, minZ, entry);
|
tryPlaceAnchoredChunk(writer, complex, chunkRng, data, placement, samples, columns, minX, minZ, entry, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,20 +221,24 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
int density = placement.getDensity(rng, minX, minZ, data);
|
int density = placement.getDensity(rng, minX, minZ, data);
|
||||||
double perAttempt = placement.getChance();
|
double perAttempt = placement.getChance();
|
||||||
for (int i = 0; i < density; i++) {
|
for (int i = 0; i < density; i++) {
|
||||||
|
objectsAttempted.incrementAndGet();
|
||||||
if (!rng.chance(perAttempt + rng.d(-0.005, 0.005))) {
|
if (!rng.chance(perAttempt + rng.d(-0.005, 0.005))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
IrisObject raw = placement.getObject(complex, rng);
|
IrisObject raw = placement.getObject(complex, rng);
|
||||||
if (raw == null) {
|
if (raw == null) {
|
||||||
|
objectsSkippedNullObj.incrementAndGet();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
IrisObject obj0 = placement.getScale().get(rng, raw);
|
IrisObject obj0 = placement.getScale().get(rng, raw);
|
||||||
if (obj0 == null) {
|
if (obj0 == null) {
|
||||||
|
objectsSkippedShrink.incrementAndGet();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (entry != null && entry.hasObjectShrink()) {
|
if (entry != null && entry.hasObjectShrink()) {
|
||||||
obj0 = entry.getShrinkScale().get(rng, obj0);
|
obj0 = entry.getShrinkScale().get(rng, obj0);
|
||||||
if (obj0 == null) {
|
if (obj0 == null) {
|
||||||
|
objectsSkippedShrink.incrementAndGet();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,6 +257,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
writer.setData(b.getX(), b.getY(), b.getZ(), marker);
|
writer.setData(b.getX(), b.getY(), b.getZ(), marker);
|
||||||
}
|
}
|
||||||
}, null, data);
|
}, null, data);
|
||||||
|
objectsPlaced.incrementAndGet();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Iris.reportError(e);
|
Iris.reportError(e);
|
||||||
}
|
}
|
||||||
@@ -163,67 +265,138 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ChunkCoordinates
|
@ChunkCoordinates
|
||||||
private void tryPlaceAnchoredChunk(MantleWriter writer, IrisComplex complex, RNG rng, IrisData data, IrisObjectPlacement placement, FloatingIslandSample[] samples, KList<Integer> columns, int minX, int minZ, IrisFloatingChildBiomes entry) {
|
private void tryPlaceAnchoredChunk(MantleWriter writer, IrisComplex complex, RNG rng, IrisData data, IrisObjectPlacement placement, FloatingIslandSample[] samples, KList<Integer> columns, int minX, int minZ, IrisFloatingChildBiomes entry, IrisBiome target) {
|
||||||
if (placement == null || columns.isEmpty()) {
|
if (placement == null || columns.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
KList<Integer> interior = interiorColumns(samples, columns);
|
|
||||||
KList<Integer> pickPool = interior.isEmpty() ? columns : interior;
|
|
||||||
int density = placement.getDensity(rng, minX, minZ, data);
|
int density = placement.getDensity(rng, minX, minZ, data);
|
||||||
double perAttempt = placement.getChance();
|
double perAttempt = placement.getChance();
|
||||||
|
KList<Integer> interior = interiorColumns(samples, columns);
|
||||||
|
|
||||||
for (int i = 0; i < density; i++) {
|
for (int i = 0; i < density; i++) {
|
||||||
|
objectsAttempted.incrementAndGet();
|
||||||
if (!rng.chance(perAttempt + rng.d(-0.005, 0.005))) {
|
if (!rng.chance(perAttempt + rng.d(-0.005, 0.005))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
IrisObject raw = placement.getObject(complex, rng);
|
IrisObject raw = placement.getObject(complex, rng);
|
||||||
if (raw == null) {
|
if (raw == null) {
|
||||||
|
objectsSkippedNullObj.incrementAndGet();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
IrisObject obj0 = placement.getScale().get(rng, raw);
|
IrisObject obj0 = placement.getScale().get(rng, raw);
|
||||||
if (obj0 == null) {
|
if (obj0 == null) {
|
||||||
|
objectsSkippedShrink.incrementAndGet();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (entry != null && entry.hasObjectShrink()) {
|
if (entry != null && entry.hasObjectShrink()) {
|
||||||
obj0 = entry.getShrinkScale().get(rng, obj0);
|
obj0 = entry.getShrinkScale().get(rng, obj0);
|
||||||
if (obj0 == null) {
|
if (obj0 == null) {
|
||||||
|
objectsSkippedShrink.incrementAndGet();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final IrisObject obj = obj0;
|
final IrisObject obj = obj0;
|
||||||
|
|
||||||
int key = pickPool.get(rng.i(0, pickPool.size() - 1));
|
FloatingObjectFootprint fp = FloatingObjectFootprint.compute(obj);
|
||||||
int xf = key & 15;
|
|
||||||
int zf = key >> 4;
|
KList<Integer> pool = interior.isEmpty() ? columns : interior;
|
||||||
FloatingIslandSample sample = samples[(zf << 4) | xf];
|
if (interior.isEmpty()) {
|
||||||
if (sample == null) {
|
objectsSkippedNoInterior.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
int pickedKey = pool.get(rng.i(0, pool.size() - 1));
|
||||||
|
int pickedXf = pickedKey & 15;
|
||||||
|
int pickedZf = pickedKey >> 4;
|
||||||
|
FloatingIslandSample pickedSample = samples[(pickedZf << 4) | pickedXf];
|
||||||
|
if (pickedSample == null) {
|
||||||
|
objectsSkippedNoFlat.incrementAndGet();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int wx = minX + xf;
|
int pickTopY = pickedSample.topY();
|
||||||
int wz = minZ + zf;
|
|
||||||
|
|
||||||
int anchorY = sample.topY() + 1 + obj.getCenter().getBlockY();
|
if (!isFootprintFlat(fp, pickedXf, pickedZf, pickTopY, samples, 2)) {
|
||||||
int id = rng.i(0, Integer.MAX_VALUE);
|
if (!isFootprintFlat(fp, pickedXf, pickedZf, pickTopY, samples, 4)) {
|
||||||
|
objectsSkippedNoFlat.incrementAndGet();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
objectsRelaxed.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
int wx = minX + pickedXf - fp.getTallestKx();
|
||||||
|
int wz = minZ + pickedZf - fp.getTallestKz();
|
||||||
|
|
||||||
IrisObjectPlacement anchored = placement.toPlacement(obj.getLoadKey());
|
IrisObjectPlacement anchored = placement.toPlacement(obj.getLoadKey());
|
||||||
anchored.setCarvingSupport(CarvingMode.ANYWHERE);
|
anchored.setMode(translateStiltModeForFloating(anchored.getMode()));
|
||||||
|
anchored.setTranslate(new IrisObjectTranslate());
|
||||||
|
anchored.setRotation(IrisObjectRotation.of(0, 0, 0));
|
||||||
anchored.setForcePlace(true);
|
anchored.setForcePlace(true);
|
||||||
anchored.setMode(ObjectPlaceMode.STRUCTURE_PIECE);
|
anchored.setBottom(false);
|
||||||
anchored.setBore(false);
|
|
||||||
anchored.setMeld(false);
|
int yv = pickTopY + 1 - fp.getLowestSolidKeyY();
|
||||||
|
|
||||||
|
IslandObjectPlacer islandPlacer = new IslandObjectPlacer(writer, samples, minX, minZ, pickTopY);
|
||||||
|
int id = rng.i(0, Integer.MAX_VALUE);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
obj.place(wx, anchorY, wz, writer, anchored, rng, (b, bd) -> {
|
obj.place(wx, yv, wz, islandPlacer, anchored, rng, (b, bd) -> {
|
||||||
String marker = placementMarker(obj, id);
|
String marker = placementMarker(obj, id);
|
||||||
if (marker != null) {
|
if (marker != null) {
|
||||||
writer.setData(b.getX(), b.getY(), b.getZ(), marker);
|
writer.setData(b.getX(), b.getY(), b.getZ(), marker);
|
||||||
}
|
}
|
||||||
}, null, data);
|
}, null, data);
|
||||||
|
objectsPlaced.incrementAndGet();
|
||||||
|
recordAnchorYHisto(pickTopY);
|
||||||
|
int trunkWx = minX + pickedXf;
|
||||||
|
int trunkWz = minZ + pickedZf;
|
||||||
|
verifyTerrainBelowObject(writer, obj, trunkWx, trunkWz, pickTopY, pickedSample);
|
||||||
|
recordWriteStats(obj, trunkWx, trunkWz, pickTopY, islandPlacer);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Iris.reportError(e);
|
Iris.reportError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isFootprintFlat(FloatingObjectFootprint fp, int pickedXf, int pickedZf, int pickTopY, FloatingIslandSample[] samples, int tolerance) {
|
||||||
|
int tallestKx = fp.getTallestKx();
|
||||||
|
int tallestKz = fp.getTallestKz();
|
||||||
|
int checked = 0;
|
||||||
|
boolean touchedChunkEdge = false;
|
||||||
|
for (long encoded : fp.footprintXZ()) {
|
||||||
|
int kx = (int) (encoded >> 32);
|
||||||
|
int kz = (int) (encoded & 0xFFFFFFFFL);
|
||||||
|
int colXf = pickedXf + (kx - tallestKx);
|
||||||
|
int colZf = pickedZf + (kz - tallestKz);
|
||||||
|
if (colXf < 0 || colXf >= 16 || colZf < 0 || colZf >= 16) {
|
||||||
|
touchedChunkEdge = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
FloatingIslandSample s = samples[(colZf << 4) | colXf];
|
||||||
|
if (s == null || Math.abs(s.topY() - pickTopY) > tolerance) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
checked++;
|
||||||
|
}
|
||||||
|
if (checked >= MIN_FOOTPRINT_CELLS_CHECKED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return touchedChunkEdge;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void recordAnchorYHisto(int topY) {
|
||||||
|
String bucket = String.valueOf(topY >> 3);
|
||||||
|
if (anchorYHisto.size() < 32) {
|
||||||
|
anchorYHisto.computeIfAbsent(bucket, k -> new AtomicLong()).incrementAndGet();
|
||||||
|
} else {
|
||||||
|
AtomicLong existing = anchorYHisto.get(bucket);
|
||||||
|
if (existing != null) {
|
||||||
|
existing.incrementAndGet();
|
||||||
|
} else {
|
||||||
|
anchorYHisto.computeIfAbsent("other", k -> new AtomicLong()).incrementAndGet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static KList<Integer> interiorColumns(FloatingIslandSample[] samples, KList<Integer> columns) {
|
private static KList<Integer> interiorColumns(FloatingIslandSample[] samples, KList<Integer> columns) {
|
||||||
KList<Integer> interior = new KList<>();
|
KList<Integer> interior = new KList<>();
|
||||||
for (int key : columns) {
|
for (int key : columns) {
|
||||||
@@ -252,23 +425,64 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
|||||||
return key + "@" + id;
|
return key + "@" + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ObjectPlaceMode translateStiltModeForFloating(ObjectPlaceMode m) {
|
||||||
|
return switch (m) {
|
||||||
|
case STILT -> ObjectPlaceMode.MAX_HEIGHT;
|
||||||
|
case FAST_STILT -> ObjectPlaceMode.FAST_MAX_HEIGHT;
|
||||||
|
case MIN_STILT -> ObjectPlaceMode.MIN_HEIGHT;
|
||||||
|
case FAST_MIN_STILT -> ObjectPlaceMode.FAST_MIN_HEIGHT;
|
||||||
|
case CENTER_STILT -> ObjectPlaceMode.CENTER_HEIGHT;
|
||||||
|
case ERODE_STILT -> ObjectPlaceMode.MAX_HEIGHT;
|
||||||
|
case STRUCTURE_PIECE -> ObjectPlaceMode.CENTER_HEIGHT;
|
||||||
|
default -> m;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int computeRadius() {
|
protected int computeRadius() {
|
||||||
int maxThickness = 0;
|
int maxObjectExtent = 0;
|
||||||
int maxHeightAbove = 0;
|
java.util.Set<String> objectKeys = new java.util.HashSet<>();
|
||||||
try {
|
try {
|
||||||
|
IrisData data = getData();
|
||||||
for (IrisBiome biome : getDimension().getAllBiomes(this::getData)) {
|
for (IrisBiome biome : getDimension().getAllBiomes(this::getData)) {
|
||||||
KList<IrisFloatingChildBiomes> entries = biome.getFloatingChildBiomes();
|
KList<IrisFloatingChildBiomes> entries = biome.getFloatingChildBiomes();
|
||||||
if (entries == null || entries.isEmpty()) {
|
if (entries == null || entries.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (IrisFloatingChildBiomes entry : entries) {
|
for (IrisFloatingChildBiomes entry : entries) {
|
||||||
maxThickness = Math.max(maxThickness, entry.getMaxThickness());
|
collectPlacementKeys(entry.getFloatingObjects(), objectKeys);
|
||||||
maxHeightAbove = Math.max(maxHeightAbove, entry.getMaxHeightAboveSurface());
|
collectPlacementKeys(entry.getExtraObjects(), objectKeys);
|
||||||
|
if (entry.isInheritObjects()) {
|
||||||
|
try {
|
||||||
|
IrisBiome target = entry.getRealBiome(biome, data);
|
||||||
|
if (target != null) {
|
||||||
|
collectPlacementKeys(target.getSurfaceObjects(), objectKeys);
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String key : objectKeys) {
|
||||||
|
try {
|
||||||
|
java.io.File f = data.getObjectLoader().findFile(key);
|
||||||
|
if (f == null) continue;
|
||||||
|
org.bukkit.util.BlockVector sz = IrisObject.sampleSize(f);
|
||||||
|
int extent = Math.max(sz.getBlockX(), sz.getBlockZ());
|
||||||
|
if (extent > maxObjectExtent) maxObjectExtent = extent;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
return Math.max(1, (maxThickness + maxHeightAbove) >> 4);
|
return Math.max(16, maxObjectExtent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void collectPlacementKeys(KList<IrisObjectPlacement> placements, java.util.Set<String> out) {
|
||||||
|
if (placements == null) return;
|
||||||
|
for (IrisObjectPlacement p : placements) {
|
||||||
|
if (p == null || p.getPlace() == null) continue;
|
||||||
|
out.addAll(p.getPlace());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+82
-30
@@ -21,15 +21,17 @@ package art.arcane.iris.engine.modifier;
|
|||||||
import art.arcane.iris.core.loader.IrisData;
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
import art.arcane.iris.core.nms.INMS;
|
import art.arcane.iris.core.nms.INMS;
|
||||||
import art.arcane.iris.engine.IrisComplex;
|
import art.arcane.iris.engine.IrisComplex;
|
||||||
import art.arcane.iris.engine.decorator.IrisFloatingSurfaceDecorator;
|
import art.arcane.iris.engine.decorator.FloatingDecorator;
|
||||||
import art.arcane.iris.engine.decorator.IrisSeaSurfaceDecorator;
|
import art.arcane.iris.engine.decorator.IrisSeaSurfaceDecorator;
|
||||||
import static art.arcane.iris.engine.mantle.EngineMantle.AIR;
|
import static art.arcane.iris.engine.mantle.EngineMantle.AIR;
|
||||||
import art.arcane.iris.engine.framework.Engine;
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
import art.arcane.iris.engine.framework.EngineAssignedModifier;
|
import art.arcane.iris.engine.framework.EngineAssignedModifier;
|
||||||
import art.arcane.iris.engine.framework.EngineDecorator;
|
import art.arcane.iris.engine.framework.EngineDecorator;
|
||||||
|
import art.arcane.iris.engine.mantle.components.MantleFloatingObjectComponent;
|
||||||
import art.arcane.iris.engine.object.FloatingIslandSample;
|
import art.arcane.iris.engine.object.FloatingIslandSample;
|
||||||
import art.arcane.iris.engine.object.IrisBiome;
|
import art.arcane.iris.engine.object.IrisBiome;
|
||||||
import art.arcane.iris.engine.object.IrisBiomeCustom;
|
import art.arcane.iris.engine.object.IrisBiomeCustom;
|
||||||
|
import art.arcane.iris.engine.object.IrisDecorationPart;
|
||||||
import art.arcane.iris.engine.object.IrisDimension;
|
import art.arcane.iris.engine.object.IrisDimension;
|
||||||
import art.arcane.iris.engine.object.IrisFloatingChildBiomes;
|
import art.arcane.iris.engine.object.IrisFloatingChildBiomes;
|
||||||
import art.arcane.iris.util.common.data.B;
|
import art.arcane.iris.util.common.data.B;
|
||||||
@@ -43,21 +45,23 @@ import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
|||||||
import org.bukkit.block.Biome;
|
import org.bukkit.block.Biome;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<BlockData> {
|
public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<BlockData> {
|
||||||
public static final long FLOATING_BASE_SEED_SALT = 0x5EED_F107_00F1B10CL;
|
public static final long FLOATING_BASE_SEED_SALT = 0x5EED_F107_00F1B10CL;
|
||||||
private static final java.util.concurrent.atomic.AtomicLong columnsChecked = new java.util.concurrent.atomic.AtomicLong();
|
private static final AtomicLong columnsChecked = new AtomicLong();
|
||||||
private static final java.util.concurrent.atomic.AtomicLong samplesAccepted = new java.util.concurrent.atomic.AtomicLong();
|
private static final AtomicLong samplesAccepted = new AtomicLong();
|
||||||
private static final java.util.concurrent.atomic.AtomicLong decorateInvocations = new java.util.concurrent.atomic.AtomicLong();
|
private static final AtomicLong decorateInvocations = new AtomicLong();
|
||||||
private static final java.util.concurrent.atomic.AtomicLong decorateSkippedNotAir = new java.util.concurrent.atomic.AtomicLong();
|
private static final AtomicLong decorateSkippedNotAir = new AtomicLong();
|
||||||
private static final java.util.concurrent.atomic.AtomicLong decorateSkippedNoInherit = new java.util.concurrent.atomic.AtomicLong();
|
private static final AtomicLong decorateSkippedNoInherit = new AtomicLong();
|
||||||
private static final java.util.concurrent.atomic.AtomicLong decoratePhaseColumns = new java.util.concurrent.atomic.AtomicLong();
|
private static final AtomicLong decoratePhaseColumns = new AtomicLong();
|
||||||
private static final java.util.concurrent.atomic.AtomicLong decoratePlaced = new java.util.concurrent.atomic.AtomicLong();
|
private static final AtomicLong decoratePlaced = new AtomicLong();
|
||||||
private static final java.util.concurrent.atomic.AtomicLong decorateNoChange = new java.util.concurrent.atomic.AtomicLong();
|
private static final AtomicLong decorateNoChange = new AtomicLong();
|
||||||
private static final java.util.concurrent.atomic.AtomicLong decorateFloorNull = new java.util.concurrent.atomic.AtomicLong();
|
private static final AtomicLong decorateFloorNull = new AtomicLong();
|
||||||
private static final java.util.concurrent.ConcurrentHashMap<String, java.util.concurrent.atomic.AtomicLong> floorMatHisto = new java.util.concurrent.ConcurrentHashMap<>();
|
private static final java.util.concurrent.ConcurrentHashMap<String, AtomicLong> floorMatHisto = new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
private static final java.util.concurrent.atomic.AtomicLong lastReportMs = new java.util.concurrent.atomic.AtomicLong(0L);
|
private static final AtomicLong lastReportMs = new AtomicLong(0L);
|
||||||
|
private static final AtomicLong reportCycle = new AtomicLong(0L);
|
||||||
private final RNG rng;
|
private final RNG rng;
|
||||||
private final EngineDecorator surfaceDecorator;
|
|
||||||
private final EngineDecorator seaSurfaceDecorator;
|
private final EngineDecorator seaSurfaceDecorator;
|
||||||
|
|
||||||
public static void reportFloatingStats() {
|
public static void reportFloatingStats() {
|
||||||
@@ -66,15 +70,36 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
|||||||
.sorted((a, b) -> Long.compare(b.getValue().get(), a.getValue().get()))
|
.sorted((a, b) -> Long.compare(b.getValue().get(), a.getValue().get()))
|
||||||
.limit(5)
|
.limit(5)
|
||||||
.forEach(e -> topFloors.append(' ').append(e.getKey()).append('=').append(e.getValue().get()));
|
.forEach(e -> topFloors.append(' ').append(e.getKey()).append('=').append(e.getValue().get()));
|
||||||
art.arcane.iris.Iris.info("[floating-debug] columns=" + columnsChecked.get()
|
|
||||||
|
StringBuilder topAnchorY = new StringBuilder();
|
||||||
|
MantleFloatingObjectComponent.anchorYHisto.entrySet().stream()
|
||||||
|
.sorted((a, b) -> Long.compare(b.getValue().get(), a.getValue().get()))
|
||||||
|
.limit(5)
|
||||||
|
.forEach(e -> topAnchorY.append(' ').append(e.getKey()).append('=').append(e.getValue().get()));
|
||||||
|
|
||||||
|
art.arcane.iris.Iris.info("[floating-debug]"
|
||||||
|
+ " columns=" + columnsChecked.get()
|
||||||
+ " samples=" + samplesAccepted.get()
|
+ " samples=" + samplesAccepted.get()
|
||||||
+ " decInvoke=" + decorateInvocations.get()
|
+ " decInvoke=" + decorateInvocations.get()
|
||||||
+ " decPlaced=" + decoratePlaced.get()
|
+ " decPlaced=" + decoratePlaced.get()
|
||||||
+ " decNoChange=" + decorateNoChange.get()
|
+ " decNoChange=" + decorateNoChange.get()
|
||||||
+ " decFloorNull=" + decorateFloorNull.get()
|
+ " decFloorNull=" + decorateFloorNull.get()
|
||||||
|
+ " decCandidatesNull=" + FloatingDecorator.decCandidatesNull.get()
|
||||||
+ " decSkipNonAir=" + decorateSkippedNotAir.get()
|
+ " decSkipNonAir=" + decorateSkippedNotAir.get()
|
||||||
+ " decSkipNoInherit=" + decorateSkippedNoInherit.get()
|
+ " decSkipNoInherit=" + decorateSkippedNoInherit.get()
|
||||||
+ " decPhaseCols=" + decoratePhaseColumns.get()
|
+ " decPhaseCols=" + decoratePhaseColumns.get()
|
||||||
|
+ " objAttempt=" + MantleFloatingObjectComponent.objectsAttempted.get()
|
||||||
|
+ " objPlaced=" + MantleFloatingObjectComponent.objectsPlaced.get()
|
||||||
|
+ " objNoFlat=" + MantleFloatingObjectComponent.objectsSkippedNoFlat.get()
|
||||||
|
+ " objNoInterior=" + MantleFloatingObjectComponent.objectsSkippedNoInterior.get()
|
||||||
|
+ " objRelax=" + MantleFloatingObjectComponent.objectsRelaxed.get()
|
||||||
|
+ " objShrinkDrop=" + MantleFloatingObjectComponent.objectsSkippedShrink.get()
|
||||||
|
+ " objNullObj=" + MantleFloatingObjectComponent.objectsSkippedNullObj.get()
|
||||||
|
+ " writeAttempt=" + MantleFloatingObjectComponent.writesAttemptedTotal.get()
|
||||||
|
+ " writeDropBelow=" + MantleFloatingObjectComponent.writesDroppedBelowTotal.get()
|
||||||
|
+ " writeDropOverhang=" + MantleFloatingObjectComponent.writesDroppedOverhangTotal.get()
|
||||||
|
+ " terrainMismatch=" + MantleFloatingObjectComponent.terrainMismatchWarnings.get()
|
||||||
|
+ " anchorY:" + (topAnchorY.length() == 0 ? " <none>" : topAnchorY.toString())
|
||||||
+ " topFloors:" + (topFloors.length() == 0 ? " <none>" : topFloors.toString()));
|
+ " topFloors:" + (topFloors.length() == 0 ? " <none>" : topFloors.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,13 +108,44 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
|||||||
long last = lastReportMs.get();
|
long last = lastReportMs.get();
|
||||||
if (now - last >= 10000L && lastReportMs.compareAndSet(last, now)) {
|
if (now - last >= 10000L && lastReportMs.compareAndSet(last, now)) {
|
||||||
reportFloatingStats();
|
reportFloatingStats();
|
||||||
|
if (reportCycle.incrementAndGet() >= 30) {
|
||||||
|
reportCycle.set(0);
|
||||||
|
resetAllCounters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void resetAllCounters() {
|
||||||
|
columnsChecked.set(0);
|
||||||
|
samplesAccepted.set(0);
|
||||||
|
decorateInvocations.set(0);
|
||||||
|
decorateSkippedNotAir.set(0);
|
||||||
|
decorateSkippedNoInherit.set(0);
|
||||||
|
decoratePhaseColumns.set(0);
|
||||||
|
decoratePlaced.set(0);
|
||||||
|
decorateNoChange.set(0);
|
||||||
|
decorateFloorNull.set(0);
|
||||||
|
floorMatHisto.clear();
|
||||||
|
FloatingDecorator.decCandidatesNull.set(0);
|
||||||
|
MantleFloatingObjectComponent.resetObjectCounters();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void recordFloorMat(String matKey) {
|
||||||
|
if (floorMatHisto.size() < 32) {
|
||||||
|
floorMatHisto.computeIfAbsent(matKey, k -> new AtomicLong()).incrementAndGet();
|
||||||
|
} else {
|
||||||
|
AtomicLong existing = floorMatHisto.get(matKey);
|
||||||
|
if (existing != null) {
|
||||||
|
existing.incrementAndGet();
|
||||||
|
} else {
|
||||||
|
floorMatHisto.computeIfAbsent("other", k -> new AtomicLong()).incrementAndGet();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IrisFloatingChildBiomeModifier(Engine engine) {
|
public IrisFloatingChildBiomeModifier(Engine engine) {
|
||||||
super(engine, "FloatingChildBiomes");
|
super(engine, "FloatingChildBiomes");
|
||||||
rng = new RNG(engine.getSeedManager().getTerrain() ^ 0x7EB0A73F1DCE514DL);
|
rng = new RNG(engine.getSeedManager().getTerrain() ^ 0x7EB0A73F1DCE514DL);
|
||||||
surfaceDecorator = new IrisFloatingSurfaceDecorator(engine);
|
|
||||||
seaSurfaceDecorator = new IrisSeaSurfaceDecorator(engine);
|
seaSurfaceDecorator = new IrisSeaSurfaceDecorator(engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +168,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
|||||||
}
|
}
|
||||||
columnsChecked.incrementAndGet();
|
columnsChecked.incrementAndGet();
|
||||||
|
|
||||||
FloatingIslandSample sample = FloatingIslandSample.sampleMemoized(parent, wx, wz, chunkHeight, baseSeed, data, complex, getEngine());
|
FloatingIslandSample sample = FloatingIslandSample.sampleMemoized(parent, wx, wz, chunkHeight, baseSeed, data, getEngine());
|
||||||
if (sample == null) {
|
if (sample == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -202,7 +258,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
|||||||
if (parent == null || parent.getFloatingChildBiomes() == null || parent.getFloatingChildBiomes().isEmpty()) {
|
if (parent == null || parent.getFloatingChildBiomes() == null || parent.getFloatingChildBiomes().isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
FloatingIslandSample sample = FloatingIslandSample.sampleMemoized(parent, wx, wz, chunkHeight, baseSeed, data, complex, getEngine());
|
FloatingIslandSample sample = FloatingIslandSample.sampleMemoized(parent, wx, wz, chunkHeight, baseSeed, data, getEngine());
|
||||||
if (sample == null) {
|
if (sample == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -225,20 +281,19 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
|||||||
if (floor == null) {
|
if (floor == null) {
|
||||||
decorateFloorNull.incrementAndGet();
|
decorateFloorNull.incrementAndGet();
|
||||||
} else {
|
} else {
|
||||||
String matKey = floor.getMaterial().getKey().getKey();
|
recordFloorMat(floor.getMaterial().getKey().getKey());
|
||||||
floorMatHisto.computeIfAbsent(matKey, k -> new java.util.concurrent.atomic.AtomicLong()).incrementAndGet();
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
surfaceDecorator.decorate(xf, zf, wx, wz, output, target, topY, max);
|
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);
|
||||||
|
if (placed > 0) {
|
||||||
|
decoratePlaced.addAndGet(placed);
|
||||||
|
} else {
|
||||||
|
decorateNoChange.incrementAndGet();
|
||||||
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
art.arcane.iris.Iris.reportError(e);
|
art.arcane.iris.Iris.reportError(e);
|
||||||
}
|
}
|
||||||
BlockData afterAbove = output.get(xf, topY + 1, zf);
|
|
||||||
if (afterAbove != null && !B.isAir(afterAbove)) {
|
|
||||||
decoratePlaced.incrementAndGet();
|
|
||||||
} else {
|
|
||||||
decorateNoChange.incrementAndGet();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
decorateSkippedNotAir.incrementAndGet();
|
decorateSkippedNotAir.incrementAndGet();
|
||||||
}
|
}
|
||||||
@@ -269,10 +324,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
|||||||
}
|
}
|
||||||
if (fluidTopY > 0 && fluidTopY + 1 < chunkHeight && B.isAir(output.get(xf, fluidTopY + 1, zf))) {
|
if (fluidTopY > 0 && fluidTopY + 1 < chunkHeight && B.isAir(output.get(xf, fluidTopY + 1, zf))) {
|
||||||
try {
|
try {
|
||||||
seaSurfaceDecorator.decorate(xf, zf,
|
seaSurfaceDecorator.decorate(xf, zf, wx, wx + 1, wx - 1, wz, wz + 1, wz - 1, output, target, fluidTopY, chunkHeight);
|
||||||
wx, wx + 1, wx - 1,
|
|
||||||
wz, wz + 1, wz - 1,
|
|
||||||
output, target, fluidTopY, chunkHeight);
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
art.arcane.iris.Iris.reportError(e);
|
art.arcane.iris.Iris.reportError(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ package art.arcane.iris.engine.object;
|
|||||||
|
|
||||||
import art.arcane.iris.Iris;
|
import art.arcane.iris.Iris;
|
||||||
import art.arcane.iris.core.loader.IrisData;
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
import art.arcane.iris.engine.IrisComplex;
|
|
||||||
import art.arcane.iris.engine.framework.Engine;
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
import art.arcane.iris.util.project.noise.CNG;
|
import art.arcane.iris.util.project.noise.CNG;
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
@@ -63,13 +62,13 @@ public final class FloatingIslandSample {
|
|||||||
CHUNK_MEMO.get().clear();
|
CHUNK_MEMO.get().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FloatingIslandSample sampleMemoized(IrisBiome parent, int wx, int wz, int chunkHeight, long baseSeed, IrisData data, IrisComplex complex, Engine engine) {
|
public static FloatingIslandSample sampleMemoized(IrisBiome parent, int wx, int wz, int chunkHeight, long baseSeed, IrisData data, Engine engine) {
|
||||||
long key = (((long) wx) << 32) ^ (wz & 0xFFFFFFFFL);
|
long key = (((long) wx) << 32) ^ (wz & 0xFFFFFFFFL);
|
||||||
HashMap<Long, FloatingIslandSample> memo = CHUNK_MEMO.get();
|
HashMap<Long, FloatingIslandSample> memo = CHUNK_MEMO.get();
|
||||||
if (memo.containsKey(key)) {
|
if (memo.containsKey(key)) {
|
||||||
return memo.get(key);
|
return memo.get(key);
|
||||||
}
|
}
|
||||||
FloatingIslandSample result = sample(parent, wx, wz, chunkHeight, baseSeed, data, complex, engine);
|
FloatingIslandSample result = sample(parent, wx, wz, chunkHeight, baseSeed, data, engine);
|
||||||
memo.put(key, result);
|
memo.put(key, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -111,7 +110,7 @@ public final class FloatingIslandSample {
|
|||||||
return baseSeed ^ ((long) wx * 341873128712L) ^ ((long) wz * 132897987541L);
|
return baseSeed ^ ((long) wx * 341873128712L) ^ ((long) wz * 132897987541L);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FloatingIslandSample sample(IrisBiome parent, int wx, int wz, int chunkHeight, long baseSeed, IrisData data, IrisComplex complex, Engine engine) {
|
public static FloatingIslandSample sample(IrisBiome parent, int wx, int wz, int chunkHeight, long baseSeed, IrisData data, Engine engine) {
|
||||||
KList<IrisFloatingChildBiomes> entries = parent.getFloatingChildBiomes();
|
KList<IrisFloatingChildBiomes> entries = parent.getFloatingChildBiomes();
|
||||||
if (entries == null || entries.isEmpty()) {
|
if (entries == null || entries.isEmpty()) {
|
||||||
return reject(REJECT_NO_ENTRIES);
|
return reject(REJECT_NO_ENTRIES);
|
||||||
@@ -153,8 +152,6 @@ public final class FloatingIslandSample {
|
|||||||
return reject(REJECT_NO_SEED);
|
return reject(REJECT_NO_SEED);
|
||||||
}
|
}
|
||||||
|
|
||||||
int surfaceY = (int) Math.round(complex.getHeightStream().get(wx & ~63, wz & ~63));
|
|
||||||
|
|
||||||
CNG altitudeCng = entry.getAltitudeCng(baseSeed, data);
|
CNG altitudeCng = entry.getAltitudeCng(baseSeed, data);
|
||||||
if (altitudeCng == null) {
|
if (altitudeCng == null) {
|
||||||
warnNullCng("altitudeStyle", parent);
|
warnNullCng("altitudeStyle", parent);
|
||||||
@@ -162,9 +159,10 @@ public final class FloatingIslandSample {
|
|||||||
}
|
}
|
||||||
double altNoise = altitudeCng.noise(wx, wz);
|
double altNoise = altitudeCng.noise(wx, wz);
|
||||||
double altClamped = Math.max(0, Math.min(1, altNoise));
|
double altClamped = Math.max(0, Math.min(1, altNoise));
|
||||||
int minAlt = Math.max(0, entry.getMinHeightAboveSurface());
|
int worldMin = engine.getWorld().minHeight();
|
||||||
int maxAlt = Math.max(minAlt, entry.getMaxHeightAboveSurface());
|
int minAlt = Math.max(0, entry.getMinHeightAboveSurface() - worldMin);
|
||||||
int baseY = surfaceY + minAlt + (int) Math.round(altClamped * (maxAlt - minAlt));
|
int maxAlt = Math.max(minAlt, entry.getMaxHeightAboveSurface() - worldMin);
|
||||||
|
int baseY = minAlt + (int) Math.round(altClamped * (maxAlt - minAlt));
|
||||||
|
|
||||||
IrisBiome target = entry.getRealBiome(parent, data);
|
IrisBiome target = entry.getRealBiome(parent, data);
|
||||||
int topH = computeTopHeight(entry, target, engine, baseSeed, wx, wz, data);
|
int topH = computeTopHeight(entry, target, engine, baseSeed, wx, wz, data);
|
||||||
@@ -188,12 +186,12 @@ public final class FloatingIslandSample {
|
|||||||
int botY = baseY - depth;
|
int botY = baseY - depth;
|
||||||
|
|
||||||
Integer minAbsoluteY = entry.getMinAbsoluteY();
|
Integer minAbsoluteY = entry.getMinAbsoluteY();
|
||||||
if (minAbsoluteY != null && botY < minAbsoluteY) {
|
if (minAbsoluteY != null && botY < minAbsoluteY - worldMin) {
|
||||||
botY = minAbsoluteY;
|
botY = minAbsoluteY - worldMin;
|
||||||
}
|
}
|
||||||
Integer maxAbsoluteY = entry.getMaxAbsoluteY();
|
Integer maxAbsoluteY = entry.getMaxAbsoluteY();
|
||||||
if (maxAbsoluteY != null && topY > maxAbsoluteY) {
|
if (maxAbsoluteY != null && topY > maxAbsoluteY - worldMin) {
|
||||||
topY = maxAbsoluteY;
|
topY = maxAbsoluteY - worldMin;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (botY < 0) {
|
if (botY < 0) {
|
||||||
|
|||||||
@@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
* 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.object;
|
||||||
|
|
||||||
|
import art.arcane.iris.Iris;
|
||||||
|
import art.arcane.iris.util.common.data.B;
|
||||||
|
import org.bukkit.block.data.BlockData;
|
||||||
|
import org.bukkit.util.BlockVector;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class FloatingObjectFootprint {
|
||||||
|
private static final ConcurrentHashMap<String, FloatingObjectFootprint> CACHE = new ConcurrentHashMap<>();
|
||||||
|
private static final boolean DIAGNOSTIC_LOG = Boolean.parseBoolean(System.getProperty("iris.floating.footprintLog", "true"));
|
||||||
|
|
||||||
|
private final int lowestSolidKeyY;
|
||||||
|
private final int centerX;
|
||||||
|
private final int centerY;
|
||||||
|
private final int centerZ;
|
||||||
|
private final int tallestKx;
|
||||||
|
private final int tallestKz;
|
||||||
|
private final Set<Long> footprintXZ;
|
||||||
|
|
||||||
|
private FloatingObjectFootprint(int lowestSolidKeyY, int centerX, int centerY, int centerZ, int tallestKx, int tallestKz, Set<Long> footprintXZ) {
|
||||||
|
this.lowestSolidKeyY = lowestSolidKeyY;
|
||||||
|
this.centerX = centerX;
|
||||||
|
this.centerY = centerY;
|
||||||
|
this.centerZ = centerZ;
|
||||||
|
this.tallestKx = tallestKx;
|
||||||
|
this.tallestKz = tallestKz;
|
||||||
|
this.footprintXZ = Collections.unmodifiableSet(footprintXZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FloatingObjectFootprint compute(IrisObject obj) {
|
||||||
|
String cacheKey = obj.getLoadKey() + "@" + obj.getW() + "x" + obj.getH() + "x" + obj.getD();
|
||||||
|
return CACHE.computeIfAbsent(cacheKey, k -> doCompute(obj, k));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FloatingObjectFootprint doCompute(IrisObject obj, String cacheKey) {
|
||||||
|
int cx = obj.getCenter().getBlockX();
|
||||||
|
int cy = obj.getCenter().getBlockY();
|
||||||
|
int cz = obj.getCenter().getBlockZ();
|
||||||
|
Set<Long> xzSet = new HashSet<>();
|
||||||
|
Map<Long, int[]> columnStats = new HashMap<>();
|
||||||
|
|
||||||
|
obj.getBlocks().forEach((BlockVector key, BlockData bd) -> {
|
||||||
|
if (!B.isSolid(bd)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int kx = key.getBlockX();
|
||||||
|
int ky = key.getBlockY();
|
||||||
|
int kz = key.getBlockZ();
|
||||||
|
long packed = ((long) kx << 32) | (kz & 0xFFFFFFFFL);
|
||||||
|
xzSet.add(packed);
|
||||||
|
int[] stats = columnStats.get(packed);
|
||||||
|
if (stats == null) {
|
||||||
|
stats = new int[]{ky, 1};
|
||||||
|
columnStats.put(packed, stats);
|
||||||
|
} else {
|
||||||
|
if (ky < stats[0]) {
|
||||||
|
stats[0] = ky;
|
||||||
|
}
|
||||||
|
stats[1]++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
long tallestPacked = resolveTallestColumn(columnStats);
|
||||||
|
int lowestSolidKeyY = columnStats.isEmpty()
|
||||||
|
? cy
|
||||||
|
: columnStats.get(tallestPacked)[0];
|
||||||
|
int tallestKx = columnStats.isEmpty() ? 0 : (int) (tallestPacked >> 32);
|
||||||
|
int tallestKz = columnStats.isEmpty() ? 0 : (int) (tallestPacked & 0xFFFFFFFFL);
|
||||||
|
if (DIAGNOSTIC_LOG) {
|
||||||
|
logFootprintDiagnostic(cacheKey, obj, cx, cy, cz, lowestSolidKeyY, tallestKx, tallestKz, columnStats);
|
||||||
|
}
|
||||||
|
return new FloatingObjectFootprint(lowestSolidKeyY, cx, cy, cz, tallestKx, tallestKz, xzSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void logFootprintDiagnostic(String cacheKey, IrisObject obj, int cx, int cy, int cz, int anchorY, int tallestKx, int tallestKz, Map<Long, int[]> columnStats) {
|
||||||
|
if (columnStats.isEmpty()) {
|
||||||
|
Iris.info("[FloatingFootprint] key=" + cacheKey + " center=(" + cx + "," + cy + "," + cz + ") anchor=" + anchorY + " columns=0 (EMPTY)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tallestCount = 0;
|
||||||
|
int tallestLow = Integer.MAX_VALUE;
|
||||||
|
int floorLow = Integer.MAX_VALUE;
|
||||||
|
int ceilingLow = Integer.MIN_VALUE;
|
||||||
|
int minKx = Integer.MAX_VALUE, maxKx = Integer.MIN_VALUE;
|
||||||
|
int minKz = Integer.MAX_VALUE, maxKz = Integer.MIN_VALUE;
|
||||||
|
TreeMap<Integer, Integer> lowYHisto = new TreeMap<>();
|
||||||
|
for (Map.Entry<Long, int[]> e : columnStats.entrySet()) {
|
||||||
|
long packed = e.getKey();
|
||||||
|
int kx = (int) (packed >> 32);
|
||||||
|
int kz = (int) (packed & 0xFFFFFFFFL);
|
||||||
|
if (kx < minKx) minKx = kx;
|
||||||
|
if (kx > maxKx) maxKx = kx;
|
||||||
|
if (kz < minKz) minKz = kz;
|
||||||
|
if (kz > maxKz) maxKz = kz;
|
||||||
|
int[] s = e.getValue();
|
||||||
|
int count = s[1];
|
||||||
|
int low = s[0];
|
||||||
|
if (count > tallestCount || (count == tallestCount && low < tallestLow)) {
|
||||||
|
tallestCount = count;
|
||||||
|
tallestLow = low;
|
||||||
|
}
|
||||||
|
if (low < floorLow) floorLow = low;
|
||||||
|
if (low > ceilingLow) ceilingLow = low;
|
||||||
|
lowYHisto.merge(low, 1, Integer::sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
int straysBelowAnchor = 0;
|
||||||
|
for (int[] s : columnStats.values()) {
|
||||||
|
if (s[0] < anchorY) straysBelowAnchor++;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map.Entry<Long, int[]>> sorted = new ArrayList<>(columnStats.entrySet());
|
||||||
|
sorted.sort((a, b) -> {
|
||||||
|
int cmp = Integer.compare(b.getValue()[1], a.getValue()[1]);
|
||||||
|
if (cmp != 0) return cmp;
|
||||||
|
return Integer.compare(a.getValue()[0], b.getValue()[0]);
|
||||||
|
});
|
||||||
|
StringBuilder topN = new StringBuilder();
|
||||||
|
int showN = Math.min(4, sorted.size());
|
||||||
|
for (int i = 0; i < showN; i++) {
|
||||||
|
Map.Entry<Long, int[]> e = sorted.get(i);
|
||||||
|
int kx = (int) (e.getKey() >> 32);
|
||||||
|
int kz = (int) (e.getKey() & 0xFFFFFFFFL);
|
||||||
|
int[] s = e.getValue();
|
||||||
|
if (i > 0) topN.append(",");
|
||||||
|
topN.append("(").append(kx).append(",").append(kz).append(")c=").append(s[1]).append(":y=").append(s[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder histo = new StringBuilder();
|
||||||
|
int histoEntries = 0;
|
||||||
|
for (Map.Entry<Integer, Integer> e : lowYHisto.entrySet()) {
|
||||||
|
if (histoEntries++ > 0) histo.append(",");
|
||||||
|
histo.append(e.getKey()).append("=").append(e.getValue());
|
||||||
|
if (histoEntries >= 6) {
|
||||||
|
if (lowYHisto.size() > 6) histo.append(",...+").append(lowYHisto.size() - 6);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String keyStyle = (minKx >= 0 && minKz >= 0) ? "RAW" : "SIGNED";
|
||||||
|
|
||||||
|
Iris.info("[FloatingFootprint] key=" + cacheKey
|
||||||
|
+ " dims=" + obj.getW() + "x" + obj.getH() + "x" + obj.getD()
|
||||||
|
+ " center=(" + cx + "," + cy + "," + cz + ")"
|
||||||
|
+ " keyStyle=" + keyStyle
|
||||||
|
+ " kxRange=[" + minKx + "," + maxKx + "]"
|
||||||
|
+ " kzRange=[" + minKz + "," + maxKz + "]"
|
||||||
|
+ " cols=" + columnStats.size()
|
||||||
|
+ " anchor=" + anchorY
|
||||||
|
+ " relAnchor=" + (anchorY - cy)
|
||||||
|
+ " tallestKxKz=(" + tallestKx + "," + tallestKz + ")"
|
||||||
|
+ " floorLow=" + floorLow
|
||||||
|
+ " ceilingLow=" + ceilingLow
|
||||||
|
+ " tallest=count" + tallestCount + ":y" + tallestLow
|
||||||
|
+ " straysBelow=" + straysBelowAnchor
|
||||||
|
+ " topCols=[" + topN + "]"
|
||||||
|
+ " lowYHisto={" + histo + "}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long resolveTallestColumn(Map<Long, int[]> columnStats) {
|
||||||
|
long bestPacked = 0L;
|
||||||
|
int tallestCount = 0;
|
||||||
|
int tallestLow = Integer.MAX_VALUE;
|
||||||
|
for (Map.Entry<Long, int[]> e : columnStats.entrySet()) {
|
||||||
|
int[] stats = e.getValue();
|
||||||
|
int count = stats[1];
|
||||||
|
int low = stats[0];
|
||||||
|
if (count > tallestCount || (count == tallestCount && low < tallestLow)) {
|
||||||
|
tallestCount = count;
|
||||||
|
tallestLow = low;
|
||||||
|
bestPacked = e.getKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestPacked;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean columnInFootprint(int objKeyX, int objKeyZ) {
|
||||||
|
return footprintXZ.contains(((long) objKeyX << 32) | (objKeyZ & 0xFFFFFFFFL));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int lowestSolidRelCenterY() {
|
||||||
|
return lowestSolidKeyY - centerY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLowestSolidKeyY() {
|
||||||
|
return lowestSolidKeyY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCenterX() {
|
||||||
|
return centerX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCenterY() {
|
||||||
|
return centerY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCenterZ() {
|
||||||
|
return centerZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTallestKx() {
|
||||||
|
return tallestKx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTallestKz() {
|
||||||
|
return tallestKz;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Long> footprintXZ() {
|
||||||
|
return footprintXZ;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -114,13 +114,13 @@ public class IrisFloatingChildBiomes implements IRare {
|
|||||||
|
|
||||||
@MinNumber(0)
|
@MinNumber(0)
|
||||||
@MaxNumber(2032)
|
@MaxNumber(2032)
|
||||||
@Desc("Minimum blocks above the parent biome surface where the island base can sit.")
|
@Desc("Minimum absolute world Y where the island base can sit. Island altitude is independent of the parent biome's terrain height; altitudeStyle noise varies the base between min and max per column.")
|
||||||
private int minHeightAboveSurface = 60;
|
private int minHeightAboveSurface = 160;
|
||||||
|
|
||||||
@MinNumber(0)
|
@MinNumber(0)
|
||||||
@MaxNumber(2032)
|
@MaxNumber(2032)
|
||||||
@Desc("Maximum blocks above the parent biome surface where the island base can sit.")
|
@Desc("Maximum absolute world Y where the island base can sit. Island altitude is independent of the parent biome's terrain height; altitudeStyle noise varies the base between min and max per column.")
|
||||||
private int maxHeightAboveSurface = 110;
|
private int maxHeightAboveSurface = 210;
|
||||||
|
|
||||||
@Desc("Optional absolute minimum world Y for the island base. When set, baseY is clamped upward so the tail bottom stays above this value.")
|
@Desc("Optional absolute minimum world Y for the island base. When set, baseY is clamped upward so the tail bottom stays above this value.")
|
||||||
private Integer minAbsoluteY = null;
|
private Integer minAbsoluteY = null;
|
||||||
|
|||||||
@@ -60,8 +60,6 @@ import net.minecraft.world.level.dimension.LevelStem;
|
|||||||
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||||
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||||
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||||
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
|
|
||||||
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
|
|
||||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||||
import net.minecraft.world.level.storage.ServerLevelData;
|
import net.minecraft.world.level.storage.ServerLevelData;
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
@@ -86,28 +84,15 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.FileSystem;
|
|
||||||
import java.nio.file.FileSystems;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipFile;
|
|
||||||
|
|
||||||
public class NMSBinding implements INMSBinding {
|
public class NMSBinding implements INMSBinding {
|
||||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||||
@@ -788,96 +773,6 @@ public class NMSBinding implements INMSBinding {
|
|||||||
return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName);
|
return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Pattern VANILLA_DATAPACK_ENTRY = Pattern.compile(
|
|
||||||
"^data/minecraft/(?:worldgen/(?:structure|structure_set|template_pool|processor_list|configured_feature|placed_feature)/.+\\.json"
|
|
||||||
+ "|structures?/.+\\.nbt"
|
|
||||||
+ "|tags/worldgen/biome/has_structure/.+\\.json)$"
|
|
||||||
);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, byte[]> extractVanillaDatapack() {
|
|
||||||
Map<String, byte[]> entries = new LinkedHashMap<>();
|
|
||||||
File serverJar = resolveServerJar();
|
|
||||||
if (serverJar == null) {
|
|
||||||
Iris.error("Unable to locate server JAR for vanilla datapack extraction.");
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
Iris.info("Extracting vanilla datapack from " + serverJar.getName() + "...");
|
|
||||||
try (ZipFile zip = new ZipFile(serverJar)) {
|
|
||||||
Enumeration<? extends ZipEntry> zipEntries = zip.entries();
|
|
||||||
while (zipEntries.hasMoreElements()) {
|
|
||||||
ZipEntry entry = zipEntries.nextElement();
|
|
||||||
if (entry.isDirectory()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String name = entry.getName();
|
|
||||||
if (!VANILLA_DATAPACK_ENTRY.matcher(name).matches()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String datapackPath = normalizeStructurePath(name);
|
|
||||||
try (InputStream is = zip.getInputStream(entry)) {
|
|
||||||
entries.put(datapackPath, is.readAllBytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Iris.error("Failed to read vanilla datapack entries from " + serverJar.getName());
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
Iris.info("Extracted " + entries.size() + " vanilla datapack entries.");
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String normalizeStructurePath(String path) {
|
|
||||||
return path.replace("data/minecraft/structures/", "data/minecraft/structure/");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static File resolveServerJar() {
|
|
||||||
try {
|
|
||||||
URL url = MinecraftServer.class.getProtectionDomain().getCodeSource().getLocation();
|
|
||||||
if (url != null) {
|
|
||||||
File file = Path.of(url.toURI()).toFile();
|
|
||||||
if (file.isFile() && file.getName().endsWith(".jar")) {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
if (file.isDirectory()) {
|
|
||||||
return resolveServerJarFromDirectory(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
String classpath = System.getProperty("java.class.path", "");
|
|
||||||
for (String entry : classpath.split(File.pathSeparator)) {
|
|
||||||
File file = new File(entry);
|
|
||||||
if (file.isFile() && file.getName().endsWith(".jar") && containsVanillaData(file)) {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static File resolveServerJarFromDirectory(File dir) {
|
|
||||||
File[] jars = dir.listFiles((d, name) -> name.endsWith(".jar"));
|
|
||||||
if (jars == null) return null;
|
|
||||||
for (File jar : jars) {
|
|
||||||
if (containsVanillaData(jar)) {
|
|
||||||
return jar;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean containsVanillaData(File jar) {
|
|
||||||
try (ZipFile zip = new ZipFile(jar)) {
|
|
||||||
return zip.getEntry("data/minecraft/worldgen/structure_set/villages.json") != null
|
|
||||||
|| zip.getEntry("data/minecraft/worldgen/structure_set/village_plains.json") != null;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object createRuntimeLevelStem(Object registryAccess, ChunkGenerator raw) {
|
public Object createRuntimeLevelStem(Object registryAccess, ChunkGenerator raw) {
|
||||||
if (!(registryAccess instanceof RegistryAccess access)) {
|
if (!(registryAccess instanceof RegistryAccess access)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user