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]'