mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-05-19 16:10:42 +00:00
floatt
This commit is contained in:
@@ -46,7 +46,6 @@ import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface INMSBinding {
|
||||
@@ -162,10 +161,6 @@ public interface INMSBinding {
|
||||
|
||||
KMap<Material, List<BlockProperty>> getBlockProperties();
|
||||
|
||||
default Map<String, byte[]> extractVanillaDatapack() {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
private void validateDimensionTypes(WorldCreator c) {
|
||||
if (c.generator() instanceof PlatformChunkGenerator gen
|
||||
&& 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.MantleWriter;
|
||||
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.FloatingObjectFootprint;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisFloatingChildBiomes;
|
||||
import art.arcane.iris.engine.object.IrisObject;
|
||||
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.util.project.context.ChunkContext;
|
||||
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.math.RNG;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@ComponentFlag(ReservedFlag.FLOATING_OBJECT)
|
||||
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) {
|
||||
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
|
||||
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
|
||||
IrisComplex complex = context.getComplex();
|
||||
@@ -68,7 +165,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
if (parent == null || parent.getFloatingChildBiomes() == null || parent.getFloatingChildBiomes().isEmpty()) {
|
||||
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) {
|
||||
samples[(zf << 4) | xf] = sample;
|
||||
}
|
||||
@@ -103,14 +200,14 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
|
||||
if (entry.isInheritObjects() && target != null) {
|
||||
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();
|
||||
if (extras != null && !extras.isEmpty()) {
|
||||
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);
|
||||
double perAttempt = placement.getChance();
|
||||
for (int i = 0; i < density; i++) {
|
||||
objectsAttempted.incrementAndGet();
|
||||
if (!rng.chance(perAttempt + rng.d(-0.005, 0.005))) {
|
||||
continue;
|
||||
}
|
||||
IrisObject raw = placement.getObject(complex, rng);
|
||||
if (raw == null) {
|
||||
objectsSkippedNullObj.incrementAndGet();
|
||||
continue;
|
||||
}
|
||||
IrisObject obj0 = placement.getScale().get(rng, raw);
|
||||
if (obj0 == null) {
|
||||
objectsSkippedShrink.incrementAndGet();
|
||||
continue;
|
||||
}
|
||||
if (entry != null && entry.hasObjectShrink()) {
|
||||
obj0 = entry.getShrinkScale().get(rng, obj0);
|
||||
if (obj0 == null) {
|
||||
objectsSkippedShrink.incrementAndGet();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -156,6 +257,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
writer.setData(b.getX(), b.getY(), b.getZ(), marker);
|
||||
}
|
||||
}, null, data);
|
||||
objectsPlaced.incrementAndGet();
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
}
|
||||
@@ -163,67 +265,138 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
}
|
||||
|
||||
@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()) {
|
||||
return;
|
||||
}
|
||||
KList<Integer> interior = interiorColumns(samples, columns);
|
||||
KList<Integer> pickPool = interior.isEmpty() ? columns : interior;
|
||||
int density = placement.getDensity(rng, minX, minZ, data);
|
||||
double perAttempt = placement.getChance();
|
||||
KList<Integer> interior = interiorColumns(samples, columns);
|
||||
|
||||
for (int i = 0; i < density; i++) {
|
||||
objectsAttempted.incrementAndGet();
|
||||
if (!rng.chance(perAttempt + rng.d(-0.005, 0.005))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IrisObject raw = placement.getObject(complex, rng);
|
||||
if (raw == null) {
|
||||
objectsSkippedNullObj.incrementAndGet();
|
||||
continue;
|
||||
}
|
||||
IrisObject obj0 = placement.getScale().get(rng, raw);
|
||||
if (obj0 == null) {
|
||||
objectsSkippedShrink.incrementAndGet();
|
||||
continue;
|
||||
}
|
||||
if (entry != null && entry.hasObjectShrink()) {
|
||||
obj0 = entry.getShrinkScale().get(rng, obj0);
|
||||
if (obj0 == null) {
|
||||
objectsSkippedShrink.incrementAndGet();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
final IrisObject obj = obj0;
|
||||
|
||||
int key = pickPool.get(rng.i(0, pickPool.size() - 1));
|
||||
int xf = key & 15;
|
||||
int zf = key >> 4;
|
||||
FloatingIslandSample sample = samples[(zf << 4) | xf];
|
||||
if (sample == null) {
|
||||
FloatingObjectFootprint fp = FloatingObjectFootprint.compute(obj);
|
||||
|
||||
KList<Integer> pool = interior.isEmpty() ? columns : interior;
|
||||
if (interior.isEmpty()) {
|
||||
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;
|
||||
}
|
||||
int wx = minX + xf;
|
||||
int wz = minZ + zf;
|
||||
int pickTopY = pickedSample.topY();
|
||||
|
||||
int anchorY = sample.topY() + 1 + obj.getCenter().getBlockY();
|
||||
int id = rng.i(0, Integer.MAX_VALUE);
|
||||
if (!isFootprintFlat(fp, pickedXf, pickedZf, pickTopY, samples, 2)) {
|
||||
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());
|
||||
anchored.setCarvingSupport(CarvingMode.ANYWHERE);
|
||||
anchored.setMode(translateStiltModeForFloating(anchored.getMode()));
|
||||
anchored.setTranslate(new IrisObjectTranslate());
|
||||
anchored.setRotation(IrisObjectRotation.of(0, 0, 0));
|
||||
anchored.setForcePlace(true);
|
||||
anchored.setMode(ObjectPlaceMode.STRUCTURE_PIECE);
|
||||
anchored.setBore(false);
|
||||
anchored.setMeld(false);
|
||||
anchored.setBottom(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 {
|
||||
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);
|
||||
if (marker != null) {
|
||||
writer.setData(b.getX(), b.getY(), b.getZ(), marker);
|
||||
}
|
||||
}, 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) {
|
||||
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) {
|
||||
KList<Integer> interior = new KList<>();
|
||||
for (int key : columns) {
|
||||
@@ -252,23 +425,64 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
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
|
||||
protected int computeRadius() {
|
||||
int maxThickness = 0;
|
||||
int maxHeightAbove = 0;
|
||||
int maxObjectExtent = 0;
|
||||
java.util.Set<String> objectKeys = new java.util.HashSet<>();
|
||||
try {
|
||||
IrisData data = getData();
|
||||
for (IrisBiome biome : getDimension().getAllBiomes(this::getData)) {
|
||||
KList<IrisFloatingChildBiomes> entries = biome.getFloatingChildBiomes();
|
||||
if (entries == null || entries.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (IrisFloatingChildBiomes entry : entries) {
|
||||
maxThickness = Math.max(maxThickness, entry.getMaxThickness());
|
||||
maxHeightAbove = Math.max(maxHeightAbove, entry.getMaxHeightAboveSurface());
|
||||
collectPlacementKeys(entry.getFloatingObjects(), objectKeys);
|
||||
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) {
|
||||
}
|
||||
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.nms.INMS;
|
||||
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 static art.arcane.iris.engine.mantle.EngineMantle.AIR;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.EngineAssignedModifier;
|
||||
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.IrisBiome;
|
||||
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.IrisFloatingChildBiomes;
|
||||
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.data.BlockData;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<BlockData> {
|
||||
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 java.util.concurrent.atomic.AtomicLong samplesAccepted = new java.util.concurrent.atomic.AtomicLong();
|
||||
private static final java.util.concurrent.atomic.AtomicLong decorateInvocations = new java.util.concurrent.atomic.AtomicLong();
|
||||
private static final java.util.concurrent.atomic.AtomicLong decorateSkippedNotAir = new java.util.concurrent.atomic.AtomicLong();
|
||||
private static final java.util.concurrent.atomic.AtomicLong decorateSkippedNoInherit = new java.util.concurrent.atomic.AtomicLong();
|
||||
private static final java.util.concurrent.atomic.AtomicLong decoratePhaseColumns = new java.util.concurrent.atomic.AtomicLong();
|
||||
private static final java.util.concurrent.atomic.AtomicLong decoratePlaced = new java.util.concurrent.atomic.AtomicLong();
|
||||
private static final java.util.concurrent.atomic.AtomicLong decorateNoChange = new java.util.concurrent.atomic.AtomicLong();
|
||||
private static final java.util.concurrent.atomic.AtomicLong decorateFloorNull = new java.util.concurrent.atomic.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.atomic.AtomicLong lastReportMs = new java.util.concurrent.atomic.AtomicLong(0L);
|
||||
private static final AtomicLong columnsChecked = new AtomicLong();
|
||||
private static final AtomicLong samplesAccepted = new AtomicLong();
|
||||
private static final AtomicLong decorateInvocations = new AtomicLong();
|
||||
private static final AtomicLong decorateSkippedNotAir = new AtomicLong();
|
||||
private static final AtomicLong decorateSkippedNoInherit = new AtomicLong();
|
||||
private static final AtomicLong decoratePhaseColumns = new AtomicLong();
|
||||
private static final AtomicLong decoratePlaced = new AtomicLong();
|
||||
private static final AtomicLong decorateNoChange = new AtomicLong();
|
||||
private static final AtomicLong decorateFloorNull = new AtomicLong();
|
||||
private static final java.util.concurrent.ConcurrentHashMap<String, AtomicLong> floorMatHisto = new java.util.concurrent.ConcurrentHashMap<>();
|
||||
private static final AtomicLong lastReportMs = new AtomicLong(0L);
|
||||
private static final AtomicLong reportCycle = new AtomicLong(0L);
|
||||
private final RNG rng;
|
||||
private final EngineDecorator surfaceDecorator;
|
||||
private final EngineDecorator seaSurfaceDecorator;
|
||||
|
||||
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()))
|
||||
.limit(5)
|
||||
.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()
|
||||
+ " decInvoke=" + decorateInvocations.get()
|
||||
+ " decPlaced=" + decoratePlaced.get()
|
||||
+ " decNoChange=" + decorateNoChange.get()
|
||||
+ " decFloorNull=" + decorateFloorNull.get()
|
||||
+ " decCandidatesNull=" + FloatingDecorator.decCandidatesNull.get()
|
||||
+ " decSkipNonAir=" + decorateSkippedNotAir.get()
|
||||
+ " decSkipNoInherit=" + decorateSkippedNoInherit.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()));
|
||||
}
|
||||
|
||||
@@ -83,13 +108,44 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
||||
long last = lastReportMs.get();
|
||||
if (now - last >= 10000L && lastReportMs.compareAndSet(last, now)) {
|
||||
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) {
|
||||
super(engine, "FloatingChildBiomes");
|
||||
rng = new RNG(engine.getSeedManager().getTerrain() ^ 0x7EB0A73F1DCE514DL);
|
||||
surfaceDecorator = new IrisFloatingSurfaceDecorator(engine);
|
||||
seaSurfaceDecorator = new IrisSeaSurfaceDecorator(engine);
|
||||
}
|
||||
|
||||
@@ -112,7 +168,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
||||
}
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
@@ -202,7 +258,7 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
||||
if (parent == null || parent.getFloatingChildBiomes() == null || parent.getFloatingChildBiomes().isEmpty()) {
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
@@ -225,20 +281,19 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier<Block
|
||||
if (floor == null) {
|
||||
decorateFloorNull.incrementAndGet();
|
||||
} else {
|
||||
String matKey = floor.getMaterial().getKey().getKey();
|
||||
floorMatHisto.computeIfAbsent(matKey, k -> new java.util.concurrent.atomic.AtomicLong()).incrementAndGet();
|
||||
recordFloorMat(floor.getMaterial().getKey().getKey());
|
||||
}
|
||||
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) {
|
||||
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 {
|
||||
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))) {
|
||||
try {
|
||||
seaSurfaceDecorator.decorate(xf, zf,
|
||||
wx, wx + 1, wx - 1,
|
||||
wz, wz + 1, wz - 1,
|
||||
output, target, fluidTopY, chunkHeight);
|
||||
seaSurfaceDecorator.decorate(xf, zf, wx, wx + 1, wx - 1, wz, wz + 1, wz - 1, output, target, fluidTopY, chunkHeight);
|
||||
} catch (Throwable 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.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
@@ -63,13 +62,13 @@ public final class FloatingIslandSample {
|
||||
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);
|
||||
HashMap<Long, FloatingIslandSample> memo = CHUNK_MEMO.get();
|
||||
if (memo.containsKey(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);
|
||||
return result;
|
||||
}
|
||||
@@ -111,7 +110,7 @@ public final class FloatingIslandSample {
|
||||
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();
|
||||
if (entries == null || entries.isEmpty()) {
|
||||
return reject(REJECT_NO_ENTRIES);
|
||||
@@ -153,8 +152,6 @@ public final class FloatingIslandSample {
|
||||
return reject(REJECT_NO_SEED);
|
||||
}
|
||||
|
||||
int surfaceY = (int) Math.round(complex.getHeightStream().get(wx & ~63, wz & ~63));
|
||||
|
||||
CNG altitudeCng = entry.getAltitudeCng(baseSeed, data);
|
||||
if (altitudeCng == null) {
|
||||
warnNullCng("altitudeStyle", parent);
|
||||
@@ -162,9 +159,10 @@ public final class FloatingIslandSample {
|
||||
}
|
||||
double altNoise = altitudeCng.noise(wx, wz);
|
||||
double altClamped = Math.max(0, Math.min(1, altNoise));
|
||||
int minAlt = Math.max(0, entry.getMinHeightAboveSurface());
|
||||
int maxAlt = Math.max(minAlt, entry.getMaxHeightAboveSurface());
|
||||
int baseY = surfaceY + minAlt + (int) Math.round(altClamped * (maxAlt - minAlt));
|
||||
int worldMin = engine.getWorld().minHeight();
|
||||
int minAlt = Math.max(0, entry.getMinHeightAboveSurface() - worldMin);
|
||||
int maxAlt = Math.max(minAlt, entry.getMaxHeightAboveSurface() - worldMin);
|
||||
int baseY = minAlt + (int) Math.round(altClamped * (maxAlt - minAlt));
|
||||
|
||||
IrisBiome target = entry.getRealBiome(parent, data);
|
||||
int topH = computeTopHeight(entry, target, engine, baseSeed, wx, wz, data);
|
||||
@@ -188,12 +186,12 @@ public final class FloatingIslandSample {
|
||||
int botY = baseY - depth;
|
||||
|
||||
Integer minAbsoluteY = entry.getMinAbsoluteY();
|
||||
if (minAbsoluteY != null && botY < minAbsoluteY) {
|
||||
botY = minAbsoluteY;
|
||||
if (minAbsoluteY != null && botY < minAbsoluteY - worldMin) {
|
||||
botY = minAbsoluteY - worldMin;
|
||||
}
|
||||
Integer maxAbsoluteY = entry.getMaxAbsoluteY();
|
||||
if (maxAbsoluteY != null && topY > maxAbsoluteY) {
|
||||
topY = maxAbsoluteY;
|
||||
if (maxAbsoluteY != null && topY > maxAbsoluteY - worldMin) {
|
||||
topY = maxAbsoluteY - worldMin;
|
||||
}
|
||||
|
||||
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)
|
||||
@MaxNumber(2032)
|
||||
@Desc("Minimum blocks above the parent biome surface where the island base can sit.")
|
||||
private int minHeightAboveSurface = 60;
|
||||
@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 = 160;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(2032)
|
||||
@Desc("Maximum blocks above the parent biome surface where the island base can sit.")
|
||||
private int maxHeightAboveSurface = 110;
|
||||
@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 = 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.")
|
||||
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.flat.FlatLayerInfo;
|
||||
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.ServerLevelData;
|
||||
import org.bukkit.*;
|
||||
@@ -86,28 +84,15 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
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.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
public Object createRuntimeLevelStem(Object registryAccess, ChunkGenerator raw) {
|
||||
if (!(registryAccess instanceof RegistryAccess access)) {
|
||||
|
||||
Reference in New Issue
Block a user