diff --git a/build.gradle b/build.gradle index c3b4969cd..94ecba786 100644 --- a/build.gradle +++ b/build.gradle @@ -247,7 +247,7 @@ allprojects { compileOnly 'it.unimi.dsi:fastutil:8.5.8' compileOnly 'com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2' compileOnly 'org.zeroturnaround:zt-zip:1.14' - compileOnly 'com.google.code.gson:gson:2.9.0' + compileOnly 'com.google.code.gson:gson:2.10.1' compileOnly 'org.ow2.asm:asm:9.2' compileOnly 'com.google.guava:guava:33.0.0-jre' compileOnly 'bsf:bsf:2.4.0' diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandJigsaw.java b/core/src/main/java/com/volmit/iris/core/commands/CommandJigsaw.java index 70e8e2ae3..710184027 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandJigsaw.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandJigsaw.java @@ -21,6 +21,7 @@ package com.volmit.iris.core.commands; import com.volmit.iris.Iris; import com.volmit.iris.core.edit.JigsawEditor; import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.engine.framework.placer.WorldObjectPlacer; import com.volmit.iris.engine.jigsaw.PlannedStructure; import com.volmit.iris.engine.object.IrisJigsawPiece; import com.volmit.iris.engine.object.IrisJigsawStructure; @@ -56,10 +57,16 @@ public class CommandJigsaw implements DecreeExecutor { IrisJigsawStructure structure ) { PrecisionStopwatch p = PrecisionStopwatch.start(); - PlannedStructure ps = new PlannedStructure(structure, new IrisPosition(player().getLocation()), new RNG()); - VolmitSender sender = sender(); - sender.sendMessage(C.GREEN + "Generated " + ps.getPieces().size() + " pieces in " + Form.duration(p.getMilliseconds(), 2)); - ps.place(world(), failed -> sender.sendMessage(failed ? C.GREEN + "Placed the structure!" : C.RED + "Failed to place the structure!")); + try { + var world = world(); + WorldObjectPlacer placer = new WorldObjectPlacer(world); + PlannedStructure ps = new PlannedStructure(structure, new IrisPosition(player().getLocation().add(0, world.getMinHeight(), 0)), new RNG()); + VolmitSender sender = sender(); + sender.sendMessage(C.GREEN + "Generated " + ps.getPieces().size() + " pieces in " + Form.duration(p.getMilliseconds(), 2)); + ps.place(placer, failed -> sender.sendMessage(failed ? C.GREEN + "Placed the structure!" : C.RED + "Failed to place the structure!")); + } catch (IllegalArgumentException e) { + sender().sendMessage(C.RED + "Failed to place the structure: " + e.getMessage()); + } } @Decree(description = "Create a jigsaw piece") diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java index 5815af8e0..2188485c0 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java @@ -80,6 +80,7 @@ import java.nio.file.Files; import java.nio.file.attribute.FileTime; import java.time.Duration; import java.time.temporal.ChronoUnit; +import java.util.Arrays; import java.util.Date; import java.util.Objects; import java.util.concurrent.ExecutionException; @@ -347,6 +348,63 @@ public class CommandStudio implements DecreeExecutor { player().openInventory(inv); } + + @Decree(description = "Get all structures in a radius of chunks", aliases = "dist", origin = DecreeOrigin.PLAYER) + public void distances(@Param(description = "The radius") int radius) { + var engine = engine(); + if (engine == null) { + sender().sendMessage(C.RED + "Only works in an Iris world!"); + return; + } + var sender = sender(); + int d = radius*2; + KMap> data = new KMap<>(); + var multiBurst = new MultiBurst("Distance Sampler", Thread.MIN_PRIORITY); + var executor = multiBurst.burst(radius * radius); + + sender.sendMessage(C.GRAY + "Generating data..."); + var loc = player().getLocation(); + new Spiraler(d, d, (x, z) -> executor.queue(() -> { + var struct = engine.getStructureAt(x, z); + if (struct != null) { + data.computeIfAbsent(struct.getLoadKey(), (k) -> new KList<>()).add(new Position2(x, z)); + } + })).setOffset(loc.getBlockX(), loc.getBlockZ()).drain(); + + executor.complete(); + multiBurst.close(); + for (var key : data.keySet()) { + var list = data.get(key); + KList distances = new KList<>(list.size() - 1); + for (int i = 0; i < list.size(); i++) { + var pos = list.get(i); + double dist = Integer.MAX_VALUE; + for (var p : list) { + if (p.equals(pos)) continue; + dist = Math.min(dist, Math.sqrt(Math.pow(pos.getX() - p.getX(), 2) + Math.pow(pos.getZ() - p.getZ(), 2))); + } + if (dist == Integer.MAX_VALUE) continue; + distances.add(Math.round(dist * 16)); + } + long[] array = new long[distances.size()]; + for (int i = 0; i < distances.size(); i++) { + array[i] = distances.get(i); + } + Arrays.sort(array); + long min = array.length > 0 ? array[0] : 0; + long max = array.length > 0 ? array[array.length - 1] : 0; + long sum = Arrays.stream(array).sum(); + long avg = array.length > 0 ? Math.round(sum / (double) array.length) : 0; + String msg = "%s: %s => min: %s/max: %s -> avg: %s".formatted(key, list.size(), min, max, avg); + sender.sendMessage(msg); + } + if (data.isEmpty()) { + sender.sendMessage(C.RED + "No data found!"); + } else { + sender.sendMessage(C.GREEN + "Done!"); + } + } + @Decree(description = "Render a world map (External GUI)", aliases = "render") public void map() { if (noGUI()) return; diff --git a/core/src/main/java/com/volmit/iris/engine/framework/placer/WorldObjectPlacer.java b/core/src/main/java/com/volmit/iris/engine/framework/placer/WorldObjectPlacer.java new file mode 100644 index 000000000..16968eee5 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/framework/placer/WorldObjectPlacer.java @@ -0,0 +1,127 @@ +package com.volmit.iris.engine.framework.placer; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.tools.IrisToolbelt; +import com.volmit.iris.engine.data.cache.Cache; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.framework.IrisLootEvent; +import com.volmit.iris.engine.mantle.EngineMantle; +import com.volmit.iris.engine.object.IObjectPlacer; +import com.volmit.iris.engine.object.InventorySlotType; +import com.volmit.iris.engine.object.IrisLootTable; +import com.volmit.iris.engine.object.TileData; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.data.B; +import com.volmit.iris.util.math.RNG; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.TileState; +import org.bukkit.block.data.BlockData; +import org.bukkit.inventory.InventoryHolder; + +@Getter +@EqualsAndHashCode(exclude = {"engine", "mantle"}) +public class WorldObjectPlacer implements IObjectPlacer { + private final World world; + private final Engine engine; + private final EngineMantle mantle; + + public WorldObjectPlacer(World world) { + var a = IrisToolbelt.access(world); + if (a == null || a.getEngine() == null) throw new IllegalStateException(world.getName() + " is not an Iris World!"); + this.world = world; + this.engine = a.getEngine(); + this.mantle = engine.getMantle(); + } + + @Override + public int getHighest(int x, int z, IrisData data) { + return mantle.getHighest(x, z, data); + } + + @Override + public int getHighest(int x, int z, IrisData data, boolean ignoreFluid) { + return mantle.getHighest(x, z, data, ignoreFluid); + } + + @Override + public void set(int x, int y, int z, BlockData d) { + Block block = world.getBlockAt(x, y + world.getMinHeight(), z); + + if (y <= world.getMinHeight() || block.getType() == Material.BEDROCK) return; + InventorySlotType slot = null; + if (B.isStorageChest(d)) { + slot = InventorySlotType.STORAGE; + } + + if (slot != null) { + RNG rx = new RNG(Cache.key(x, z)); + KList tables = engine.getLootTables(rx, block); + + try { + Bukkit.getPluginManager().callEvent(new IrisLootEvent(engine, block, slot, tables)); + + if (!tables.isEmpty()){ + Iris.debug("IrisLootEvent has been accessed"); + } + + if (tables.isEmpty()) + return; + InventoryHolder m = (InventoryHolder) block.getState(); + engine.addItems(false, m.getInventory(), rx, tables, slot, x, y, z, 15); + } catch (Throwable e) { + Iris.reportError(e); + } + } + + block.setBlockData(d); + } + + @Override + public BlockData get(int x, int y, int z) { + return world.getBlockAt(x, y + world.getMinHeight(), z).getBlockData(); + } + + @Override + public boolean isPreventingDecay() { + return mantle.isPreventingDecay(); + } + + @Override + public boolean isCarved(int x, int y, int z) { + return mantle.isCarved(x, y, z); + } + + @Override + public boolean isSolid(int x, int y, int z) { + return world.getBlockAt(x, y + world.getMinHeight(), z).getType().isSolid(); + } + + @Override + public boolean isUnderwater(int x, int z) { + return mantle.isUnderwater(x, z); + } + + @Override + public int getFluidHeight() { + return mantle.getFluidHeight(); + } + + @Override + public boolean isDebugSmartBore() { + return mantle.isDebugSmartBore(); + } + + @Override + public void setTile(int xx, int yy, int zz, TileData tile) { + BlockState state = world.getBlockAt(xx, yy + world.getMinHeight(), zz).getState(); + tile.toBukkitTry(state); + state.update(); + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedPiece.java b/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedPiece.java index 08088ac38..5a966e752 100644 --- a/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedPiece.java +++ b/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedPiece.java @@ -20,30 +20,19 @@ package com.volmit.iris.engine.jigsaw; import com.volmit.iris.Iris; import com.volmit.iris.core.loader.IrisData; -import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.*; -import com.volmit.iris.engine.platform.PlatformChunkGenerator; import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.context.IrisContext; +import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.math.AxisAlignedBB; -import com.volmit.iris.util.math.BlockPosition; -import com.volmit.iris.util.math.RNG; +import lombok.AccessLevel; import lombok.Data; import lombok.EqualsAndHashCode; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.TileState; -import org.bukkit.block.data.BlockData; -import org.bukkit.inventory.InventoryHolder; +import lombok.Setter; import org.bukkit.util.BlockVector; import java.util.ArrayList; import java.util.List; -@SuppressWarnings("ALL") @Data public class PlannedPiece { private IrisPosition position; @@ -58,6 +47,12 @@ public class PlannedPiece { private AxisAlignedBB box; @EqualsAndHashCode.Exclude private PlannedStructure structure; + @EqualsAndHashCode.Exclude + @Setter(AccessLevel.NONE) + private ParentConnection parent = null; + @EqualsAndHashCode.Exclude + @Setter(AccessLevel.NONE) + private KMap realPositions; public PlannedPiece(PlannedStructure structure, IrisPosition position, IrisJigsawPiece piece) { this(structure, position, piece, 0, 0, 0); @@ -79,6 +74,7 @@ public class PlannedPiece { this.object.setLoadKey(piece.getObject()); this.ogObject.setLoadKey(piece.getObject()); this.connected = new KList<>(); + this.realPositions = new KMap<>(); } @@ -97,7 +93,15 @@ public class PlannedPiece { } BlockVector v = getObject().getCenter(); - box = object.getAABB().shifted(position.add(new IrisPosition(object.getCenter()))); + IrisPosition pos = new IrisPosition(); + IrisObjectPlacement options = piece.getPlacementOptions(); + if (options != null && options.getTranslate() != null) { + IrisObjectTranslate translate = options.getTranslate(); + pos.setX(translate.getX()); + pos.setY(translate.getY()); + pos.setZ(translate.getZ()); + } + box = object.getAABB().shifted(position.add(new IrisPosition(object.getCenter())).add(pos)); return box; } @@ -129,11 +133,21 @@ public class PlannedPiece { return c; } - public boolean connect(IrisJigsawPieceConnector c) { - if (piece.getConnectors().contains(c)) { - return connected.addIfMissing(c); - } + public KList getChildConnectors() { + ParentConnection pc = getParent(); + KList c = getConnected().copy(); + if (pc != null) c.removeIf(i -> i.equals(pc.connector)); + return c; + } + public boolean connect(IrisJigsawPieceConnector c, PlannedPiece p, IrisJigsawPieceConnector pc) { + if (piece.getConnectors().contains(c) && p.getPiece().getConnectors().contains(pc)) { + if (connected.contains(c) || p.connected.contains(pc)) return false; + connected.add(c); + p.connected.add(pc); + p.parent = new ParentConnection(this, c, p, pc); + return true; + } return false; } @@ -165,105 +179,29 @@ public class PlannedPiece { } public boolean isFull() { - return connected.size() >= piece.getConnectors().size() || isDead(); + return connected.size() >= piece.getConnectors().size(); } - public boolean place(World world) { - PlatformChunkGenerator a = IrisToolbelt.access(world); - - int minY = 0; - if (a != null) { - minY = a.getEngine().getMinHeight(); - - if (!a.getEngine().getDimension().isBedrock()) - minY--; //If the dimension has no bedrock, allow it to go a block lower + public void setRealPositions(int x, int y, int z, IObjectPlacer placer) { + boolean isUnderwater = piece.getPlacementOptions().isUnderwater(); + for (IrisJigsawPieceConnector c : piece.getConnectors()) { + var pos = c.getPosition().add(new IrisPosition(x, 0, z)); + if (y < 0) { + pos.setY(pos.getY() + placer.getHighest(pos.getX(), pos.getZ(), getData(), isUnderwater) + (object.getH() / 2)); + } else { + pos.setY(pos.getY() + y); + } + realPositions.put(c, pos); } - Engine engine = a != null ? a.getEngine() : IrisContext.get().getEngine(); + } - getPiece().getPlacementOptions().setTranslate(new IrisObjectTranslate()); - getPiece().getPlacementOptions().getRotation().setEnabled(false); - getPiece().getPlacementOptions().setRotateTowardsSlope(false); - int finalMinY = minY; - RNG rng = getStructure().getRng().nextParallelRNG(37555); - - // TODO: REAL CLASSES!!!!!!! - return getObject().place(position.getX() + getObject().getCenter().getBlockX(), position.getY() + getObject().getCenter().getBlockY(), position.getZ() + getObject().getCenter().getBlockZ(), new IObjectPlacer() { - @Override - public int getHighest(int x, int z, IrisData data) { - return position.getY(); - } - - @Override - public int getHighest(int x, int z, IrisData data, boolean ignoreFluid) { - return position.getY(); - } - - @Override - public void set(int x, int y, int z, BlockData d) { - Block block = world.getBlockAt(x, y, z); - - //Prevent blocks being set in or bellow bedrock - if (y <= finalMinY || block.getType() == Material.BEDROCK) return; - - block.setBlockData(d); - - if (a != null && getPiece().getPlacementOptions().getLoot().isNotEmpty() && - block.getState() instanceof InventoryHolder) { - - IrisLootTable table = getPiece().getPlacementOptions().getTable(block.getBlockData(), getData()); - if (table == null) return; - engine.addItems(false, ((InventoryHolder) block.getState()).getInventory(), - rng.nextParallelRNG(BlockPosition.toLong(x, y, z)), - new KList<>(table), InventorySlotType.STORAGE, x, y, z, 15); - } - } - - @Override - public BlockData get(int x, int y, int z) { - return world.getBlockAt(x, y, z).getBlockData(); - } - - @Override - public boolean isPreventingDecay() { - return false; - } - - @Override - public boolean isCarved(int x, int y, int z) { - return false; - } - - @Override - public boolean isSolid(int x, int y, int z) { - return world.getBlockAt(x, y, z).getType().isSolid(); - } - - @Override - public boolean isUnderwater(int x, int z) { - return false; - } - - @Override - public int getFluidHeight() { - return 0; - } - - @Override - public boolean isDebugSmartBore() { - return false; - } - - @Override - public void setTile(int xx, int yy, int zz, TileData tile) { - BlockState state = world.getBlockAt(xx, yy, zz).getState(); - tile.toBukkitTry(state); - state.update(); - } - - @Override - public Engine getEngine() { - return engine; - } - }, piece.getPlacementOptions(), rng, getData().getEngine() == null ? engine.getData() : getData()) != -1; + public record ParentConnection(PlannedPiece parent, IrisJigsawPieceConnector parentConnector, PlannedPiece self, IrisJigsawPieceConnector connector) { + public IrisPosition getTargetPosition() { + var pos = parent.realPositions.get(parentConnector); + if (pos == null) return null; + return pos.add(new IrisPosition(parentConnector.getDirection().toVector())) + .sub(connector.getPosition()) + .sub(new IrisPosition(self.object.getCenter())); + } } } diff --git a/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java b/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java index 18c03bd9b..77d4157f7 100644 --- a/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java +++ b/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java @@ -21,9 +21,8 @@ package com.volmit.iris.engine.jigsaw; import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; import com.volmit.iris.Iris; import com.volmit.iris.core.loader.IrisData; -import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.framework.placer.WorldObjectPlacer; import com.volmit.iris.engine.object.*; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.mantle.Mantle; @@ -34,12 +33,6 @@ import com.volmit.iris.util.matter.slices.container.JigsawStructuresContainer; import com.volmit.iris.util.scheduling.J; import lombok.Data; import org.bukkit.Axis; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.TileState; -import org.bukkit.block.data.BlockData; import java.util.function.Consumer; @@ -110,6 +103,7 @@ public class PlannedStructure { options = i.getPiece().getPlacementOptions(); options.getRotation().setEnabled(false); options.setRotateTowardsSlope(false); + options.setWarp(new IrisGeneratorStyle(NoiseStyle.FLAT)); } else { options.setMode(i.getPiece().getPlaceMode()); } @@ -119,7 +113,6 @@ public class PlannedStructure { int sz = (v.getD() / 2); int xx = i.getPosition().getX() + sx; int zz = i.getPosition().getZ() + sz; - RNG rngf = new RNG(Cache.key(xx, zz)); int offset = i.getPosition().getY() - startHeight; int height; @@ -133,6 +126,17 @@ public class PlannedStructure { height = i.getStructure().getStructure().getLockY(); } + PlannedPiece.ParentConnection connection = i.getParent(); + if (connection != null && connection.connector().isLockY()) { + var pos = connection.getTargetPosition(); + if (pos != null) { + height = pos.getY(); + offset = 0; + } else { + Iris.warn("Failed to get target position for " + v.getLoadKey()); + } + } + height += offset + (v.getH() / 2); if (options.getMode().equals(ObjectPlaceMode.PAINT)) { @@ -141,89 +145,15 @@ public class PlannedStructure { int id = rng.i(0, Integer.MAX_VALUE); JigsawPieceContainer container = JigsawPieceContainer.toContainer(i.getPiece()); + i.setRealPositions(xx, height, zz, placer); return v.place(xx, height, zz, placer, options, rng, (b, data) -> { e.set(b.getX(), b.getY(), b.getZ(), v.getLoadKey() + "@" + id); e.set(b.getX(), b.getY(), b.getZ(), container); }, null, getData().getEngine() != null ? getData() : eng.getData()) != -1; } - public void place(World world, Consumer consumer) { - var a = IrisToolbelt.access(world); - if (a == null || a.getEngine() == null) { - consumer.accept(null); - return; - } - var engine = a.getEngine(); - var engineMantle = engine.getMantle(); - var placer = new IObjectPlacer() { - @Override - public int getHighest(int x, int z, IrisData data) { - return engineMantle.getHighest(x, z, data); - } - - @Override - public int getHighest(int x, int z, IrisData data, boolean ignoreFluid) { - return engineMantle.getHighest(x, z, data, ignoreFluid); - } - - @Override - public void set(int x, int y, int z, BlockData d) { - Block block = world.getBlockAt(x, y + world.getMinHeight(), z); - - //Prevent blocks being set in or bellow bedrock - if (y <= world.getMinHeight() || block.getType() == Material.BEDROCK) return; - - block.setBlockData(d); - } - - @Override - public BlockData get(int x, int y, int z) { - return world.getBlockAt(x, y + world.getMinHeight(), z).getBlockData(); - } - - @Override - public boolean isPreventingDecay() { - return engineMantle.isPreventingDecay(); - } - - @Override - public boolean isCarved(int x, int y, int z) { - return engineMantle.isCarved(x, y, z); - } - - @Override - public boolean isSolid(int x, int y, int z) { - return world.getBlockAt(x, y + world.getMinHeight(), z).getType().isSolid(); - } - - @Override - public boolean isUnderwater(int x, int z) { - return engineMantle.isUnderwater(x, z); - } - - @Override - public int getFluidHeight() { - return engineMantle.getFluidHeight(); - } - - @Override - public boolean isDebugSmartBore() { - return engineMantle.isDebugSmartBore(); - } - - @Override - public void setTile(int xx, int yy, int zz, TileData tile) { - BlockState state = world.getBlockAt(xx, yy + world.getMinHeight(), zz).getState(); - tile.toBukkitTry(state); - state.update(); - } - - @Override - public Engine getEngine() { - return engine; - } - }; - J.s(() -> consumer.accept(place(placer, engineMantle.getMantle(), engine))); + public void place(WorldObjectPlacer placer, Consumer consumer) { + J.s(() -> consumer.accept(place(placer, placer.getMantle().getMantle(), placer.getEngine()))); } private void generateOutwards() { @@ -330,8 +260,7 @@ public class PlannedStructure { return false; } - piece.connect(pieceConnector); - test.connect(testConnector); + piece.connect(pieceConnector, test, testConnector); pieces.add(test); return true; @@ -340,7 +269,8 @@ public class PlannedStructure { private KList getShuffledPiecesFor(IrisJigsawPieceConnector c) { KList p = new KList<>(); - for (String i : c.getPools().shuffleCopy(rng)) { + KList pools = terminating && getStructure().getTerminatePool() != null ? new KList<>(getStructure().getTerminatePool()) : c.getPools().shuffleCopy(rng); + for (String i : pools) { for (String j : getData().getJigsawPoolLoader().load(i).getPieces().shuffleCopy(rng)) { IrisJigsawPiece pi = getData().getJigsawPieceLoader().load(j); @@ -366,7 +296,9 @@ public class PlannedStructure { } public KList getPiecesWithAvailableConnectors() { - return pieces.copy().removeWhere(PlannedPiece::isFull); + KList available = pieces.copy().removeWhere(PlannedPiece::isFull); + if (!terminating) available.removeIf(PlannedPiece::isDead); + return available; } public int getVolume() { diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java index f10ff67c7..b7d8223b7 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java @@ -18,7 +18,6 @@ package com.volmit.iris.engine.mantle.components; -import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.engine.jigsaw.PlannedStructure; import com.volmit.iris.engine.mantle.EngineMantle; import com.volmit.iris.engine.mantle.IrisMantleComponent; @@ -35,34 +34,31 @@ import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.matter.slices.container.JigsawStructuresContainer; import com.volmit.iris.util.noise.CNG; -import com.volmit.iris.util.noise.NoiseType; +import org.jetbrains.annotations.Nullable; import java.util.List; public class MantleJigsawComponent extends IrisMantleComponent { + private final CNG cng; public MantleJigsawComponent(EngineMantle engineMantle) { super(engineMantle, MantleFlag.JIGSAW); - } - - private RNG applyNoise(int x, int z) { - long seed = Cache.key(x, z) + getEngineMantle().getEngine().getSeedManager().getJigsaw(); - CNG cng = CNG.signatureFast(new RNG(seed), NoiseType.WHITE, NoiseType.GLOB); - return new RNG((long) (seed * cng.noise(x, z))); + cng = NoiseStyle.STATIC.create(new RNG(jigsaw())); } @Override public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) { - RNG rng = applyNoise(x, z); int xxx = 8 + (x << 4); int zzz = 8 + (z << 4); IrisRegion region = getComplex().getRegionStream().get(xxx, zzz); IrisBiome biome = getComplex().getTrueBiomeStream().get(xxx, zzz); - generateJigsaw(writer, rng, x, z, biome, region); + generateJigsaw(writer, x, z, biome, region); } @ChunkCoordinates - private void generateJigsaw(MantleWriter writer, RNG rng, int x, int z, IrisBiome biome, IrisRegion region) { + private void generateJigsaw(MantleWriter writer, int x, int z, IrisBiome biome, IrisRegion region) { + long seed = cng.fit(Integer.MIN_VALUE, Integer.MIN_VALUE, x, z); + if (getDimension().getStronghold() != null) { List poss = getDimension().getStrongholds(seed()); @@ -70,7 +66,7 @@ public class MantleJigsawComponent extends IrisMantleComponent { for (Position2 pos : poss) { if (x == pos.getX() >> 4 && z == pos.getZ() >> 4) { IrisJigsawStructure structure = getData().getJigsawStructureLoader().load(getDimension().getStronghold()); - place(writer, pos.toIris(), structure, rng); + place(writer, pos.toIris(), structure, new RNG(seed)); return; } } @@ -80,27 +76,23 @@ public class MantleJigsawComponent extends IrisMantleComponent { KSet cachedRegions = new KSet<>(); KMap> cache = new KMap<>(); KMap distanceCache = new KMap<>(); - boolean placed = placeStructures(writer, rng, x, z, biome.getJigsawStructures(), cachedRegions, cache, distanceCache); + boolean placed = placeStructures(writer, seed, x, z, biome.getJigsawStructures(), cachedRegions, cache, distanceCache); if (!placed) - placed = placeStructures(writer, rng, x, z, region.getJigsawStructures(), cachedRegions, cache, distanceCache); + placed = placeStructures(writer, seed, x, z, region.getJigsawStructures(), cachedRegions, cache, distanceCache); if (!placed) - placeStructures(writer, rng, x, z, getDimension().getJigsawStructures(), cachedRegions, cache, distanceCache); + placeStructures(writer, seed, x, z, getDimension().getJigsawStructures(), cachedRegions, cache, distanceCache); } @ChunkCoordinates - private boolean placeStructures(MantleWriter writer, RNG rng, int x, int z, KList structures, + private boolean placeStructures(MantleWriter writer, long seed, int x, int z, KList structures, KSet cachedRegions, KMap> cache, KMap distanceCache) { - for (IrisJigsawStructurePlacement i : structures) { - if (rng.nextInt(i.getRarity()) == 0) { - if (checkMinDistances(i.collectMinDistances(), x, z, cachedRegions, cache, distanceCache)) - continue; - IrisPosition position = new IrisPosition((x << 4) + rng.nextInt(15), 0, (z << 4) + rng.nextInt(15)); - IrisJigsawStructure structure = getData().getJigsawStructureLoader().load(i.getStructure()); - if (place(writer, position, structure, rng)) - return true; - } - } - return false; + IrisJigsawStructurePlacement i = pick(structures, seed, x, z); + if (i == null || checkMinDistances(i.collectMinDistances(), x, z, cachedRegions, cache, distanceCache)) + return false; + RNG rng = new RNG(seed); + IrisPosition position = new IrisPosition((x << 4) + rng.nextInt(15), 0, (z << 4) + rng.nextInt(15)); + IrisJigsawStructure structure = getData().getJigsawStructureLoader().load(i.getStructure()); + return place(writer, position, structure, rng); } @ChunkCoordinates @@ -138,7 +130,7 @@ public class MantleJigsawComponent extends IrisMantleComponent { public IrisJigsawStructure guess(int x, int z) { // todo The guess doesnt bring into account that the placer may return -1 // todo doesnt bring skipped placements into account - RNG rng = applyNoise(x, z); + long seed = cng.fit(Integer.MIN_VALUE, Integer.MIN_VALUE, x, z); IrisBiome biome = getEngineMantle().getEngine().getSurfaceBiome((x << 4) + 8, (z << 4) + 8); IrisRegion region = getEngineMantle().getEngine().getRegion((x << 4) + 8, (z << 4) + 8); @@ -154,29 +146,26 @@ public class MantleJigsawComponent extends IrisMantleComponent { } } - for (IrisJigsawStructurePlacement i : biome.getJigsawStructures()) { - if (rng.nextInt(i.getRarity()) == 0) { - return getData().getJigsawStructureLoader().load(i.getStructure()); - } - } + IrisJigsawStructurePlacement i = pick(biome.getJigsawStructures(), seed, x, z); + if (i == null) i = pick(region.getJigsawStructures(), seed, x, z); + if (i == null) i = pick(getDimension().getJigsawStructures(), seed, x, z); + return i != null ? getData().getJigsawStructureLoader().load(i.getStructure()) : null; + } - for (IrisJigsawStructurePlacement i : region.getJigsawStructures()) { - if (rng.nextInt(i.getRarity()) == 0) { - return getData().getJigsawStructureLoader().load(i.getStructure()); - } - } - - for (IrisJigsawStructurePlacement i : getDimension().getJigsawStructures()) { - if (rng.nextInt(i.getRarity()) == 0) { - return getData().getJigsawStructureLoader().load(i.getStructure()); - } - } - - return null; + @Nullable + @ChunkCoordinates + private IrisJigsawStructurePlacement pick(List structures, long seed, int x, int z) { + return IRare.pick(structures.stream() + .filter(p -> p.shouldPlace(jigsaw(), x, z)) + .toList(), new RNG(seed).nextDouble()); } @BlockCoordinates private boolean place(MantleWriter writer, IrisPosition position, IrisJigsawStructure structure, RNG rng) { return new PlannedStructure(structure, position, rng).place(writer, getMantle(), writer.getEngine()); } + + private long jigsaw() { + return getEngineMantle().getEngine().getSeedManager().getJigsaw(); + } } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawPieceConnector.java b/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawPieceConnector.java index bc5fce61b..f3afe074a 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawPieceConnector.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawPieceConnector.java @@ -79,6 +79,9 @@ public class IrisJigsawPieceConnector { @Required private IrisDirection direction = IrisDirection.UP_POSITIVE_Y; + @Desc("Lock the Y position of this connector") + private boolean lockY = false; + public String toString() { return direction.getFace().name() + "@(" + position.getX() + "," + position.getY() + "," + position.getZ() + ")"; } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawStructure.java b/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawStructure.java index 8a009364f..9d6d28506 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawStructure.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawStructure.java @@ -56,6 +56,10 @@ public class IrisJigsawStructure extends IrisRegistrant { @Desc("If set to true, iris will look for any pieces with only one connector in valid pools for edge connectors and attach them to 'terminate' the paths/piece connectors. Essentially it caps off ends. For example in a village, Iris would add houses to the ends of roads where possible. For terminators to be selected, they can only have one connector or they wont be chosen.") private boolean terminate = true; + @RegistryListResource(IrisJigsawPool.class) + @Desc("The pool to use when terminating pieces") + private String terminatePool = null; + @Desc("Override the y range instead of placing on the height map") private IrisStyledRange overrideYRange = null; diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawStructurePlacement.java b/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawStructurePlacement.java index 16eea76e8..ed62db9c0 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawStructurePlacement.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawStructurePlacement.java @@ -18,20 +18,23 @@ package com.volmit.iris.engine.object; +import com.volmit.iris.Iris; import com.volmit.iris.engine.object.annotations.ArrayType; import com.volmit.iris.engine.object.annotations.Desc; +import com.volmit.iris.engine.object.annotations.MinNumber; import com.volmit.iris.engine.object.annotations.RegistryListResource; import com.volmit.iris.engine.object.annotations.Required; import com.volmit.iris.engine.object.annotations.Snippet; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.documentation.ChunkCoordinates; +import com.volmit.iris.util.math.RNG; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; - @Snippet("jigsaw-structure-placement") @Accessors(chain = true) @NoArgsConstructor @@ -39,16 +42,33 @@ import lombok.experimental.Accessors; @Desc("Represents a jigsaw structure placer") @Data @EqualsAndHashCode(callSuper = false) -public class IrisJigsawStructurePlacement { +public class IrisJigsawStructurePlacement implements IRare { @RegistryListResource(IrisJigsawStructure.class) @Required @Desc("The structure to place") private String structure; @Required - @Desc("The 1 in X chance rarity") + @Desc("The 1 in X chance rarity applies when generating multiple structures at once") private int rarity = 100; + @Required + @Desc("The salt to use when generating the structure (to differentiate structures)") + private int salt = 76134; + + @Required + @MinNumber(0) + @Desc("Average distance in chunks between two neighboring generation attempts") + private int spacing = 32; + + @Required + @MinNumber(0) + @Desc("Minimum distance in chunks between two neighboring generation attempts\nThe maximum distance of two neighboring generation attempts is 2*spacing - separation") + private int separation = 16; + + @Desc("The method used to spread the structure") + private SpreadType spreadType = SpreadType.TRIANGULAR; + @ArrayType(type = IrisJigsawMinDistance.class) @Desc("List of minimum distances to check for") private KList minDistances = new KList<>(); @@ -64,4 +84,42 @@ public class IrisJigsawStructurePlacement { private int toChunks(int blocks) { return (int) Math.ceil(blocks / 16d); } + + @ChunkCoordinates + public boolean shouldPlace(long seed, int x, int z) { + if (separation > spacing) { + separation = spacing; + Iris.warn("JigsawStructurePlacement: separation must be less than or equal to spacing"); + } + + int i = Math.floorDiv(x, spacing); + int j = Math.floorDiv(z, spacing); + RNG rng = new RNG(i * 341873128712L + j * 132897987541L + seed + salt); + + int k = spacing - separation; + int l = spreadType.apply(rng, k); + int m = spreadType.apply(rng, k); + return i * spacing + l == x && j * spacing + m == z; + } + + @Desc("Spread type") + public enum SpreadType { + @Desc("Linear spread") + LINEAR(RNG::i), + @Desc("Triangular spread") + TRIANGULAR((rng, bound) -> (rng.i(bound) + rng.i(bound)) / 2); + private final SpreadMethod method; + + SpreadType(SpreadMethod method) { + this.method = method; + } + + public int apply(RNG rng, int bound) { + return method.apply(rng, bound); + } + } + + private interface SpreadMethod { + int apply(RNG rng, int bound); + } } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java b/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java index 0e8b3b907..cb85b0d66 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java @@ -506,7 +506,7 @@ public class IrisObject extends IrisRegistrant { if (rdata != null) { // Slope condition if (!config.getSlopeCondition().isDefault() && - !config.getSlopeCondition().isValid(rdata.getEngine().getComplex().getSlopeStream().get(x, z))) { + !config.getSlopeCondition().isValid(rdata.getEngine().getComplex().getSlopeStream().get(x, z)) && !config.isForcePlace()) { return -1; } @@ -571,8 +571,10 @@ public class IrisObject extends IrisRegistrant { if (yv < 0) { if (config.getMode().equals(ObjectPlaceMode.CENTER_HEIGHT) || config.getMode() == ObjectPlaceMode.CENTER_STILT) { y = (c != null ? c.getSurface() : placer.getHighest(x, z, getLoader(), config.isUnderwater())) + rty; - if (placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z)) { - bail = true; + if (!config.isForcePlace()) { + if (placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z)) { + bail = true; + } } } else if (config.getMode().equals(ObjectPlaceMode.MAX_HEIGHT) || config.getMode().equals(ObjectPlaceMode.STILT)) { BlockVector offset = new BlockVector(config.getTranslate().getX(), config.getTranslate().getY(), config.getTranslate().getZ()); @@ -586,9 +588,11 @@ public class IrisObject extends IrisRegistrant { for (int i = minX; i <= maxX; i++) { for (int ii = minZ; ii <= maxZ; ii++) { int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty; - if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { - bail = true; - break; + if (!config.isForcePlace()) { + if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { + bail = true; + break; + } } if (h > y) y = h; @@ -610,9 +614,11 @@ public class IrisObject extends IrisRegistrant { for (int i = minX; i <= maxX; i += Math.abs(xRadius) + 1) { for (int ii = minZ; ii <= maxZ; ii += Math.abs(zRadius) + 1) { int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty; - if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { - bail = true; - break; + if (!config.isForcePlace()) { + if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { + bail = true; + break; + } } if (h > y) y = h; @@ -632,9 +638,11 @@ public class IrisObject extends IrisRegistrant { for (int i = minX; i <= maxX; i++) { for (int ii = minZ; ii <= maxZ; ii++) { int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty; - if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { - bail = true; - break; + if (!config.isForcePlace()) { + if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { + bail = true; + break; + } } if (h < y) { y = h; @@ -658,9 +666,11 @@ public class IrisObject extends IrisRegistrant { for (int i = minX; i <= maxX; i += Math.abs(xRadius) + 1) { for (int ii = minZ; ii <= maxZ; ii += Math.abs(zRadius) + 1) { int h = placer.getHighest(i, ii, getLoader(), config.isUnderwater()) + rty; - if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { - bail = true; - break; + if (!config.isForcePlace()) { + if (placer.isCarved(i, h, ii) || placer.isCarved(i, h - 1, ii) || placer.isCarved(i, h - 2, ii) || placer.isCarved(i, h - 3, ii)) { + bail = true; + break; + } } if (h < y) { y = h; @@ -669,45 +679,51 @@ public class IrisObject extends IrisRegistrant { } } else if (config.getMode().equals(ObjectPlaceMode.PAINT)) { y = placer.getHighest(x, z, getLoader(), config.isUnderwater()) + rty; - if (placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z)) { - bail = true; + if (!config.isForcePlace()) { + if (placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z)) { + bail = true; + } } } } else { y = yv; - if (placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z)) { - bail = true; + if (!config.isForcePlace()) { + if (placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z)) { + bail = true; + } } } if (yv >= 0 && config.isBottom()) { y += Math.floorDiv(h, 2); - bail = placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z); + if (!config.isForcePlace()) { + bail = placer.isCarved(x, y, z) || placer.isCarved(x, y - 1, z) || placer.isCarved(x, y - 2, z) || placer.isCarved(x, y - 3, z); + } } - if (bail) { + if (bail && !config.isForcePlace()) { return -1; } if (yv < 0) { - if (!config.isUnderwater() && !config.isOnwater() && placer.isUnderwater(x, z)) { + if (!config.isForcePlace() && !config.isUnderwater() && !config.isOnwater() && placer.isUnderwater(x, z)) { return -1; } } - if (c != null && Math.max(0, h + yrand + ty) + 1 >= c.getHeight()) { + if (!config.isForcePlace() && c != null && Math.max(0, h + yrand + ty) + 1 >= c.getHeight()) { return -1; } - if (config.isUnderwater() && y + rty + ty >= placer.getFluidHeight()) { + if (!config.isForcePlace() && config.isUnderwater() && y + rty + ty >= placer.getFluidHeight()) { return -1; } - if (!config.getClamp().canPlace(y + rty + ty, y - rty + ty)) { + if (!config.isForcePlace() && !config.getClamp().canPlace(y + rty + ty, y - rty + ty)) { return -1; } - if (!config.getAllowedCollisions().isEmpty() || !config.getForbiddenCollisions().isEmpty()) { + if (!config.isForcePlace() && (!config.getAllowedCollisions().isEmpty() || !config.getForbiddenCollisions().isEmpty())) { Engine engine = rdata.getEngine(); BlockVector offset = new BlockVector(config.getTranslate().getX(), config.getTranslate().getY(), config.getTranslate().getZ()); for (int i = x - Math.floorDiv(w, 2) + (int) offset.getX(); i <= x + Math.floorDiv(w, 2) - (w % 2 == 0 ? 1 : 0) + (int) offset.getX(); i++) { diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisObjectPlacement.java b/core/src/main/java/com/volmit/iris/engine/object/IrisObjectPlacement.java index 813ef8c4a..d03248e87 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisObjectPlacement.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisObjectPlacement.java @@ -136,6 +136,8 @@ public class IrisObjectPlacement { @ArrayType(type = String.class) @Desc("List of objects to this object is forbidden to collied with") private KList forbiddenCollisions = new KList<>(); + @Desc("Ignore any placement restrictions for this object") + private boolean forcePlace = false; private transient AtomicCache cache = new AtomicCache<>(); public IrisObjectPlacement toPlacement(String... place) { diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisObjectRotation.java b/core/src/main/java/com/volmit/iris/engine/object/IrisObjectRotation.java index 5b75ef725..6e8eb2a11 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisObjectRotation.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisObjectRotation.java @@ -101,6 +101,11 @@ public class IrisObjectRotation { i.setPosition(rotate(i.getPosition())); i.setDirection(rotate(i.getDirection())); } + try { + var translate = piece.getPlacementOptions().getTranslate(); + var pos = rotate(new IrisPosition(translate.getX(), translate.getY(), translate.getZ())); + translate.setX(pos.getX()).setY(pos.getY()).setZ(pos.getZ()); + } catch (NullPointerException ignored) {} return piece; } diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index e34922e78..05cd9bf4b 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -16,7 +16,7 @@ libraries: - commons-io:commons-io:2.13.0 - io.timeandspace:smoothie-map:2.0.2 - com.google.guava:guava:31.0.1-jre - - com.google.code.gson:gson:2.8.9 + - com.google.code.gson:gson:2.10.1 - org.zeroturnaround:zt-zip:1.14 - it.unimi.dsi:fastutil:8.5.6 - org.ow2.asm:asm:9.2