From ebff9d6ce5ebf8e3f5e02d692f6dae9e6d3859c7 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 22 Jul 2024 16:33:57 -0700 Subject: [PATCH 1/3] WIP --- src/main/java/world/bentobox/boxed/Boxed.java | 2 + .../world/bentobox/boxed/BoxedPladdon.java | 7 +- .../commands/AdminPlaceStructureCommand.java | 4 +- .../boxed/listeners/NewAreaListener.java | 160 +++++++++++------- .../boxed/objects/ToBePlacedStructures.java | 78 +++++++++ 5 files changed, 189 insertions(+), 62 deletions(-) create mode 100644 src/main/java/world/bentobox/boxed/objects/ToBePlacedStructures.java diff --git a/src/main/java/world/bentobox/boxed/Boxed.java b/src/main/java/world/bentobox/boxed/Boxed.java index 8255fb5..1351833 100644 --- a/src/main/java/world/bentobox/boxed/Boxed.java +++ b/src/main/java/world/bentobox/boxed/Boxed.java @@ -17,6 +17,7 @@ import org.bukkit.generator.ChunkGenerator; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.commands.admin.DefaultAdminCommand; import world.bentobox.bentobox.api.commands.island.DefaultPlayerCommand; @@ -158,6 +159,7 @@ public class Boxed extends GameModeAddon { @Override public void onDisable() { + BentoBox.getInstance().logDebug("Disabling!"); // Save the advancements cache getAdvManager().save(); } diff --git a/src/main/java/world/bentobox/boxed/BoxedPladdon.java b/src/main/java/world/bentobox/boxed/BoxedPladdon.java index 5970347..5d25dec 100644 --- a/src/main/java/world/bentobox/boxed/BoxedPladdon.java +++ b/src/main/java/world/bentobox/boxed/BoxedPladdon.java @@ -7,9 +7,14 @@ import world.bentobox.bentobox.api.addons.Pladdon; public class BoxedPladdon extends Pladdon { + private Boxed addon; + @Override public Addon getAddon() { - return new Boxed(); + if (addon == null) { + addon = new Boxed(); + } + return addon; } } diff --git a/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java b/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java index 87e767c..82415d4 100644 --- a/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java +++ b/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java @@ -26,7 +26,7 @@ import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.util.Util; import world.bentobox.boxed.Boxed; import world.bentobox.boxed.listeners.NewAreaListener; -import world.bentobox.boxed.listeners.NewAreaListener.StructureRecord; +import world.bentobox.boxed.objects.ToBePlacedStructures.StructureRecord; /** * Enables admins to place templates in a Box and have them recorded for future boxes. @@ -149,7 +149,7 @@ public class AdminPlaceStructureCommand extends CompositeCommand { int z = args.size() == 1 || args.get(3).equals("~") ? user.getLocation().getBlockZ() : Integer.parseInt(args.get(3).trim()); Location spot = new Location(user.getWorld(), x, y, z); s.place(spot, true, sr, mirror, PALETTE, INTEGRITY, new Random()); - NewAreaListener.removeJigsaw(new StructureRecord(tag.getKey(), s, spot, sr, mirror, noMobs)); + NewAreaListener.removeJigsaw(new StructureRecord(tag.getKey(), tag.getKey(), spot, sr, mirror, noMobs)); boolean result = saveStructure(spot, tag, user, sr, mirror); if (result) { user.sendMessage("boxed.commands.boxadmin.place.saved"); diff --git a/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java b/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java index e58cc91..4c8ceae 100644 --- a/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java +++ b/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java @@ -58,28 +58,14 @@ import world.bentobox.boxed.nms.AbstractMetaData; import world.bentobox.boxed.objects.BoxedJigsawBlock; import world.bentobox.boxed.objects.BoxedStructureBlock; import world.bentobox.boxed.objects.IslandStructures; +import world.bentobox.boxed.objects.ToBePlacedStructures; +import world.bentobox.boxed.objects.ToBePlacedStructures.StructureRecord; /** * @author tastybento Place structures in areas after they are created */ public class NewAreaListener implements Listener { - /** - * Structure record contains the name of the structure, the structure itself, - * where it was placed and enums for rotation, mirror, and a flag to paste mobs - * or not. - * - * @param name - name of structure - * @param structure - Structure object - * @param location - location where it has been placed - * @param rot - rotation - * @param mirror - mirror setting - * @param noMobs - if false, mobs not pasted - */ - public record StructureRecord(String name, Structure structure, Location location, StructureRotation rot, - Mirror mirror, Boolean noMobs) { - } - private static final Map BUTCHER_ANIMALS = Map.of(0, EntityType.COW, 1, EntityType.SHEEP, 2, EntityType.PIG); private static final List CARDINALS = List.of(BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, @@ -95,16 +81,29 @@ public class NewAreaListener implements Listener { "village_snowy", "village_taiga"); private final Boxed addon; private final File structureFile; + /** + * Queue for structures that have been determined to be built now + */ private final Queue itemsToBuild = new LinkedList<>(); + + /** + * Store for structures that are pending being built, e.g., waiting until the chunk they are is in loaded + */ + private final Map, List> readyToBuild; + + /** + * A cache of all structures that have been placed. Used to determine if players have entered them + */ + private final Map islandStructureCache = new HashMap<>(); + private static final Random rand = new Random(); private boolean pasting = true; private static final Gson gson = new Gson(); - Pair min = new Pair<>(0, 0); - Pair max = new Pair<>(0, 0); + private static final String TODO = "ToDo"; // Database handler for structure data private final Database handler; - private final Map islandStructureCache = new HashMap<>(); - private Map, List> readyToBuild = new HashMap<>(); + private final Database todo; + private static String bukkitVersion = "v" + Bukkit.getBukkitVersion().replace('.', '_').replace('-', '_'); private static String pluginPackageName; @@ -120,12 +119,20 @@ public class NewAreaListener implements Listener { structureFile = new File(addon.getDataFolder(), "structures.yml"); // Get database ready handler = new Database<>(addon, IslandStructures.class); - // Try to build something every second + // Load the pending structures + todo = new Database(addon, ToBePlacedStructures.class); + readyToBuild = this.loadToDos().getReadyToBuild(); + // Try to build something runStructurePrinter(); } + /** + * Runs a recurring task to build structures in the queue and register Jar structures. + */ private void runStructurePrinter() { + // Set up recurring task Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), this::buildStructure, 100, 60); + // Run through all the structures in the Jar and register them with the server for (String js : JAR_STRUCTURES) { addon.saveResource("structures/" + js + ".nbt", false); File structureFile = new File(addon.getDataFolder(), "structures/" + js + ".nbt"); @@ -142,7 +149,7 @@ public class NewAreaListener implements Listener { } /** - * Build something in the queue + * Build something in the queue. Structures are built one by one */ private void buildStructure() { // Only kick off a build if there is something to build and something isn't @@ -158,7 +165,12 @@ public class NewAreaListener implements Listener { // Set the semaphore - only paste one at a time pasting = true; // Place the structure - this cannot be done async - item.structure().place(item.location(), true, item.rot(), item.mirror(), -1, 1, rand); + Structure structure = Bukkit.getStructureManager().loadStructure(NamespacedKey.fromString(item.structure())); + if (structure == null) { + BentoBox.getInstance().logError("Could not load " + item.structure()); + return; + } + structure.place(item.location(), true, item.rot(), item.mirror(), -1, 1, rand); addon.log(item.name() + " placed at " + item.location().getWorld().getName() + " " + Util.xyz(item.location().toVector())); // Remove any jigsaw artifacts @@ -173,6 +185,7 @@ public class NewAreaListener implements Listener { } handler.saveObjectAsync(getIslandStructData(id)); }); + // Remove from the todo list // Clear the semaphore pasting = false; } @@ -214,8 +227,8 @@ public class NewAreaListener implements Listener { return; } Pair chunkCoords = new Pair(chunk.getX(), chunk.getZ()); - if (this.readyToBuild.containsKey(chunkCoords)) { - Iterator it = this.readyToBuild.get(chunkCoords).iterator(); + if (readyToBuild.containsKey(chunkCoords)) { + Iterator it = readyToBuild.get(chunkCoords).iterator(); while (it.hasNext()) { StructureRecord item = it.next(); if (item.location().getWorld().equals(e.getWorld())) { @@ -223,6 +236,10 @@ public class NewAreaListener implements Listener { it.remove(); } } + // Save to latest to the database + ToBePlacedStructures tbd = new ToBePlacedStructures(); + tbd.setReadyToBuild(readyToBuild); + todo.saveObjectAsync(tbd); } } @@ -336,46 +353,53 @@ public class NewAreaListener implements Listener { if (world == null) { return; } - // Loop through the structures in the file - there could be more than one + + Map, List> readyToBuild = new HashMap<>(); + for (String vector : section.getKeys(false)) { - StructureRotation rot = StructureRotation.NONE; - Mirror mirror = Mirror.NONE; - boolean noMobs = false; - String name = section.getString(vector); - // Check for rotation - String[] split = name.split(","); - if (split.length > 1) { - // Rotation - rot = Enums.getIfPresent(StructureRotation.class, split[1].strip().toUpperCase(Locale.ENGLISH)) - .or(StructureRotation.NONE); - name = split[0]; - } - if (split.length == 3) { - // Mirror - mirror = Enums.getIfPresent(Mirror.class, split[2].strip().toUpperCase(Locale.ENGLISH)).or(Mirror.NONE); - } - if (split.length == 4) { - noMobs = split[3].strip().toUpperCase(Locale.ENGLISH).equals("NO_MOBS"); - } - // Load Structure - Structure s = Bukkit.getStructureManager().loadStructure(NamespacedKey.fromString("minecraft:" + name)); - if (s == null) { + String[] nameParts = section.getString(vector).split(","); + String name = nameParts[0].strip(); + StructureRotation rotation = nameParts.length > 1 + ? Enums.getIfPresent(StructureRotation.class, nameParts[1].strip().toUpperCase(Locale.ENGLISH)).or( + StructureRotation.NONE) + : StructureRotation.NONE; + Mirror mirror = nameParts.length > 2 + ? Enums.getIfPresent(Mirror.class, nameParts[2].strip().toUpperCase(Locale.ENGLISH)).or(Mirror.NONE) + : Mirror.NONE; + boolean noMobs = nameParts.length > 3 && "NO_MOBS".equalsIgnoreCase(nameParts[3].strip()); + + // Check the structure exists + Structure structure = Bukkit.getStructureManager() + .loadStructure(NamespacedKey.fromString("minecraft:" + name)); + if (structure == null) { BentoBox.getInstance().logError("Could not load " + name); return; } - // Extract coords - String[] value = vector.split(","); - if (value.length > 2) { - int x = Integer.parseInt(value[0].strip()) + center.getBlockX(); - int y = Integer.parseInt(value[1].strip()); - int z = Integer.parseInt(value[2].strip()) + center.getBlockZ(); - Location l = new Location(world, x, y, z); - readyToBuild.computeIfAbsent(new Pair(x >> 4, z >> 4), k -> new ArrayList<>()) - .add(new StructureRecord(name, s, l, rot, mirror, noMobs)); + + String[] coords = vector.split(","); + if (coords.length > 2) { + int x = Integer.parseInt(coords[0].strip()) + center.getBlockX(); + int y = Integer.parseInt(coords[1].strip()); + int z = Integer.parseInt(coords[2].strip()) + center.getBlockZ(); + Location location = new Location(world, x, y, z); + + readyToBuild.computeIfAbsent(new Pair<>(x >> 4, z >> 4), k -> new ArrayList<>()) + .add(new StructureRecord(name, "minecraft:" + name, location, + rotation, mirror, noMobs)); } else { - addon.logError("Structure file syntax error: " + vector + ": " + Arrays.toString(value)); + addon.logError("Structure file syntax error: " + vector + ": " + Arrays.toString(coords)); } } + + ToBePlacedStructures tbd = this.loadToDos(); + Map, List> mergedMap = tbd.getReadyToBuild(); + readyToBuild.forEach((key, value) -> mergedMap.merge(key, value, (list1, list2) -> { + list1.addAll(list2); + return list1; + })); + + tbd.setReadyToBuild(readyToBuild); + todo.saveObjectAsync(tbd); } /** @@ -387,7 +411,11 @@ public class NewAreaListener implements Listener { */ public static BoundingBox removeJigsaw(StructureRecord item) { Location loc = item.location(); - Structure structure = item.structure(); + Structure structure = Bukkit.getStructureManager().loadStructure(NamespacedKey.fromString(item.structure())); + if (structure == null) { + BentoBox.getInstance().logError("Could not load " + item.structure()); + return new BoundingBox(); + } StructureRotation structureRotation = item.rot(); String key = item.name(); @@ -600,4 +628,18 @@ public class NewAreaListener implements Listener { return handler.nmsData(block); } + private ToBePlacedStructures loadToDos() { + if (!todo.objectExists(TODO)) { + return new ToBePlacedStructures(); + } + ToBePlacedStructures list = todo.loadObject(TODO); + if (list == null) { + return new ToBePlacedStructures(); + } + if (!list.getReadyToBuild().isEmpty()) { + addon.log("Loaded " + list.getReadyToBuild().size() + " structure todos."); + } + return list; + } + } diff --git a/src/main/java/world/bentobox/boxed/objects/ToBePlacedStructures.java b/src/main/java/world/bentobox/boxed/objects/ToBePlacedStructures.java new file mode 100644 index 0000000..c47a26a --- /dev/null +++ b/src/main/java/world/bentobox/boxed/objects/ToBePlacedStructures.java @@ -0,0 +1,78 @@ +package world.bentobox.boxed.objects; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bukkit.Location; +import org.bukkit.block.structure.Mirror; +import org.bukkit.block.structure.StructureRotation; + +import com.google.gson.annotations.Expose; + +import world.bentobox.bentobox.database.objects.DataObject; +import world.bentobox.bentobox.database.objects.Table; +import world.bentobox.bentobox.util.Pair; + +/** + * Stores all the structures to be placed in the world. This is a queue that is done over + * time to avoid lag and if the server is stopped then the todo list is saved here + * @author tastybento + * + */ +@Table(name = "ToBePlacedStructures") +public class ToBePlacedStructures implements DataObject { + + /** + * Structure record contains the name of the structure, the structure itself, + * where it was placed and enums for rotation, mirror, and a flag to paste mobs + * or not. + * + * @param name - name of structure + * @param structure - Structure namespaced key + * @param location - location where it has been placed + * @param rot - rotation + * @param mirror - mirror setting + * @param noMobs - if false, mobs not pasted + */ + public record StructureRecord(@Expose String name, @Expose String structure, @Expose Location location, + @Expose StructureRotation rot, @Expose Mirror mirror, @Expose Boolean noMobs) { + } + + @Expose + String uniqueId = "ToDo"; + @Expose + private Map, List> readyToBuild = new HashMap<>(); + + /** + * @return the uniqueId + */ + public String getUniqueId() { + return uniqueId; + } + + /** + * @param uniqueId the uniqueId to set + */ + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + } + + /** + * @return the readyToBuild + */ + public Map, List> getReadyToBuild() { + if (readyToBuild == null) { + readyToBuild = new HashMap<>(); + } + return readyToBuild; + } + + /** + * @param readyToBuild the readyToBuild to set + */ + public void setReadyToBuild(Map, List> readyToBuild) { + this.readyToBuild = readyToBuild; + } + +} \ No newline at end of file From cc85ca2279b6d86a00518f15b10b5e65bd48ffa8 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 27 Jul 2024 16:55:07 -0700 Subject: [PATCH 2/3] Remove debug --- src/main/java/world/bentobox/boxed/Boxed.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/world/bentobox/boxed/Boxed.java b/src/main/java/world/bentobox/boxed/Boxed.java index 1351833..8255fb5 100644 --- a/src/main/java/world/bentobox/boxed/Boxed.java +++ b/src/main/java/world/bentobox/boxed/Boxed.java @@ -17,7 +17,6 @@ import org.bukkit.generator.ChunkGenerator; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.commands.admin.DefaultAdminCommand; import world.bentobox.bentobox.api.commands.island.DefaultPlayerCommand; @@ -159,7 +158,6 @@ public class Boxed extends GameModeAddon { @Override public void onDisable() { - BentoBox.getInstance().logDebug("Disabling!"); // Save the advancements cache getAdvManager().save(); } From cf4531fd262c1b8dde827901911929a5eb12d886 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 27 Jul 2024 17:00:05 -0700 Subject: [PATCH 3/3] Resolve static analysis issues --- .../boxed/listeners/NewAreaListener.java | 30 +++++++++---------- .../boxed/objects/ToBePlacedStructures.java | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java b/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java index 4c8ceae..42721b0 100644 --- a/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java +++ b/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java @@ -89,7 +89,7 @@ public class NewAreaListener implements Listener { /** * Store for structures that are pending being built, e.g., waiting until the chunk they are is in loaded */ - private final Map, List> readyToBuild; + private final Map, List> pending; /** * A cache of all structures that have been placed. Used to determine if players have entered them @@ -100,9 +100,10 @@ public class NewAreaListener implements Listener { private boolean pasting = true; private static final Gson gson = new Gson(); private static final String TODO = "ToDo"; + private static final String COULD_NOT_LOAD = "Could not load "; // Database handler for structure data private final Database handler; - private final Database todo; + private final Database toPlace; private static String bukkitVersion = "v" + Bukkit.getBukkitVersion().replace('.', '_').replace('-', '_'); private static String pluginPackageName; @@ -120,8 +121,8 @@ public class NewAreaListener implements Listener { // Get database ready handler = new Database<>(addon, IslandStructures.class); // Load the pending structures - todo = new Database(addon, ToBePlacedStructures.class); - readyToBuild = this.loadToDos().getReadyToBuild(); + toPlace = new Database<>(addon, ToBePlacedStructures.class); + pending = this.loadToDos().getReadyToBuild(); // Try to build something runStructurePrinter(); } @@ -167,7 +168,7 @@ public class NewAreaListener implements Listener { // Place the structure - this cannot be done async Structure structure = Bukkit.getStructureManager().loadStructure(NamespacedKey.fromString(item.structure())); if (structure == null) { - BentoBox.getInstance().logError("Could not load " + item.structure()); + BentoBox.getInstance().logError(COULD_NOT_LOAD + item.structure()); return; } structure.place(item.location(), true, item.rot(), item.mirror(), -1, 1, rand); @@ -185,7 +186,6 @@ public class NewAreaListener implements Listener { } handler.saveObjectAsync(getIslandStructData(id)); }); - // Remove from the todo list // Clear the semaphore pasting = false; } @@ -227,8 +227,8 @@ public class NewAreaListener implements Listener { return; } Pair chunkCoords = new Pair(chunk.getX(), chunk.getZ()); - if (readyToBuild.containsKey(chunkCoords)) { - Iterator it = readyToBuild.get(chunkCoords).iterator(); + if (pending.containsKey(chunkCoords)) { + Iterator it = pending.get(chunkCoords).iterator(); while (it.hasNext()) { StructureRecord item = it.next(); if (item.location().getWorld().equals(e.getWorld())) { @@ -238,8 +238,8 @@ public class NewAreaListener implements Listener { } // Save to latest to the database ToBePlacedStructures tbd = new ToBePlacedStructures(); - tbd.setReadyToBuild(readyToBuild); - todo.saveObjectAsync(tbd); + tbd.setReadyToBuild(pending); + toPlace.saveObjectAsync(tbd); } } @@ -372,7 +372,7 @@ public class NewAreaListener implements Listener { Structure structure = Bukkit.getStructureManager() .loadStructure(NamespacedKey.fromString("minecraft:" + name)); if (structure == null) { - BentoBox.getInstance().logError("Could not load " + name); + BentoBox.getInstance().logError(COULD_NOT_LOAD + name); return; } @@ -399,7 +399,7 @@ public class NewAreaListener implements Listener { })); tbd.setReadyToBuild(readyToBuild); - todo.saveObjectAsync(tbd); + toPlace.saveObjectAsync(tbd); } /** @@ -413,7 +413,7 @@ public class NewAreaListener implements Listener { Location loc = item.location(); Structure structure = Bukkit.getStructureManager().loadStructure(NamespacedKey.fromString(item.structure())); if (structure == null) { - BentoBox.getInstance().logError("Could not load " + item.structure()); + BentoBox.getInstance().logError(COULD_NOT_LOAD + item.structure()); return new BoundingBox(); } StructureRotation structureRotation = item.rot(); @@ -629,10 +629,10 @@ public class NewAreaListener implements Listener { } private ToBePlacedStructures loadToDos() { - if (!todo.objectExists(TODO)) { + if (!toPlace.objectExists(TODO)) { return new ToBePlacedStructures(); } - ToBePlacedStructures list = todo.loadObject(TODO); + ToBePlacedStructures list = toPlace.loadObject(TODO); if (list == null) { return new ToBePlacedStructures(); } diff --git a/src/main/java/world/bentobox/boxed/objects/ToBePlacedStructures.java b/src/main/java/world/bentobox/boxed/objects/ToBePlacedStructures.java index c47a26a..5d8078c 100644 --- a/src/main/java/world/bentobox/boxed/objects/ToBePlacedStructures.java +++ b/src/main/java/world/bentobox/boxed/objects/ToBePlacedStructures.java @@ -16,7 +16,7 @@ import world.bentobox.bentobox.util.Pair; /** * Stores all the structures to be placed in the world. This is a queue that is done over - * time to avoid lag and if the server is stopped then the todo list is saved here + * time to avoid lag and if the server is stopped then the pending list is saved here * @author tastybento * */