diff --git a/core/src/main/java/art/arcane/iris/core/commands/CommandFind.java b/core/src/main/java/art/arcane/iris/core/commands/CommandFind.java
new file mode 100644
index 000000000..5c06e4489
--- /dev/null
+++ b/core/src/main/java/art/arcane/iris/core/commands/CommandFind.java
@@ -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 .
+ */
+
+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.");
+ }
+}
diff --git a/core/src/main/java/art/arcane/iris/core/commands/CommandIris.java b/core/src/main/java/art/arcane/iris/core/commands/CommandIris.java
index 7413fa0c2..5b604486c 100644
--- a/core/src/main/java/art/arcane/iris/core/commands/CommandIris.java
+++ b/core/src/main/java/art/arcane/iris/core/commands/CommandIris.java
@@ -101,6 +101,7 @@ public class CommandIris implements DirectorExecutor {
private CommandEdit edit;
private CommandDeveloper developer;
private CommandPack pack;
+ private CommandFind find;
public static boolean worldCreation = false;
private static final AtomicReference mainWorld = new AtomicReference<>();
String WorldEngine;
diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/IslandObjectPlacer.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/IslandObjectPlacer.java
index 159af8ad1..5f271dfd3 100644
--- a/core/src/main/java/art/arcane/iris/engine/mantle/components/IslandObjectPlacer.java
+++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/IslandObjectPlacer.java
@@ -33,6 +33,7 @@ public class IslandObjectPlacer implements IObjectPlacer {
private final MantleWriter wrapped;
private final FloatingIslandSample[] samples;
+ private final boolean[] overhangAllowed;
private final int minX;
private final int minZ;
private final int chunkMaxIslandTopY;
@@ -57,6 +58,38 @@ public class IslandObjectPlacer implements IObjectPlacer {
}
}
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() {
@@ -73,38 +106,29 @@ public class IslandObjectPlacer implements IObjectPlacer {
private boolean shouldSkipAirColumn(int x, int y, int z) {
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;
}
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;
+ writesDroppedOverhang++;
+ return true;
}
private @Nullable FloatingIslandSample sampleAt(int x, int z) {
diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleFloatingObjectComponent.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleFloatingObjectComponent.java
index 50fe179e1..965701b84 100644
--- a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleFloatingObjectComponent.java
+++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleFloatingObjectComponent.java
@@ -62,6 +62,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
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;
+ private static final IrisObjectRotation ROTATION_NONE = IrisObjectRotation.of(0, 0, 0);
public static final java.util.concurrent.ConcurrentHashMap anchorYHisto = new java.util.concurrent.ConcurrentHashMap<>();
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) {
- long warned = terrainMismatchWarnings.get();
- if (warned >= TERRAIN_MISMATCH_WARNING_CAP) {
+ private static void verifyTerrainBelowObject(IrisObject obj, int wx, int wz, int pickTopY, FloatingIslandSample sample) {
+ if (terrainMismatchWarnings.get() >= TERRAIN_MISMATCH_WARNING_CAP) {
return;
}
- boolean mantleSolid;
- try {
- mantleSolid = writer.isSolid(wx, pickTopY, wz);
- } catch (Throwable t) {
- mantleSolid = false;
- }
- boolean sampleSolid = sample != null
+ if (sample != null
&& sample.solidMask != null
&& sample.topIdx >= 0
&& sample.topIdx < sample.solidMask.length
- && sample.solidMask[sample.topIdx];
- if (mantleSolid && sampleSolid) {
+ && sample.solidMask[sample.topIdx]) {
return;
}
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);
Iris.warn("[FloatingTerrainCheck] object=" + objKey
+ " at=(" + wx + "," + (pickTopY + 1) + "," + wz + ")"
- + " expected solid below at y=" + pickTopY
- + " mantleSolid=" + mantleSolid
- + " sampleSolid=" + sampleSolid
+ + " sample reports non-solid trunk column"
+ " sampleTopY=" + sampleTop
+ " sampleBaseY=" + sampleBase
+ " sampleTopIdx=" + sampleTopIdx
@@ -198,16 +189,21 @@ 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, target);
- }
- }
-
+ KList surface = entry.isInheritObjects() && target != null ? target.getSurfaceObjects() : null;
KList extras = entry.getExtraObjects();
- if (extras != null && !extras.isEmpty()) {
- for (IrisObjectPlacement placement : extras) {
- tryPlaceAnchoredChunk(writer, complex, chunkRng, data, placement, samples, columns, minX, minZ, entry, target);
+ boolean hasSurface = surface != null && !surface.isEmpty();
+ boolean hasExtras = extras != null && !extras.isEmpty();
+ if (hasSurface || hasExtras) {
+ KList 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
- private void tryPlaceAnchoredChunk(MantleWriter writer, IrisComplex complex, RNG rng, IrisData data, IrisObjectPlacement placement, FloatingIslandSample[] samples, KList 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 columns, KList interior, int minX, int minZ, IrisFloatingChildBiomes entry) {
if (placement == null || columns.isEmpty()) {
return;
}
int density = placement.getDensity(rng, minX, minZ, data);
double perAttempt = placement.getChance();
- KList interior = interiorColumns(samples, columns);
for (int i = 0; i < density; i++) {
objectsAttempted.incrementAndGet();
@@ -329,7 +324,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
IrisObjectPlacement anchored = placement.toPlacement(obj.getLoadKey());
anchored.setMode(translateStiltModeForFloating(anchored.getMode()));
anchored.setTranslate(new IrisObjectTranslate());
- anchored.setRotation(IrisObjectRotation.of(0, 0, 0));
+ anchored.setRotation(ROTATION_NONE);
anchored.setForcePlace(true);
anchored.setBottom(false);
@@ -349,7 +344,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
recordAnchorYHisto(pickTopY);
int trunkWx = minX + pickedXf;
int trunkWz = minZ + pickedZf;
- verifyTerrainBelowObject(writer, obj, trunkWx, trunkWz, pickTopY, pickedSample);
+ verifyTerrainBelowObject(obj, trunkWx, trunkWz, pickTopY, pickedSample);
recordWriteStats(obj, trunkWx, trunkWz, pickTopY, islandPlacer);
} catch (Throwable e) {
Iris.reportError(e);
@@ -362,7 +357,9 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
int tallestKz = fp.getTallestKz();
int checked = 0;
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 kz = (int) (encoded & 0xFFFFFFFFL);
int colXf = pickedXf + (kx - tallestKx);
diff --git a/core/src/main/java/art/arcane/iris/engine/object/FloatingObjectFootprint.java b/core/src/main/java/art/arcane/iris/engine/object/FloatingObjectFootprint.java
index ed9ea46ef..c8697ae9d 100644
--- a/core/src/main/java/art/arcane/iris/engine/object/FloatingObjectFootprint.java
+++ b/core/src/main/java/art/arcane/iris/engine/object/FloatingObjectFootprint.java
@@ -24,13 +24,9 @@ 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;
@@ -44,16 +40,16 @@ public class FloatingObjectFootprint {
private final int centerZ;
private final int tallestKx;
private final int tallestKz;
- private final Set footprintXZ;
+ private final long[] footprintXZ;
- private FloatingObjectFootprint(int lowestSolidKeyY, int centerX, int centerY, int centerZ, int tallestKx, int tallestKz, Set footprintXZ) {
+ private FloatingObjectFootprint(int lowestSolidKeyY, int centerX, int centerY, int centerZ, int tallestKx, int tallestKz, 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);
+ this.footprintXZ = footprintXZ;
}
public static FloatingObjectFootprint compute(IrisObject obj) {
@@ -65,7 +61,6 @@ public class FloatingObjectFootprint {
int cx = obj.getCenter().getBlockX();
int cy = obj.getCenter().getBlockY();
int cz = obj.getCenter().getBlockZ();
- Set xzSet = new HashSet<>();
Map columnStats = new HashMap<>();
obj.getBlocks().forEach((BlockVector key, BlockData bd) -> {
@@ -76,7 +71,6 @@ public class FloatingObjectFootprint {
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};
@@ -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);
int lowestSolidKeyY = columnStats.isEmpty()
? cy
@@ -98,7 +98,7 @@ public class FloatingObjectFootprint {
if (DIAGNOSTIC_LOG) {
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 columnStats) {
@@ -204,10 +204,6 @@ public class FloatingObjectFootprint {
return bestPacked;
}
- public boolean columnInFootprint(int objKeyX, int objKeyZ) {
- return footprintXZ.contains(((long) objKeyX << 32) | (objKeyZ & 0xFFFFFFFFL));
- }
-
public int lowestSolidRelCenterY() {
return lowestSolidKeyY - centerY;
}
@@ -236,7 +232,7 @@ public class FloatingObjectFootprint {
return tallestKz;
}
- public Set footprintXZ() {
+ public long[] footprintXZ() {
return footprintXZ;
}
}