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 922041241..a59aa2a69 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 @@ -57,7 +57,7 @@ public class CommandJigsaw implements DecreeExecutor { PrecisionStopwatch p = PrecisionStopwatch.start(); PlannedStructure ps = new PlannedStructure(structure, new IrisPosition(player().getLocation()), new RNG()); sender().sendMessage(C.GREEN + "Generated " + ps.getPieces().size() + " pieces in " + Form.duration(p.getMilliseconds(), 2)); - ps.place(world()); + ps.place(world(), failed -> sender().sendMessage(failed == 0 ? C.GREEN + "Placed the structure!" : C.RED + "Failed to place " + failed + " pieces!")); } @Decree(description = "Create a jigsaw piece") 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 e93fe5c7d..d2b0d43ea 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 @@ -165,7 +165,7 @@ public class PlannedPiece { return connected.size() >= piece.getConnectors().size() || isDead(); } - public void place(World world) { + public boolean place(World world) { PlatformChunkGenerator a = IrisToolbelt.access(world); int minY = 0; @@ -175,14 +175,16 @@ public class PlannedPiece { if (!a.getEngine().getDimension().isBedrock()) minY--; //If the dimension has no bedrock, allow it to go a block lower } + Engine engine = a != null ? a.getEngine() : IrisContext.get().getEngine(); getPiece().getPlacementOptions().setTranslate(new IrisObjectTranslate()); - getPiece().getPlacementOptions().setRotation(rotation); + getPiece().getPlacementOptions().getRotation().setEnabled(false); + getPiece().getPlacementOptions().setRotateTowardsSlope(false); int finalMinY = minY; RNG rng = getStructure().getRng().nextParallelRNG(37555); // TODO: REAL CLASSES!!!!!!! - getOgObject().place(position.getX() + getObject().getCenter().getBlockX(), position.getY() + getObject().getCenter().getBlockY(), position.getZ() + getObject().getCenter().getBlockZ(), new IObjectPlacer() { + 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(); @@ -207,7 +209,6 @@ public class PlannedPiece { IrisLootTable table = getPiece().getPlacementOptions().getTable(block.getBlockData(), getData()); if (table == null) return; - Engine engine = a.getEngine(); engine.addItems(false, ((InventoryHolder) block.getState()).getInventory(), rng.nextParallelRNG(BlockPosition.toLong(x, y, z)), new KList<>(table), InventorySlotType.STORAGE, x, y, z, 15); @@ -258,12 +259,8 @@ public class PlannedPiece { @Override public Engine getEngine() { - if (IrisToolbelt.isIrisWorld(world)) { - return IrisToolbelt.access(world).getEngine(); - } - - return IrisContext.get().getEngine(); + return engine; } - }, piece.getPlacementOptions(), rng, getData()); + }, piece.getPlacementOptions(), rng, getData().getEngine() == null ? engine.getData() : getData()) != -1; } } 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 dd7eade40..36cdcaa00 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 @@ -23,17 +23,19 @@ import com.volmit.iris.Iris; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.engine.mantle.MantleWriter; import com.volmit.iris.engine.object.*; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.mantle.Mantle; +import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.matter.slices.container.JigsawPieceContainer; +import com.volmit.iris.util.matter.slices.container.JigsawStructuresContainer; import lombok.Data; import org.bukkit.Axis; import org.bukkit.World; -import org.bukkit.block.TileState; -import org.bukkit.block.data.BlockData; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.IntConsumer; @Data public class PlannedStructure { @@ -74,27 +76,38 @@ public class PlannedStructure { } } - public void place(IObjectPlacer placer, Mantle e, Engine eng) { + public boolean place(IObjectPlacer placer, Mantle e, Engine eng) { IrisObjectPlacement options = new IrisObjectPlacement(); - options.getRotation().setEnabled(false); + options.setRotation(IrisObjectRotation.of(0,0,0)); int startHeight = pieces.get(0).getPosition().getY(); + boolean placed = false; for (PlannedPiece i : pieces) { - place(i, startHeight, options, placer, e, eng); + if (place(i, startHeight, options, placer, e, eng)) + placed = true; } + if (placed) { + Position2 chunkPos = new Position2(position.getX() >> 4, position.getZ() >> 4); + Position2 regionPos = new Position2(chunkPos.getX() >> 5, chunkPos.getZ() >> 5); + JigsawStructuresContainer slice = e.get(regionPos.getX(), 0, regionPos.getZ(), JigsawStructuresContainer.class); + if (slice == null) slice = new JigsawStructuresContainer(); + slice.add(structure, chunkPos); + e.set(regionPos.getX(), 0, regionPos.getZ(), slice); + } + return placed; } - public void place(PlannedPiece i, int startHeight, IrisObjectPlacement o, IObjectPlacer placer, Mantle e, Engine eng) { + public boolean place(PlannedPiece i, int startHeight, IrisObjectPlacement o, IObjectPlacer placer, Mantle e, Engine eng) { IrisObjectPlacement options = o; if (i.getPiece().getPlacementOptions() != null) { options = i.getPiece().getPlacementOptions(); options.getRotation().setEnabled(false); + options.setRotateTowardsSlope(false); } else { options.setMode(i.getPiece().getPlaceMode()); } - IrisObject vo = i.getOgObject(); IrisObject v = i.getObject(); int sx = (v.getW() / 2); int sz = (v.getD() / 2); @@ -108,7 +121,7 @@ public class PlannedStructure { if (i.getStructure().getStructure().getOverrideYRange() != null) { height = (int) i.getStructure().getStructure().getOverrideYRange().get(rng, xx, zz, getData()); } else { - height = placer.getHighest(xx, zz, getData()); + height = placer.getHighest(xx, zz, getData(), options.isUnderwater()); } } else { height = i.getStructure().getStructure().getLockY(); @@ -122,15 +135,22 @@ public class PlannedStructure { int id = rng.i(0, Integer.MAX_VALUE); JigsawPieceContainer container = JigsawPieceContainer.toContainer(i.getPiece()); - vo.place(xx, height, zz, placer, options, rng, (b, data) -> { + 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()); + }, null, getData()) != -1; } - public void place(World world) { + public void place(World world, IntConsumer consumer) { + AtomicInteger processed = new AtomicInteger(); + AtomicInteger failures = new AtomicInteger(); for (PlannedPiece i : pieces) { - Iris.sq(() -> i.place(world)); + Iris.sq(() -> { + if (!i.place(world)) failures.incrementAndGet(); + if (processed.incrementAndGet() == pieces.size()) { + consumer.accept(failures.get()); + } + }); } } @@ -167,9 +187,7 @@ public class PlannedStructure { private boolean generateRotatedPiece(PlannedPiece piece, IrisJigsawPieceConnector pieceConnector, IrisJigsawPiece idea) { if (!piece.getPiece().getPlacementOptions().getRotation().isEnabled()) { - if (generateRotatedPiece(piece, pieceConnector, idea, 0, 0, 0)) { - return true; - } + return generateRotatedPiece(piece, pieceConnector, idea, 0, 0, 0); } KList forder1 = new KList().qadd(0).qadd(1).qadd(2).qadd(3).shuffle(rng); @@ -216,7 +234,7 @@ public class PlannedStructure { } private boolean generateRotatedPiece(PlannedPiece piece, IrisJigsawPieceConnector pieceConnector, IrisJigsawPiece idea, int x, int y, int z) { - return generateRotatedPiece(piece, pieceConnector, idea, IrisObjectRotation.of(x, y, z)); + return generateRotatedPiece(piece, pieceConnector, idea, IrisObjectRotation.of(x * 90D, y * 90D, z * 90D)); } private boolean generatePositionedPiece(PlannedPiece piece, IrisJigsawPieceConnector pieceConnector, PlannedPiece test, IrisJigsawPieceConnector testConnector) { 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 d9255ef12..f10ff67c7 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,33 +18,42 @@ 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; import com.volmit.iris.engine.mantle.MantleWriter; import com.volmit.iris.engine.object.*; import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.context.ChunkContext; import com.volmit.iris.util.documentation.BlockCoordinates; import com.volmit.iris.util.documentation.ChunkCoordinates; import com.volmit.iris.util.mantle.MantleFlag; 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 java.util.List; public class MantleJigsawComponent extends IrisMantleComponent { - private final CNG cng; public MantleJigsawComponent(EngineMantle engineMantle) { super(engineMantle, MantleFlag.JIGSAW); - cng = NoiseStyle.STATIC.create(new RNG(engineMantle.getEngine().getSeedManager().getJigsaw())); + } + + 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))); } @Override public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) { - RNG rng = new RNG(cng.fit(-Integer.MAX_VALUE, Integer.MAX_VALUE, x, z)); + RNG rng = applyNoise(x, z); int xxx = 8 + (x << 4); int zzz = 8 + (z << 4); IrisRegion region = getComplex().getRegionStream().get(xxx, zzz); @@ -68,21 +77,58 @@ public class MantleJigsawComponent extends IrisMantleComponent { } } - boolean placed = placeStructures(writer, rng, x, z, biome.getJigsawStructures()); + KSet cachedRegions = new KSet<>(); + KMap> cache = new KMap<>(); + KMap distanceCache = new KMap<>(); + boolean placed = placeStructures(writer, rng, x, z, biome.getJigsawStructures(), cachedRegions, cache, distanceCache); if (!placed) - placed = placeStructures(writer, rng, x, z, region.getJigsawStructures()); + placed = placeStructures(writer, rng, x, z, region.getJigsawStructures(), cachedRegions, cache, distanceCache); if (!placed) - placeStructures(writer, rng, x, z, getDimension().getJigsawStructures()); + placeStructures(writer, rng, 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, RNG rng, 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()); - place(writer, position, structure, rng); - return true; + if (place(writer, position, structure, rng)) + return true; + } + } + return false; + } + + @ChunkCoordinates + private boolean checkMinDistances(KMap minDistances, int x, int z, KSet cachedRegions, KMap> cache, KMap distanceCache) { + int range = 0; + for (int d : minDistances.values()) + range = Math.max(range, d); + + for (int xx = -range; xx <= range; xx++) { + for (int zz = -range; zz <= range; zz++) { + Position2 pos = new Position2((xx + x) >> 5, (zz + z) >> 5); + if (cachedRegions.contains(pos)) continue; + cachedRegions.add(pos); + JigsawStructuresContainer container = getMantle().get(pos.getX(), 0, pos.getZ(), JigsawStructuresContainer.class); + if (container == null) continue; + for (String key : container.getStructures()) { + cache.computeIfAbsent(key, k -> new KSet<>()).addAll(container.getPositions(key)); + } + } + } + Position2 pos = new Position2(x, z); + for (String structure : minDistances.keySet()) { + if (!cache.containsKey(structure)) continue; + double minDist = minDistances.get(structure); + minDist = minDist * minDist; + for (Position2 sPos : cache.get(structure)) { + double dist = distanceCache.computeIfAbsent(sPos, position2 -> position2.distance(pos)); + if (minDist > dist) return true; } } return false; @@ -91,8 +137,8 @@ public class MantleJigsawComponent extends IrisMantleComponent { @ChunkCoordinates public IrisJigsawStructure guess(int x, int z) { // todo The guess doesnt bring into account that the placer may return -1 - boolean t = false; - RNG rng = new RNG(cng.fit(-Integer.MAX_VALUE, Integer.MAX_VALUE, x, z)); + // todo doesnt bring skipped placements into account + RNG rng = applyNoise(x, z); IrisBiome biome = getEngineMantle().getEngine().getSurfaceBiome((x << 4) + 8, (z << 4) + 8); IrisRegion region = getEngineMantle().getEngine().getRegion((x << 4) + 8, (z << 4) + 8); @@ -130,7 +176,7 @@ public class MantleJigsawComponent extends IrisMantleComponent { } @BlockCoordinates - private void place(MantleWriter writer, IrisPosition position, IrisJigsawStructure structure, RNG rng) { - new PlannedStructure(structure, position, rng).place(writer, getMantle(), writer.getEngine()); + private boolean place(MantleWriter writer, IrisPosition position, IrisJigsawStructure structure, RNG rng) { + return new PlannedStructure(structure, position, rng).place(writer, getMantle(), writer.getEngine()); } } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawMinDistance.java b/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawMinDistance.java new file mode 100644 index 000000000..818f4b3dd --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawMinDistance.java @@ -0,0 +1,29 @@ +package com.volmit.iris.engine.object; + +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 lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@Snippet("jigsaw-structure-min-distance") +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Desc("Represents the min distance between jigsaw structure placements") +@Data +public class IrisJigsawMinDistance { + @Required + @RegistryListResource(IrisJigsawStructure.class) + @Desc("The structure to check against") + private String structure; + + @Required + @MinNumber(0) + @Desc("The min distance in blocks to a placed structure\nWARNING: The performance impact scales exponentially!") + private int distance; +} diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawPiece.java b/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawPiece.java index 72feb927c..c69aca0e4 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawPiece.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawPiece.java @@ -101,19 +101,12 @@ public class IrisJigsawPiece extends IrisRegistrant { } public IrisJigsawPiece copy() { - IrisJigsawPiece p = new IrisJigsawPiece(); - p.setObject(getObject()); - p.setLoader(getLoader()); - p.setLoadKey(getLoadKey()); - p.setLoadFile(getLoadFile()); - p.setConnectors(new KList<>()); - p.setPlacementOptions(getPlacementOptions()); - - for (IrisJigsawPieceConnector i : getConnectors()) { - p.getConnectors().add(i.copy()); - } - - return p; + var gson = getLoader().getGson(); + IrisJigsawPiece copy = gson.fromJson(gson.toJson(this), IrisJigsawPiece.class); + copy.setLoader(getLoader()); + copy.setLoadKey(getLoadKey()); + copy.setLoadFile(getLoadFile()); + return copy; } public boolean isTerminal() { 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 609658e2d..16eea76e8 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,10 +18,13 @@ package com.volmit.iris.engine.object; +import com.volmit.iris.engine.object.annotations.ArrayType; import com.volmit.iris.engine.object.annotations.Desc; 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 lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; @@ -45,4 +48,20 @@ public class IrisJigsawStructurePlacement { @Required @Desc("The 1 in X chance rarity") private int rarity = 100; + + @ArrayType(type = IrisJigsawMinDistance.class) + @Desc("List of minimum distances to check for") + private KList minDistances = new KList<>(); + + public KMap collectMinDistances() { + KMap map = new KMap<>(); + for (IrisJigsawMinDistance d : minDistances) { + map.compute(d.getStructure(), (k, v) -> v != null ? Math.min(toChunks(d.getDistance()), v) : toChunks(d.getDistance())); + } + return map; + } + + private int toChunks(int blocks) { + return (int) Math.ceil(blocks / 16d); + } } 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 5225c4b23..c4306c115 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 @@ -533,10 +533,16 @@ public class IrisObject extends IrisRegistrant { } else if (min == hWest) { slopeRotationY = 270; } + + double newRotation = config.getRotation().getYAxis().getMin() + slopeRotationY; + if (newRotation == 0) { + config.getRotation().setYAxis(new IrisAxisRotationClamp(false, false, 0, 0, 90)); + config.getRotation().setEnabled(config.getRotation().canRotateX() || config.getRotation().canRotateZ()); + } else { + config.getRotation().setYAxis(new IrisAxisRotationClamp(true, false, newRotation, newRotation, 90)); + config.getRotation().setEnabled(true); + } } - double newRotation = config.getRotation().getYAxis().getMin() + slopeRotationY; - config.getRotation().setYAxis(new IrisAxisRotationClamp(true, false, newRotation, newRotation, 90)); - config.getRotation().setEnabled(true); } if (config.isSmartBore()) { diff --git a/core/src/main/java/com/volmit/iris/util/matter/slices/JigsawStructuresMatter.java b/core/src/main/java/com/volmit/iris/util/matter/slices/JigsawStructuresMatter.java new file mode 100644 index 000000000..55803b946 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/matter/slices/JigsawStructuresMatter.java @@ -0,0 +1,35 @@ +package com.volmit.iris.util.matter.slices; + +import com.volmit.iris.util.data.palette.Palette; +import com.volmit.iris.util.matter.Sliced; +import com.volmit.iris.util.matter.slices.container.JigsawStructuresContainer; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +@Sliced +public class JigsawStructuresMatter extends RawMatter { + public JigsawStructuresMatter() { + this(1, 1, 1); + } + + public JigsawStructuresMatter(int width, int height, int depth) { + super(width, height, depth, JigsawStructuresContainer.class); + } + + @Override + public Palette getGlobalPalette() { + return null; + } + + @Override + public void writeNode(JigsawStructuresContainer b, DataOutputStream dos) throws IOException { + b.write(dos); + } + + @Override + public JigsawStructuresContainer readNode(DataInputStream din) throws IOException { + return new JigsawStructuresContainer(din); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/matter/slices/container/JigsawStructuresContainer.java b/core/src/main/java/com/volmit/iris/util/matter/slices/container/JigsawStructuresContainer.java new file mode 100644 index 000000000..68d0f388f --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/matter/slices/container/JigsawStructuresContainer.java @@ -0,0 +1,62 @@ +package com.volmit.iris.util.matter.slices.container; + +import com.volmit.iris.engine.object.IrisJigsawStructure; +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.Position2; +import org.jetbrains.annotations.Unmodifiable; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class JigsawStructuresContainer { + private final Map> map = new KMap<>(); + + public JigsawStructuresContainer() {} + + public JigsawStructuresContainer(DataInputStream din) throws IOException { + int s0 = din.readInt(); + for (int i = 0; i < s0; i++) { + int s1 = din.readInt(); + KList list = new KList<>(s1); + for (int j = 0; j < s1; j++) { + list.add(new Position2(din.readInt(), din.readInt())); + } + map.put(din.readUTF(), list); + } + } + + public void write(DataOutputStream dos) throws IOException { + dos.writeInt(map.size()); + for (String key : map.keySet()) { + List list = map.get(key); + dos.writeInt(list.size()); + for (Position2 pos : list) { + dos.writeInt(pos.getX()); + dos.writeInt(pos.getZ()); + } + dos.writeUTF(key); + } + } + + @Unmodifiable + public Set getStructures() { + return Collections.unmodifiableSet(map.keySet()); + } + + @Unmodifiable + public List getPositions(String structure) { + return Collections.unmodifiableList(map.get(structure)); + } + + @ChunkCoordinates + public void add(IrisJigsawStructure structure, Position2 pos) { + map.computeIfAbsent(structure.getLoadKey(), k -> new KList<>()).add(pos); + } +}