diff --git a/.github/workflows/modrinth-publish.yml b/.github/workflows/modrinth-publish.yml new file mode 100644 index 0000000..72b93e6 --- /dev/null +++ b/.github/workflows/modrinth-publish.yml @@ -0,0 +1,43 @@ +name: Publish + +on: + release: + types: [published] + +jobs: + publish: + name: Publish + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: 21 + distribution: adopt + cache: maven + + # This step will take the version tag from the release and replace it in `pom.xml` before building. + #- name: Set version from release tag + # run: mvn -B versions:set -DnewVersion=${{ github.event.release.tag_name }} -DgenerateBackupPoms=false + + - name: Build and package with Maven + run: mvn -B clean package -DskipTests -DgenerateBackupPoms=false -Pmaster --file pom.xml + + - name: Upload to Modrinth + uses: cloudnode-pro/modrinth-publish@v2 + with: + token: ${{ secrets.MODRINTH_TOKEN }} + project: F4I5miJX + name: ${{ github.event.release.name }} + version: ${{ github.event.release.tag_name }} + changelog: ${{ github.event.release.body }} + loaders: |- + paper + spigot + game-versions: |- + 1.21.4 + 1.21.5 + files: /home/runner/work/Boxed/Boxed/target/Boxed-${{ github.event.release.tag_name }}.jar diff --git a/README.md b/README.md index 0c7235a..d9ae27a 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,8 @@ java -Xms12G -Xmx12G -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMill ### Quick Start 1. Place Boxed addon into the BentoBox addons folder along with InvSwitcher and Border (use the latest versions!). -2. Place the WorldGeneratorAPI and WorldBorderAPI plugins into your plugins folder. -3. Make sure you are using BentoBox 1.16.0-SNAPSHOT or later. -4. Restart the server - new worlds will be created. There may be a delay. +2. (Optional) Installed the Datapack for custom advancements - https://github.com/BentoBoxWorld/BoxedDataPack/ +4. Restart the server - new worlds will be created. This will take a while! 5. Login 6. Type `/boxed` to start. 7. Turn off advancement announcements `/gamerule announceAdvancements false` otherwise there is a lot of spam from the server when players get advancements. diff --git a/pom.xml b/pom.xml index 87e8ef0..293578e 100644 --- a/pom.xml +++ b/pom.xml @@ -54,15 +54,15 @@ 2.0.9 - 1.21.5-R0.1-SNAPSHOT - 1.21.5-R0.1-SNAPSHOT - 2.7.1-SNAPSHOT + 1.21.6-R0.1-SNAPSHOT + 1.21.6-R0.1-SNAPSHOT + 3.5.0 ${build.version}-SNAPSHOT -LOCAL - 2.8.2 + 3.0.0 BentoBoxWorld_Boxed bentobox-world @@ -183,6 +183,12 @@ ${spigot.version} provided + + org.spigotmc.... + spigot + 1.21.5-R0.1-SNAPSHOT + provided + org.spigotmc... spigot diff --git a/src/main/java/.gitignore b/src/main/java/.gitignore new file mode 100644 index 0000000..9bb88d3 --- /dev/null +++ b/src/main/java/.gitignore @@ -0,0 +1 @@ +/.DS_Store diff --git a/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java b/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java index 82415d4..4e4d7b2 100644 --- a/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java +++ b/src/main/java/world/bentobox/boxed/commands/AdminPlaceStructureCommand.java @@ -4,19 +4,27 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.Random; +import java.util.Stack; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.NamespacedKey; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; import org.bukkit.block.structure.Mirror; import org.bukkit.block.structure.StructureRotation; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.structure.Structure; +import org.bukkit.util.BlockTransformer; +import org.bukkit.util.Vector; import com.google.common.base.Enums; @@ -52,6 +60,8 @@ public class AdminPlaceStructureCommand extends CompositeCommand { private Mirror mirror = Mirror.NONE; private boolean noMobs; + private final Stack placedStructures = new Stack<>(); + public AdminPlaceStructureCommand(CompositeCommand parent) { super(parent, "place"); } @@ -68,15 +78,20 @@ public void setup() { @Override public boolean canExecute(User user, String label, List args) { + if (args.size() == 1 && args.get(0).equalsIgnoreCase("undo")) { + return true; // Allow "undo" command without additional checks + } + // Initialize sr = StructureRotation.NONE; mirror = Mirror.NONE; // Check world - if (!((Boxed)getAddon()).inWorld(getWorld())) { + if (!((Boxed) getAddon()).inWorld(getWorld())) { user.sendMessage("boxed.commands.boxadmin.place.wrong-world"); return false; } + /* * Acceptable syntax with number of args: * 1. place @@ -85,21 +100,23 @@ public boolean canExecute(User user, String label, List args) { * 6. place ~ ~ ~ ROTATION MIRROR * 7. place ~ ~ ~ ROTATION MIRROR NO_MOBS */ - // Format is place ~ ~ ~ or coords if (args.isEmpty() || args.size() == 2 || args.size() == 3 || args.size() > 6) { this.showHelp(this, user); return false; } + // First arg must always be the structure name List options = Bukkit.getStructureManager().getStructures().keySet().stream().map(NamespacedKey::getKey).toList(); if (!options.contains(args.get(0).toLowerCase(Locale.ENGLISH))) { user.sendMessage("boxed.commands.boxadmin.place.unknown-structure"); return false; } + // If that is all we have, we're done if (args.size() == 1) { return true; } + // Next come the coordinates - there must be at least 3 of them if ((!args.get(1).equals("~") && !Util.isInteger(args.get(1), true)) || (!args.get(2).equals("~") && !Util.isInteger(args.get(2), true)) @@ -107,27 +124,32 @@ public boolean canExecute(User user, String label, List args) { user.sendMessage("boxed.commands.boxadmin.place.use-integers"); return false; } + // If that is all we have, we're done if (args.size() == 4) { return true; } - // But there is more! + + // Handle rotation sr = Enums.getIfPresent(StructureRotation.class, args.get(4).toUpperCase(Locale.ENGLISH)).orNull(); if (sr == null) { user.sendMessage("boxed.commands.boxadmin.place.unknown-rotation"); Arrays.stream(StructureRotation.values()).map(StructureRotation::name).forEach(user::sendRawMessage); return false; } + if (args.size() == 5) { return true; } - // But there is more! + + // Handle mirror mirror = Enums.getIfPresent(Mirror.class, args.get(5).toUpperCase(Locale.ENGLISH)).orNull(); if (mirror == null) { user.sendMessage("boxed.commands.boxadmin.place.unknown-mirror"); Arrays.stream(Mirror.values()).map(Mirror::name).forEach(user::sendRawMessage); return false; } + if (args.size() == 7) { if (args.get(6).toUpperCase(Locale.ENGLISH).equals("NO_MOBS")) { noMobs = true; @@ -136,20 +158,40 @@ public boolean canExecute(User user, String label, List args) { return false; } } + // Syntax is okay return true; } @Override public boolean execute(User user, String label, List args) { + if (args.size() == 1 && args.get(0).equalsIgnoreCase("undo")) { + return undoLastPlacement(user); + } + NamespacedKey tag = NamespacedKey.fromString(args.get(0).toLowerCase(Locale.ENGLISH)); Structure s = Bukkit.getStructureManager().loadStructure(tag); - int x = args.size() == 1 || args.get(1).equals("~") ? user.getLocation().getBlockX() : Integer.parseInt(args.get(1).trim()); - int y = args.size() == 1 || args.get(2).equals("~") ? user.getLocation().getBlockY() : Integer.parseInt(args.get(2).trim()); - int z = args.size() == 1 || args.get(3).equals("~") ? user.getLocation().getBlockZ() : Integer.parseInt(args.get(3).trim()); + int x = args.size() == 1 || args.get(1).equals("~") ? user.getLocation().getBlockX() + : Integer.parseInt(args.get(1).trim()); + int y = args.size() == 1 || args.get(2).equals("~") ? user.getLocation().getBlockY() + : Integer.parseInt(args.get(2).trim()); + 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(), tag.getKey(), spot, sr, mirror, noMobs)); + Map removedBlocks = new HashMap<>(); + BlockTransformer store = (region, xx, yy, zz, current, state) -> { + // Store the state + removedBlocks.put(new Vector(xx, yy, zz), region.getBlockData(xx, yy, zz)); + return state.getOriginal(); + }; + + s.place(spot, true, sr, mirror, PALETTE, INTEGRITY, new Random(), Collections.singleton(store), // Transformer to store blocks + Collections.emptyList() // No entity transformers + ); + NewAreaListener + .removeJigsaw(new StructureRecord(tag.getKey(), tag.getKey(), spot, sr, mirror, noMobs, removedBlocks)); + placedStructures.push(new StructureRecord(tag.getKey(), tag.getKey(), spot, sr, mirror, noMobs, removedBlocks)); // Track the placement + boolean result = saveStructure(spot, tag, user, sr, mirror); if (result) { user.sendMessage("boxed.commands.boxadmin.place.saved"); @@ -184,9 +226,74 @@ private boolean saveStructure(Location spot, NamespacedKey tag, User user, Struc } + private boolean undoLastPlacement(User user) { + if (placedStructures.isEmpty()) { + user.sendMessage("boxed.commands.boxadmin.place.no-undo"); + return false; + } + + StructureRecord lastRecord = placedStructures.pop(); + NamespacedKey tag = NamespacedKey.fromString(lastRecord.name()); + Structure s = Bukkit.getStructureManager().loadStructure(tag); + + if (s == null) { + user.sendMessage("boxed.commands.boxadmin.place.undo-failed"); + return false; + } + + BlockTransformer erase = (region, x, y, z, current, state) -> { + Vector v = new Vector(x, y, z); + if (lastRecord.removedBlocks().containsKey(v)) { + return lastRecord.removedBlocks().get(v).createBlockState(); + } + BlockState airState = Material.AIR.createBlockData().createBlockState(); + return airState; + }; + + s.place( + lastRecord.location(), + false, // Don't respawn entities + lastRecord.rot(), lastRecord.mirror(), + PALETTE, + 1.0f, // Integrity = 1 means "place everything" + new Random(), + Collections.singleton(erase), // Transformer to erase blocks + Collections.emptyList() // No entity transformers + ); + lastRecord.removedBlocks().clear(); + removeStructure(lastRecord.location(), tag, user); // Remove from config + + user.sendMessage("boxed.commands.boxadmin.place.undo-success"); + return true; + } + + private boolean removeStructure(Location spot, NamespacedKey tag, User user) { + return getAddon().getIslands().getIslandAt(spot).map(i -> { + int xx = spot.getBlockX() - i.getCenter().getBlockX(); + int zz = spot.getBlockZ() - i.getCenter().getBlockZ(); + File structures = new File(getAddon().getDataFolder(), STRUCTURE_FILE); + YamlConfiguration config = new YamlConfiguration(); + try { + config.load(structures); + String key = spot.getWorld().getEnvironment().name().toLowerCase(Locale.ENGLISH) + "." + xx + "," + spot.getBlockY() + "," + zz; + if (config.contains(key)) { + config.set(key, null); // Remove the entry + config.save(structures); + return true; + } + } catch (IOException | InvalidConfigurationException e) { + e.printStackTrace(); + } + return false; + }).orElse(false); + } + @Override public Optional> tabComplete(User user, String alias, List args) { + if (args.size() == 1) { + return Optional.of(Util.tabLimit(Arrays.asList("undo"), args.get(0))); + } String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : ""; if (args.size() == 2) { return Optional.of(Util.tabLimit(Bukkit.getStructureManager().getStructures().keySet().stream().map(NamespacedKey::getKey).toList(), lastArg)); @@ -205,4 +312,7 @@ public Optional> tabComplete(User user, String alias, List } return Optional.of(Collections.emptyList()); } + + + } \ No newline at end of file diff --git a/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java b/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java index 2ac8399..928c34d 100644 --- a/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java +++ b/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; @@ -48,6 +49,7 @@ import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.events.BentoBoxReadyEvent; import world.bentobox.bentobox.api.events.island.IslandCreatedEvent; +import world.bentobox.bentobox.api.events.island.IslandDeleteEvent; import world.bentobox.bentobox.api.events.island.IslandResettedEvent; import world.bentobox.bentobox.database.Database; import world.bentobox.bentobox.database.objects.Island; @@ -109,7 +111,10 @@ public class NewAreaListener implements Listener { private static String pluginPackageName; /** - * @param addon addon + * Constructor for NewAreaListener. + * Initializes structure files, databases, and starts the structure printer. + * + * @param addon The Boxed addon instance. */ public NewAreaListener(Boxed addon) { this.addon = addon; @@ -150,7 +155,8 @@ private void runStructurePrinter() { } /** - * Build something in the queue. Structures are built one by one + * Builds a structure from the queue if not already pasting. + * Only one structure is built at a time. */ private void buildStructure() { // Only kick off a build if there is something to build and something isn't @@ -162,6 +168,11 @@ private void buildStructure() { } } + /** + * Places a structure at the specified location and updates the island structure cache. + * + * @param item The structure record to place. + */ private void placeStructure(StructureRecord item) { // Set the semaphore - only paste one at a time pasting = true; @@ -191,9 +202,7 @@ private void placeStructure(StructureRecord item) { } /** - * Load known structures from the templates file. This makes them available for - * admins to use in the boxed place file. If this is not done, then no - * structures (templates) will be available. + * Loads known structures from the templates file and registers them with the server. * * @param event BentoBoxReadyEvent */ @@ -216,7 +225,8 @@ public void onBentoBoxReady(BentoBoxReadyEvent event) { } /** - * Add items to the queue when they are needed due to chunk loading + * Adds items to the build queue when their chunk is loaded. + * * @param e ChunkLoadEvent */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @@ -280,10 +290,10 @@ public void onPlayerMove(PlayerMoveEvent e) { } /** - * Gives a player all the advancements that have string as a named criteria + * Gives a player all advancements that have the specified string as a named criteria. * - * @param player - player - * @param string - criteria + * @param player The player to award. + * @param string The advancement criteria string. */ private void giveAdvFromCriteria(Player player, String string) { // Give every advancement that requires a bastion @@ -302,10 +312,10 @@ private void giveAdvFromCriteria(Player player, String string) { } /** - * Get all the known island structures for this island + * Gets all known island structures for the specified island. * - * @param islandId - island ID - * @return IslandStructures + * @param islandId The island ID. + * @return IslandStructures for the island. */ private IslandStructures getIslandStructData(String islandId) { // Return from cache if it exists @@ -319,16 +329,66 @@ private IslandStructures getIslandStructData(String islandId) { return struct; } + /** + * Handles island creation event and sets up structures for the new island. + * + * @param event IslandCreatedEvent + */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onIslandCreated(IslandCreatedEvent event) { setUpIsland(event.getIsland()); } + /** + * Handles island reset event and sets up structures for the reset island. + * + * @param event IslandResettedEvent + */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onIslandReset(IslandResettedEvent event) { setUpIsland(event.getIsland()); } + /** + * Handles island deletion event and removes all pending structures for the deleted island. + * + * @param event IslandDeletedEvent + */ + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onIslandDeleted(IslandDeleteEvent event) { + String deletedIslandId = event.getIsland().getUniqueId(); + + // Remove from in-memory cache + islandStructureCache.remove(deletedIslandId); + + // Remove from in-memory pending structures + for (List records : pending.values()) { + records.removeIf(record -> event.getIsland().inIslandSpace(record.location())); + } + pending.values().removeIf(list -> list.isEmpty()); + + // Remove from pending structures in database + Map, List> readyToBuild = loadToDos().getReadyToBuild(); + boolean dbChanged = false; + for (List records : readyToBuild.values()) { + if (records.removeIf(record -> event.getIsland().inIslandSpace(record.location()))) { + dbChanged = true; + } + } + + // Save updated pending structures if any were removed + if (dbChanged) { + ToBePlacedStructures tbd = new ToBePlacedStructures(); + tbd.setReadyToBuild(readyToBuild); + toPlace.saveObjectAsync(tbd); + } + } + + /** + * Sets up structures for the given island based on the configuration. + * + * @param island The island to set up. + */ private void setUpIsland(Island island) { // Check if this island is in this game if (!(addon.inWorld(island.getWorld()))) { @@ -349,6 +409,13 @@ private void setUpIsland(Island island) { } + /** + * Places structures defined in the configuration section at the specified center location. + * + * @param section The configuration section. + * @param center The center location. + * @param env The world environment. + */ private void place(ConfigurationSection section, Location center, Environment env) { if (section == null) { return; @@ -388,9 +455,11 @@ private void place(ConfigurationSection section, Location center, Environment en Location location = new Location(world, x, y, z); // Structure will be placed at location readyToBuild.computeIfAbsent(new Pair<>(x >> 4, z >> 4), k -> new ArrayList<>()) - .add(new StructureRecord(name, "minecraft:" + name, location, rotation, mirror, noMobs)); + .add(new StructureRecord(name, "minecraft:" + name, location, rotation, mirror, noMobs, + Collections.emptyMap())); this.itemsToBuild - .add(new StructureRecord(name, "minecraft:" + name, location, rotation, mirror, noMobs)); + .add(new StructureRecord(name, "minecraft:" + name, location, rotation, mirror, noMobs, + Collections.emptyMap())); } else { addon.logError("Structure file syntax error: " + vector + ": " + Arrays.toString(coords)); } @@ -409,11 +478,10 @@ private void place(ConfigurationSection section, Location center, Environment en } /** - * Removes Jigsaw blocks from a placed structure. Fills underwater ruins with - * water. + * Removes Jigsaw blocks from a placed structure and fills underwater ruins with water. * - * @param item - record of what's required - * @return the resulting bounding box of the structure + * @param item The structure record. + * @return The resulting bounding box of the structure. */ public static BoundingBox removeJigsaw(StructureRecord item) { Location loc = item.location(); @@ -463,12 +531,9 @@ public static BoundingBox removeJigsaw(StructureRecord item) { } /** - * Process a structure block. Sets it to a structure void at a minimum. If the - * structure block has metadata indicating it is a chest, then it will fill the - * chest with a buried treasure loot. If it is waterlogged, then it will change - * the void to water. + * Processes a structure block, possibly spawning entities or filling chests with loot. * - * @param b structure block + * @param b The structure block. */ private static void processStructureBlock(Block b) { // I would like to read the data from the block and do something with it! @@ -496,6 +561,13 @@ private static void processStructureBlock(Block b) { } } + /** + * Processes a jigsaw block, setting its final state and spawning mobs if needed. + * + * @param b The jigsaw block. + * @param structureRotation The structure's rotation. + * @param pasteMobs Whether to spawn mobs. + */ private static void processJigsaw(Block b, StructureRotation structureRotation, boolean pasteMobs) { try { String data = nmsData(b); @@ -514,6 +586,12 @@ private static void processJigsaw(Block b, StructureRotation structureRotation, } } + /** + * Spawns a mob at the specified block based on the jigsaw block's pool. + * + * @param b The block. + * @param bjb The BoxedJigsawBlock data. + */ private static void spawnMob(Block b, BoxedJigsawBlock bjb) { // bjb.getPool contains a lot more than just mobs, so we have to filter it to // see if any mobs are in there. This list may need to grow in the future @@ -552,12 +630,11 @@ private static void spawnMob(Block b, BoxedJigsawBlock bjb) { } /** - * Corrects the direction of a block based on the structure's rotation + * Corrects the direction of a block based on the structure's rotation. * - * @param finalState - the final block state of the block, which may include a - * facing: direction - * @param sr - the structure's rotation - * @return a rewritten blockstate with the updated direction, if required + * @param finalState The final block state. + * @param sr The structure's rotation. + * @return The corrected block state string. */ private static String correctDirection(String finalState, StructureRotation sr) { if (sr.equals(StructureRotation.NONE)) { @@ -576,11 +653,11 @@ private static String correctDirection(String finalState, StructureRotation sr) } /** - * Adjusts the direction based on the StructureRotation + * Adjusts the direction based on the StructureRotation. * - * @param oldDirection the old direction to adjust - * @param sr the structure rotation - * @return the new direction, or SELF if something weird happens + * @param oldDirection The old direction. + * @param sr The structure rotation. + * @return The new direction, or SELF if not applicable. */ private static BlockFace getNewDirection(BlockFace oldDirection, StructureRotation sr) { if (sr.equals(StructureRotation.CLOCKWISE_180)) { @@ -606,16 +683,22 @@ private static BlockFace getNewDirection(BlockFace oldDirection, StructureRotati } /** - * Looks for north, south, east, west in the blockstate. + * Looks for north, south, east, or west in the blockstate string. * - * @param finalState - the final block state of the block - * @return direction, if found, otherwise SELF + * @param finalState The final block state string. + * @return The detected direction, or SELF if not found. */ private static BlockFace getDirection(String finalState) { return CARDINALS.stream().filter(bf -> finalState.contains(bf.name().toLowerCase(Locale.ENGLISH))).findFirst() .orElse(BlockFace.SELF); } + /** + * Gets NMS data from a block using the appropriate handler. + * + * @param block The block. + * @return The NMS data string. + */ private static String nmsData(Block block) { AbstractMetaData handler; try { @@ -632,6 +715,11 @@ private static String nmsData(Block block) { return handler.nmsData(block); } + /** + * Loads the list of structures to be placed from the database. + * + * @return ToBePlacedStructures instance. + */ private ToBePlacedStructures loadToDos() { if (!toPlace.objectExists(TODO)) { return new ToBePlacedStructures(); diff --git a/src/main/java/world/bentobox/boxed/nms/v1_21_6_R0_1_SNAPSHOT/GetMetaData.java b/src/main/java/world/bentobox/boxed/nms/v1_21_6_R0_1_SNAPSHOT/GetMetaData.java new file mode 100644 index 0000000..d7e836e --- /dev/null +++ b/src/main/java/world/bentobox/boxed/nms/v1_21_6_R0_1_SNAPSHOT/GetMetaData.java @@ -0,0 +1,22 @@ +package world.bentobox.boxed.nms.v1_21_6_R0_1_SNAPSHOT; + +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; + +import net.minecraft.core.BlockPosition; +import net.minecraft.world.level.block.entity.TileEntity; +import world.bentobox.boxed.nms.AbstractMetaData; + +public class GetMetaData extends AbstractMetaData { + + @Override + public String nmsData(Block block) { + Location w = block.getLocation(); + CraftWorld cw = (CraftWorld) w.getWorld(); // CraftWorld is NMS one + // for 1.13+ (we have use WorldServer) + TileEntity te = cw.getHandle().c_(new BlockPosition(w.getBlockX(), w.getBlockY(), w.getBlockZ())); + return getData(te, "getUpdatePacket", "tag"); + } + +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/boxed/objects/ToBePlacedStructures.java b/src/main/java/world/bentobox/boxed/objects/ToBePlacedStructures.java index 5d8078c..ef21b0e 100644 --- a/src/main/java/world/bentobox/boxed/objects/ToBePlacedStructures.java +++ b/src/main/java/world/bentobox/boxed/objects/ToBePlacedStructures.java @@ -5,8 +5,10 @@ import java.util.Map; import org.bukkit.Location; +import org.bukkit.block.data.BlockData; import org.bukkit.block.structure.Mirror; import org.bukkit.block.structure.StructureRotation; +import org.bukkit.util.Vector; import com.google.gson.annotations.Expose; @@ -36,7 +38,8 @@ public class ToBePlacedStructures implements DataObject { * @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 StructureRotation rot, @Expose Mirror mirror, @Expose Boolean noMobs, + Map removedBlocks) { } @Expose diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index dd558fe..437a1f5 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -898,6 +898,7 @@ boxed: saved: '&a Placed and saved to structures.yml' failed: '&c Could not be saved to structures.yml. Check console for error' unknown: '&c Unknown parameter: [label]' + undo-success: '&a Undone. Some changes cannot be undone.'' island: go: parameters: '[home number]'