This commit is contained in:
Brian Neumann-Fopiano
2026-04-22 16:33:25 -04:00
parent 6df718e6ca
commit 23fad24fb7
5 changed files with 207 additions and 70 deletions
@@ -0,0 +1,119 @@
/*
* 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.core.commands;
import art.arcane.iris.Iris;
import art.arcane.iris.core.service.ObjectStudioSaveService;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.util.common.director.DirectorExecutor;
import art.arcane.iris.util.common.director.specialhandlers.ObjectHandler;
import art.arcane.iris.util.common.format.C;
import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.director.annotations.Param;
import org.bukkit.entity.Player;
@Director(name = "find", origin = DirectorOrigin.PLAYER, description = "Iris Find commands", aliases = "goto")
public class CommandFind implements DirectorExecutor {
@Director(description = "Find a biome")
public void biome(
@Param(description = "The biome to look for")
IrisBiome biome,
@Param(description = "Should you be teleported", defaultValue = "true")
boolean teleport
) {
Engine e = engine();
if (e == null) {
sender().sendMessage(C.GOLD + "Not in an Iris World!");
return;
}
e.gotoBiome(biome, player(), teleport);
}
@Director(description = "Find a region")
public void region(
@Param(description = "The region to look for")
IrisRegion region,
@Param(description = "Should you be teleported", defaultValue = "true")
boolean teleport
) {
Engine e = engine();
if (e == null) {
sender().sendMessage(C.GOLD + "Not in an Iris World!");
return;
}
e.gotoRegion(region, player(), teleport);
}
@Director(description = "Find a point of interest.")
public void poi(
@Param(description = "The type of PoI to look for.")
String type,
@Param(description = "Should you be teleported", defaultValue = "true")
boolean teleport
) {
Engine e = engine();
if (e == null) {
sender().sendMessage(C.GOLD + "Not in an Iris World!");
return;
}
e.gotoPOI(type, player(), teleport);
}
@Director(description = "Find an object")
public void object(
@Param(description = "The object to look for", customHandler = ObjectHandler.class)
String object,
@Param(description = "Should you be teleported", defaultValue = "true")
boolean teleport
) {
Engine e = engine();
if (e == null) {
sender().sendMessage(C.GOLD + "Not in an Iris World!");
return;
}
Player studioPlayer = player();
if (studioPlayer != null) {
try {
if (ObjectStudioSaveService.get().teleportTo(studioPlayer, object)) {
sender().sendMessage(C.GREEN + "Object Studio: teleporting to " + object);
return;
}
} catch (Throwable t) {
Iris.reportError(t);
}
}
if (e.hasObjectPlacement(object)) {
e.gotoObject(object, player(), teleport);
return;
}
sender().sendMessage(C.RED + object + " is not configured in any region/biome object placements.");
}
}
@@ -101,6 +101,7 @@ public class CommandIris implements DirectorExecutor {
private CommandEdit edit; private CommandEdit edit;
private CommandDeveloper developer; private CommandDeveloper developer;
private CommandPack pack; private CommandPack pack;
private CommandFind find;
public static boolean worldCreation = false; public static boolean worldCreation = false;
private static final AtomicReference<Thread> mainWorld = new AtomicReference<>(); private static final AtomicReference<Thread> mainWorld = new AtomicReference<>();
String WorldEngine; String WorldEngine;
@@ -33,6 +33,7 @@ public class IslandObjectPlacer implements IObjectPlacer {
private final MantleWriter wrapped; private final MantleWriter wrapped;
private final FloatingIslandSample[] samples; private final FloatingIslandSample[] samples;
private final boolean[] overhangAllowed;
private final int minX; private final int minX;
private final int minZ; private final int minZ;
private final int chunkMaxIslandTopY; private final int chunkMaxIslandTopY;
@@ -57,6 +58,38 @@ public class IslandObjectPlacer implements IObjectPlacer {
} }
} }
this.chunkMaxIslandTopY = maxY; this.chunkMaxIslandTopY = maxY;
this.overhangAllowed = buildOverhangMask(samples);
}
private static boolean[] buildOverhangMask(FloatingIslandSample[] samples) {
boolean[] mask = new boolean[256];
for (int zf = 0; zf < 16; zf++) {
for (int xf = 0; xf < 16; xf++) {
int idx = (zf << 4) | xf;
if (samples[idx] != null) {
mask[idx] = true;
continue;
}
boolean touchedEdge = false;
boolean found = false;
for (int dz = -OVERHANG_RADIUS; dz <= OVERHANG_RADIUS && !found; dz++) {
int nzf = zf + dz;
for (int dx = -OVERHANG_RADIUS; dx <= OVERHANG_RADIUS; dx++) {
int nxf = xf + dx;
if (nxf < 0 || nxf >= 16 || nzf < 0 || nzf >= 16) {
touchedEdge = true;
continue;
}
if (samples[(nzf << 4) | nxf] != null) {
found = true;
break;
}
}
}
mask[idx] = found || touchedEdge;
}
}
return mask;
} }
public int getWritesAttempted() { public int getWritesAttempted() {
@@ -73,38 +106,29 @@ public class IslandObjectPlacer implements IObjectPlacer {
private boolean shouldSkipAirColumn(int x, int y, int z) { private boolean shouldSkipAirColumn(int x, int y, int z) {
writesAttempted++; writesAttempted++;
if (sampleAt(x, z) != null) { int xf = x - minX;
int zf = z - minZ;
if (xf >= 0 && xf < 16 && zf >= 0 && zf < 16) {
int idx = (zf << 4) | xf;
if (samples[idx] != null) {
return false;
}
if (y <= anchorTopY) {
writesDroppedBelow++;
return true;
}
if (!overhangAllowed[idx]) {
writesDroppedOverhang++;
return true;
}
return false; return false;
} }
if (y <= anchorTopY) { if (y <= anchorTopY) {
writesDroppedBelow++; writesDroppedBelow++;
return true; return true;
} }
if (!hasIslandNeighborWithin(x, z, OVERHANG_RADIUS)) { writesDroppedOverhang++;
writesDroppedOverhang++; return true;
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) { private @Nullable FloatingIslandSample sampleAt(int x, int z) {
@@ -62,6 +62,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
private static final int HEAVY_CLIP_WARNING_CAP = 30; private static final int HEAVY_CLIP_WARNING_CAP = 30;
private static final double HEAVY_CLIP_RATIO = 0.5; private static final double HEAVY_CLIP_RATIO = 0.5;
private static final int MIN_FOOTPRINT_CELLS_CHECKED = 3; private static final int MIN_FOOTPRINT_CELLS_CHECKED = 3;
private static final IrisObjectRotation ROTATION_NONE = IrisObjectRotation.of(0, 0, 0);
public static final java.util.concurrent.ConcurrentHashMap<String, AtomicLong> anchorYHisto = new java.util.concurrent.ConcurrentHashMap<>(); public static final java.util.concurrent.ConcurrentHashMap<String, AtomicLong> anchorYHisto = new java.util.concurrent.ConcurrentHashMap<>();
public MantleFloatingObjectComponent(EngineMantle engineMantle) { public MantleFloatingObjectComponent(EngineMantle engineMantle) {
@@ -106,23 +107,15 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
} }
} }
private static void verifyTerrainBelowObject(MantleWriter writer, IrisObject obj, int wx, int wz, int pickTopY, FloatingIslandSample sample) { private static void verifyTerrainBelowObject(IrisObject obj, int wx, int wz, int pickTopY, FloatingIslandSample sample) {
long warned = terrainMismatchWarnings.get(); if (terrainMismatchWarnings.get() >= TERRAIN_MISMATCH_WARNING_CAP) {
if (warned >= TERRAIN_MISMATCH_WARNING_CAP) {
return; return;
} }
boolean mantleSolid; if (sample != null
try {
mantleSolid = writer.isSolid(wx, pickTopY, wz);
} catch (Throwable t) {
mantleSolid = false;
}
boolean sampleSolid = sample != null
&& sample.solidMask != null && sample.solidMask != null
&& sample.topIdx >= 0 && sample.topIdx >= 0
&& sample.topIdx < sample.solidMask.length && sample.topIdx < sample.solidMask.length
&& sample.solidMask[sample.topIdx]; && sample.solidMask[sample.topIdx]) {
if (mantleSolid && sampleSolid) {
return; return;
} }
if (terrainMismatchWarnings.incrementAndGet() > TERRAIN_MISMATCH_WARNING_CAP) { if (terrainMismatchWarnings.incrementAndGet() > TERRAIN_MISMATCH_WARNING_CAP) {
@@ -135,9 +128,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
String sampleMaskLen = sample == null || sample.solidMask == null ? "null" : String.valueOf(sample.solidMask.length); String sampleMaskLen = sample == null || sample.solidMask == null ? "null" : String.valueOf(sample.solidMask.length);
Iris.warn("[FloatingTerrainCheck] object=" + objKey Iris.warn("[FloatingTerrainCheck] object=" + objKey
+ " at=(" + wx + "," + (pickTopY + 1) + "," + wz + ")" + " at=(" + wx + "," + (pickTopY + 1) + "," + wz + ")"
+ " expected solid below at y=" + pickTopY + " sample reports non-solid trunk column"
+ " mantleSolid=" + mantleSolid
+ " sampleSolid=" + sampleSolid
+ " sampleTopY=" + sampleTop + " sampleTopY=" + sampleTop
+ " sampleBaseY=" + sampleBase + " sampleBaseY=" + sampleBase
+ " sampleTopIdx=" + sampleTopIdx + " sampleTopIdx=" + sampleTopIdx
@@ -198,16 +189,21 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
} }
} }
if (entry.isInheritObjects() && target != null) { KList<IrisObjectPlacement> surface = entry.isInheritObjects() && target != null ? target.getSurfaceObjects() : null;
for (IrisObjectPlacement placement : target.getSurfaceObjects()) {
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()) { boolean hasSurface = surface != null && !surface.isEmpty();
for (IrisObjectPlacement placement : extras) { boolean hasExtras = extras != null && !extras.isEmpty();
tryPlaceAnchoredChunk(writer, complex, chunkRng, data, placement, samples, columns, minX, minZ, entry, target); if (hasSurface || hasExtras) {
KList<Integer> interior = interiorColumns(samples, columns);
if (hasSurface) {
for (IrisObjectPlacement placement : surface) {
tryPlaceAnchoredChunk(writer, complex, chunkRng, data, placement, samples, columns, interior, minX, minZ, entry);
}
}
if (hasExtras) {
for (IrisObjectPlacement placement : extras) {
tryPlaceAnchoredChunk(writer, complex, chunkRng, data, placement, samples, columns, interior, minX, minZ, entry);
}
} }
} }
} }
@@ -265,13 +261,12 @@ 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, IrisBiome target) { private void tryPlaceAnchoredChunk(MantleWriter writer, IrisComplex complex, RNG rng, IrisData data, IrisObjectPlacement placement, FloatingIslandSample[] samples, KList<Integer> columns, KList<Integer> interior, int minX, int minZ, IrisFloatingChildBiomes entry) {
if (placement == null || columns.isEmpty()) { if (placement == null || columns.isEmpty()) {
return; return;
} }
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(); objectsAttempted.incrementAndGet();
@@ -329,7 +324,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
IrisObjectPlacement anchored = placement.toPlacement(obj.getLoadKey()); IrisObjectPlacement anchored = placement.toPlacement(obj.getLoadKey());
anchored.setMode(translateStiltModeForFloating(anchored.getMode())); anchored.setMode(translateStiltModeForFloating(anchored.getMode()));
anchored.setTranslate(new IrisObjectTranslate()); anchored.setTranslate(new IrisObjectTranslate());
anchored.setRotation(IrisObjectRotation.of(0, 0, 0)); anchored.setRotation(ROTATION_NONE);
anchored.setForcePlace(true); anchored.setForcePlace(true);
anchored.setBottom(false); anchored.setBottom(false);
@@ -349,7 +344,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
recordAnchorYHisto(pickTopY); recordAnchorYHisto(pickTopY);
int trunkWx = minX + pickedXf; int trunkWx = minX + pickedXf;
int trunkWz = minZ + pickedZf; int trunkWz = minZ + pickedZf;
verifyTerrainBelowObject(writer, obj, trunkWx, trunkWz, pickTopY, pickedSample); verifyTerrainBelowObject(obj, trunkWx, trunkWz, pickTopY, pickedSample);
recordWriteStats(obj, trunkWx, trunkWz, pickTopY, islandPlacer); recordWriteStats(obj, trunkWx, trunkWz, pickTopY, islandPlacer);
} catch (Throwable e) { } catch (Throwable e) {
Iris.reportError(e); Iris.reportError(e);
@@ -362,7 +357,9 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
int tallestKz = fp.getTallestKz(); int tallestKz = fp.getTallestKz();
int checked = 0; int checked = 0;
boolean touchedChunkEdge = false; boolean touchedChunkEdge = false;
for (long encoded : fp.footprintXZ()) { long[] cells = fp.footprintXZ();
for (int i = 0, n = cells.length; i < n; i++) {
long encoded = cells[i];
int kx = (int) (encoded >> 32); int kx = (int) (encoded >> 32);
int kz = (int) (encoded & 0xFFFFFFFFL); int kz = (int) (encoded & 0xFFFFFFFFL);
int colXf = pickedXf + (kx - tallestKx); int colXf = pickedXf + (kx - tallestKx);
@@ -24,13 +24,9 @@ import org.bukkit.block.data.BlockData;
import org.bukkit.util.BlockVector; import org.bukkit.util.BlockVector;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -44,16 +40,16 @@ public class FloatingObjectFootprint {
private final int centerZ; private final int centerZ;
private final int tallestKx; private final int tallestKx;
private final int tallestKz; private final int tallestKz;
private final Set<Long> footprintXZ; private final long[] footprintXZ;
private FloatingObjectFootprint(int lowestSolidKeyY, int centerX, int centerY, int centerZ, int tallestKx, int tallestKz, Set<Long> footprintXZ) { private FloatingObjectFootprint(int lowestSolidKeyY, int centerX, int centerY, int centerZ, int tallestKx, int tallestKz, long[] footprintXZ) {
this.lowestSolidKeyY = lowestSolidKeyY; this.lowestSolidKeyY = lowestSolidKeyY;
this.centerX = centerX; this.centerX = centerX;
this.centerY = centerY; this.centerY = centerY;
this.centerZ = centerZ; this.centerZ = centerZ;
this.tallestKx = tallestKx; this.tallestKx = tallestKx;
this.tallestKz = tallestKz; this.tallestKz = tallestKz;
this.footprintXZ = Collections.unmodifiableSet(footprintXZ); this.footprintXZ = footprintXZ;
} }
public static FloatingObjectFootprint compute(IrisObject obj) { public static FloatingObjectFootprint compute(IrisObject obj) {
@@ -65,7 +61,6 @@ public class FloatingObjectFootprint {
int cx = obj.getCenter().getBlockX(); int cx = obj.getCenter().getBlockX();
int cy = obj.getCenter().getBlockY(); int cy = obj.getCenter().getBlockY();
int cz = obj.getCenter().getBlockZ(); int cz = obj.getCenter().getBlockZ();
Set<Long> xzSet = new HashSet<>();
Map<Long, int[]> columnStats = new HashMap<>(); Map<Long, int[]> columnStats = new HashMap<>();
obj.getBlocks().forEach((BlockVector key, BlockData bd) -> { obj.getBlocks().forEach((BlockVector key, BlockData bd) -> {
@@ -76,7 +71,6 @@ public class FloatingObjectFootprint {
int ky = key.getBlockY(); int ky = key.getBlockY();
int kz = key.getBlockZ(); int kz = key.getBlockZ();
long packed = ((long) kx << 32) | (kz & 0xFFFFFFFFL); long packed = ((long) kx << 32) | (kz & 0xFFFFFFFFL);
xzSet.add(packed);
int[] stats = columnStats.get(packed); int[] stats = columnStats.get(packed);
if (stats == null) { if (stats == null) {
stats = new int[]{ky, 1}; stats = new int[]{ky, 1};
@@ -89,6 +83,12 @@ public class FloatingObjectFootprint {
} }
}); });
long[] footprintArray = new long[columnStats.size()];
int idx = 0;
for (Long packed : columnStats.keySet()) {
footprintArray[idx++] = packed;
}
long tallestPacked = resolveTallestColumn(columnStats); long tallestPacked = resolveTallestColumn(columnStats);
int lowestSolidKeyY = columnStats.isEmpty() int lowestSolidKeyY = columnStats.isEmpty()
? cy ? cy
@@ -98,7 +98,7 @@ public class FloatingObjectFootprint {
if (DIAGNOSTIC_LOG) { if (DIAGNOSTIC_LOG) {
logFootprintDiagnostic(cacheKey, obj, cx, cy, cz, lowestSolidKeyY, tallestKx, tallestKz, columnStats); logFootprintDiagnostic(cacheKey, obj, cx, cy, cz, lowestSolidKeyY, tallestKx, tallestKz, columnStats);
} }
return new FloatingObjectFootprint(lowestSolidKeyY, cx, cy, cz, tallestKx, tallestKz, xzSet); return new FloatingObjectFootprint(lowestSolidKeyY, cx, cy, cz, tallestKx, tallestKz, footprintArray);
} }
private static void logFootprintDiagnostic(String cacheKey, IrisObject obj, int cx, int cy, int cz, int anchorY, int tallestKx, int tallestKz, Map<Long, int[]> columnStats) { private static void logFootprintDiagnostic(String cacheKey, IrisObject obj, int cx, int cy, int cz, int anchorY, int tallestKx, int tallestKz, Map<Long, int[]> columnStats) {
@@ -204,10 +204,6 @@ public class FloatingObjectFootprint {
return bestPacked; return bestPacked;
} }
public boolean columnInFootprint(int objKeyX, int objKeyZ) {
return footprintXZ.contains(((long) objKeyX << 32) | (objKeyZ & 0xFFFFFFFFL));
}
public int lowestSolidRelCenterY() { public int lowestSolidRelCenterY() {
return lowestSolidKeyY - centerY; return lowestSolidKeyY - centerY;
} }
@@ -236,7 +232,7 @@ public class FloatingObjectFootprint {
return tallestKz; return tallestKz;
} }
public Set<Long> footprintXZ() { public long[] footprintXZ() {
return footprintXZ; return footprintXZ;
} }
} }