diff --git a/.github/.gitignore b/.github/.gitignore
new file mode 100644
index 0000000..9bb88d3
--- /dev/null
+++ b/.github/.gitignore
@@ -0,0 +1 @@
+/.DS_Store
diff --git a/pom.xml b/pom.xml
index 19d65d7..1e5271e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,8 +58,8 @@
2.0.9
- 1.20.4-R0.1-SNAPSHOT
- 2.0.0-SNAPSHOT
+ 1.21-R0.1-SNAPSHOT
+ 2.4.0-SNAPSHOT
${build.version}-SNAPSHOT
diff --git a/src/main/java/world/bentobox/boxed/Settings.java b/src/main/java/world/bentobox/boxed/Settings.java
index d665c00..7a9da40 100644
--- a/src/main/java/world/bentobox/boxed/Settings.java
+++ b/src/main/java/world/bentobox/boxed/Settings.java
@@ -11,6 +11,7 @@ import org.bukkit.Difficulty;
import org.bukkit.GameMode;
import org.bukkit.entity.EntityType;
+import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.configuration.ConfigComment;
import world.bentobox.bentobox.api.configuration.ConfigEntry;
import world.bentobox.bentobox.api.configuration.StoreAt;
@@ -106,6 +107,7 @@ public class Settings implements WorldSettings {
private int ticksPerMonsterSpawns = -1;
@ConfigComment("Radius of player area. (So distance between player starting spots is twice this)")
+ @ConfigComment("MUST BE A FACTOR OF 16. If not, it will be rounded to be one.")
@ConfigComment("It is the same for every dimension : Overworld, Nether and End.")
@ConfigEntry(path = "world.area-radius", needsReset = true)
private int islandDistance = 320;
@@ -494,6 +496,11 @@ public class Settings implements WorldSettings {
*/
@Override
public int getIslandDistance() {
+ if (islandDistance % 16 != 0) {
+ islandDistance = islandDistance - (islandDistance % 16);
+ BentoBox.getInstance()
+ .logWarning("Boxed: Area radius is not a factor of 16. Rounding to " + islandDistance);
+ }
return islandDistance;
}
diff --git a/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java b/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java
index 579f01c..e58cc91 100644
--- a/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java
+++ b/src/main/java/world/bentobox/boxed/listeners/NewAreaListener.java
@@ -2,8 +2,10 @@ package world.bentobox.boxed.listeners;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@@ -12,6 +14,7 @@ import java.util.Queue;
import java.util.Random;
import org.bukkit.Bukkit;
+import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
@@ -33,6 +36,7 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
+import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.loot.LootTables;
import org.bukkit.structure.Structure;
import org.bukkit.util.BoundingBox;
@@ -93,19 +97,23 @@ public class NewAreaListener implements Listener {
private final File structureFile;
private final Queue itemsToBuild = new LinkedList<>();
private static final Random rand = new Random();
- private boolean pasting;
+ private boolean pasting = true;
private static final Gson gson = new Gson();
Pair min = new Pair<>(0, 0);
Pair max = new Pair<>(0, 0);
// Database handler for structure data
private final Database handler;
private final Map islandStructureCache = new HashMap<>();
+ private Map, List> readyToBuild = new HashMap<>();
+ private static String bukkitVersion = "v" + Bukkit.getBukkitVersion().replace('.', '_').replace('-', '_');
+ private static String pluginPackageName;
/**
* @param addon addon
*/
public NewAreaListener(Boxed addon) {
this.addon = addon;
+ pluginPackageName = addon.getClass().getPackage().getName();
// Save the default structures file from the jar
addon.saveResource("structures.yml", false);
// Load the config
@@ -113,11 +121,11 @@ public class NewAreaListener implements Listener {
// Get database ready
handler = new Database<>(addon, IslandStructures.class);
// Try to build something every second
- runStructurePrinter(addon);
+ runStructurePrinter();
}
- private void runStructurePrinter(Boxed addon2) {
- Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), this::buildStructure, 20, 20);
+ private void runStructurePrinter() {
+ Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), this::buildStructure, 100, 60);
for (String js : JAR_STRUCTURES) {
addon.saveResource("structures/" + js + ".nbt", false);
File structureFile = new File(addon.getDataFolder(), "structures/" + js + ".nbt");
@@ -146,6 +154,29 @@ public class NewAreaListener implements Listener {
}
}
+ private void placeStructure(StructureRecord item) {
+ // 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);
+ addon.log(item.name() + " placed at " + item.location().getWorld().getName() + " "
+ + Util.xyz(item.location().toVector()));
+ // Remove any jigsaw artifacts
+ BoundingBox bb = removeJigsaw(item);
+ // Store it for future reference
+ addon.getIslands().getIslandAt(item.location()).map(Island::getUniqueId).ifPresent(id -> {
+ //.log("Saved " + item.name());
+ if (item.location().getWorld().getEnvironment().equals(Environment.NETHER)) {
+ getIslandStructData(id).addNetherStructure(bb, item.name());
+ } else {
+ getIslandStructData(id).addStructure(bb, item.name());
+ }
+ handler.saveObjectAsync(getIslandStructData(id));
+ });
+ // Clear the semaphore
+ pasting = false;
+ }
+
/**
* 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
@@ -155,22 +186,49 @@ public class NewAreaListener implements Listener {
*/
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBentoBoxReady(BentoBoxReadyEvent event) {
- addon.saveResource("templates.yml", false);
- File templateFile = new File(addon.getDataFolder(), "templates.yml");
- if (templateFile.exists()) {
- YamlConfiguration loader = YamlConfiguration.loadConfiguration(templateFile);
- List list = loader.getStringList("templates");
- for (String struct : list) {
- if (!struct.endsWith("/")) {
- Bukkit.getStructureManager().loadStructure(NamespacedKey.fromString(struct));
+ Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> {
+ addon.saveResource("templates.yml", false);
+ File templateFile = new File(addon.getDataFolder(), "templates.yml");
+ if (templateFile.exists()) {
+ YamlConfiguration loader = YamlConfiguration.loadConfiguration(templateFile);
+ List list = loader.getStringList("templates");
+ for (String struct : list) {
+ if (!struct.endsWith("/")) {
+ Bukkit.getStructureManager().loadStructure(NamespacedKey.fromString(struct));
+ }
}
}
- }
-
+ pasting = false; // Allow pasting
+ });
}
/**
- * Track if a place has entered a structure.
+ * Add items to the queue when they are needed due to chunk loading
+ * @param e ChunkLoadEvent
+ */
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onChunkLoad(ChunkLoadEvent e) {
+ Chunk chunk = e.getChunk();
+ // Check if this island is in this game
+ if (!(addon.inWorld(chunk.getWorld()))) {
+ return;
+ }
+ Pair chunkCoords = new Pair(chunk.getX(), chunk.getZ());
+ if (this.readyToBuild.containsKey(chunkCoords)) {
+ Iterator it = this.readyToBuild.get(chunkCoords).iterator();
+ while (it.hasNext()) {
+ StructureRecord item = it.next();
+ if (item.location().getWorld().equals(e.getWorld())) {
+ this.itemsToBuild.add(item);
+ it.remove();
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Track if a player has entered a structure.
*
* @param e PlayerMoveEvent
*/
@@ -312,36 +370,14 @@ public class NewAreaListener implements Listener {
int y = Integer.parseInt(value[1].strip());
int z = Integer.parseInt(value[2].strip()) + center.getBlockZ();
Location l = new Location(world, x, y, z);
- itemsToBuild.add(new StructureRecord(name, s, l, rot, mirror, noMobs));
+ readyToBuild.computeIfAbsent(new Pair(x >> 4, z >> 4), k -> new ArrayList<>())
+ .add(new StructureRecord(name, s, l, rot, mirror, noMobs));
} else {
addon.logError("Structure file syntax error: " + vector + ": " + Arrays.toString(value));
}
}
}
- private void placeStructure(StructureRecord item) {
- // Set the semaphore - only paste one at a time
- pasting = true;
- // Place the structure
- item.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
- BoundingBox bb = removeJigsaw(item);
- // Store it for future reference
- addon.getIslands().getIslandAt(item.location()).map(Island::getUniqueId).ifPresent(id -> {
- addon.log("Saved " + item.name());
- if (item.location().getWorld().getEnvironment().equals(Environment.NETHER)) {
- getIslandStructData(id).addNetherStructure(bb, item.name());
- } else {
- getIslandStructData(id).addStructure(bb, item.name());
- }
- handler.saveObjectAsync(getIslandStructData(id));
- });
- // Clear the semaphore
- pasting = false;
- }
-
/**
* Removes Jigsaw blocks from a placed structure. Fills underwater ruins with
* water.
@@ -403,6 +439,9 @@ public class NewAreaListener implements Listener {
private static void processStructureBlock(Block b) {
// I would like to read the data from the block and do something with it!
String data = nmsData(b);
+ if (data.isEmpty()) {
+ return;
+ }
BoxedStructureBlock bsb = gson.fromJson(data, BoxedStructureBlock.class);
b.setType(Material.STRUCTURE_VOID);
Enums.getIfPresent(EntityType.class, bsb.getMetadata().toUpperCase(Locale.ENGLISH)).toJavaUtil()
@@ -425,6 +464,9 @@ public class NewAreaListener implements Listener {
private static void processJigsaw(Block b, StructureRotation structureRotation, boolean pasteMobs) {
String data = nmsData(b);
+ if (data.isEmpty()) {
+ return;
+ }
BoxedJigsawBlock bjb = gson.fromJson(data, BoxedJigsawBlock.class);
String finalState = correctDirection(bjb.getFinal_state(), structureRotation);
BlockData bd = Bukkit.createBlockData(finalState);
@@ -542,10 +584,6 @@ public class NewAreaListener implements Listener {
}
private static String nmsData(Block block) {
- // Bukkit method that was added in 2011
- // Example value: 1.20.4-R0.1-SNAPSHOT
- String bukkitVersion = "v" + Bukkit.getBukkitVersion().replace('.', '_').replace('-', '_');
- String pluginPackageName = BentoBox.getInstance().getClass().getPackage().getName();
AbstractMetaData handler;
try {
Class> clazz = Class.forName(pluginPackageName + ".nms." + bukkitVersion + ".GetMetaData");
@@ -555,6 +593,7 @@ public class NewAreaListener implements Listener {
throw new IllegalStateException("Class " + clazz.getName() + " does not implement AbstractGetMetaData");
}
} catch (Exception e) {
+ e.printStackTrace();
BentoBox.getInstance().logWarning("No metadata handler found for " + bukkitVersion + " in Boxed.");
handler = new world.bentobox.boxed.nms.fallback.GetMetaData();
}
diff --git a/src/main/java/world/bentobox/boxed/nms/AbstractMetaData.java b/src/main/java/world/bentobox/boxed/nms/AbstractMetaData.java
index 51196bc..ccab4a5 100644
--- a/src/main/java/world/bentobox/boxed/nms/AbstractMetaData.java
+++ b/src/main/java/world/bentobox/boxed/nms/AbstractMetaData.java
@@ -1,7 +1,14 @@
package world.bentobox.boxed.nms;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
import org.bukkit.block.Block;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.network.protocol.game.PacketPlayOutTileEntityData;
+import net.minecraft.world.level.block.entity.TileEntity;
+
/**
*
*/
@@ -9,4 +16,28 @@ public abstract class AbstractMetaData {
public abstract String nmsData(Block block);
+ protected String getData(TileEntity te, String method, String field) {
+ try {
+ // Check if the method 'j' exists
+ Method updatePacketMethod = te.getClass().getDeclaredMethod(method);
+ if (updatePacketMethod != null) {
+ // Invoke the method to get the PacketPlayOutTileEntityData object
+ updatePacketMethod.setAccessible(true);
+ PacketPlayOutTileEntityData packet = (PacketPlayOutTileEntityData) updatePacketMethod.invoke(te);
+
+ // Access the private field for the NBTTagCompound getter in PacketPlayOutTileEntityData
+ Field fieldC = packet.getClass().getDeclaredField(field);
+ fieldC.setAccessible(true);
+ NBTTagCompound nbtTag = (NBTTagCompound) fieldC.get(packet);
+
+ return nbtTag.toString(); // This will show what you want
+ }
+ } catch (NoSuchMethodException e) {
+ System.out.println("The method '" + method + "' does not exist in the TileEntity class.");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return "";
+
+ }
}
diff --git a/src/main/java/world/bentobox/boxed/nms/fallback/GetMetaData.java b/src/main/java/world/bentobox/boxed/nms/fallback/GetMetaData.java
index 423e6ee..ba63d91 100644
--- a/src/main/java/world/bentobox/boxed/nms/fallback/GetMetaData.java
+++ b/src/main/java/world/bentobox/boxed/nms/fallback/GetMetaData.java
@@ -11,7 +11,7 @@ public class GetMetaData extends AbstractMetaData {
@Override
public String nmsData(Block block) {
- return "Nothing"; // We cannot read it if we have no NMS
+ return ""; // We cannot read it if we have no NMS
}
}
diff --git a/src/main/java/world/bentobox/boxed/nms/v1_20_4_R0_1_SNAPSHOT/GetMetaData.java b/src/main/java/world/bentobox/boxed/nms/v1_20_4_R0_1_SNAPSHOT/GetMetaData.java
index 57ed417..639598a 100644
--- a/src/main/java/world/bentobox/boxed/nms/v1_20_4_R0_1_SNAPSHOT/GetMetaData.java
+++ b/src/main/java/world/bentobox/boxed/nms/v1_20_4_R0_1_SNAPSHOT/GetMetaData.java
@@ -1,14 +1,10 @@
package world.bentobox.boxed.nms.v1_20_4_R0_1_SNAPSHOT;
-import java.lang.reflect.Field;
-
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.craftbukkit.v1_20_R3.CraftWorld;
import net.minecraft.core.BlockPosition;
-import net.minecraft.nbt.NBTTagCompound;
-import net.minecraft.network.protocol.game.PacketPlayOutTileEntityData;
import net.minecraft.world.level.block.entity.TileEntity;
import world.bentobox.boxed.nms.AbstractMetaData;
@@ -18,20 +14,8 @@ public class GetMetaData extends AbstractMetaData {
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()));
- try {
- PacketPlayOutTileEntityData packet = ((PacketPlayOutTileEntityData) te.j()); // get update packet from NMS
- // object
- // here we should use reflection because "c" field isn't accessible
- Field f = packet.getClass().getDeclaredField("c"); // get field
- f.setAccessible(true); // make it available
- NBTTagCompound nbtTag = (NBTTagCompound) f.get(packet);
- return nbtTag.toString(); // this will show what you want
- } catch (Exception exc) {
- exc.printStackTrace();
- }
- return "Nothing";
- }
+ return getData(te, "j", "c");
+ }
}
\ No newline at end of file
diff --git a/src/main/java/world/bentobox/boxed/nms/v1_20_6_R0_1_SNAPSHOT/GetMetaData.java b/src/main/java/world/bentobox/boxed/nms/v1_20_6_R0_1_SNAPSHOT/GetMetaData.java
index be6b438..98c4a99 100644
--- a/src/main/java/world/bentobox/boxed/nms/v1_20_6_R0_1_SNAPSHOT/GetMetaData.java
+++ b/src/main/java/world/bentobox/boxed/nms/v1_20_6_R0_1_SNAPSHOT/GetMetaData.java
@@ -1,14 +1,10 @@
package world.bentobox.boxed.nms.v1_20_6_R0_1_SNAPSHOT;
-import java.lang.reflect.Field;
-
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.craftbukkit.v1_20_R4.CraftWorld;
import net.minecraft.core.BlockPosition;
-import net.minecraft.nbt.NBTTagCompound;
-import net.minecraft.network.protocol.game.PacketPlayOutTileEntityData;
import net.minecraft.world.level.block.entity.TileEntity;
import world.bentobox.boxed.nms.AbstractMetaData;
@@ -18,20 +14,8 @@ public class GetMetaData extends AbstractMetaData {
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()));
- try {
- PacketPlayOutTileEntityData packet = ((PacketPlayOutTileEntityData) te.j()); // get update packet from NMS
- // object
- // here we should use reflection because "c" field isn't accessible
- Field f = packet.getClass().getDeclaredField("c"); // get field
- f.setAccessible(true); // make it available
- NBTTagCompound nbtTag = (NBTTagCompound) f.get(packet);
- return nbtTag.toString(); // this will show what you want
- } catch (Exception exc) {
- exc.printStackTrace();
- }
- return "Nothing";
- }
+ return getData(te, "j", "c");
+ }
}
\ No newline at end of file
diff --git a/src/main/java/world/bentobox/boxed/nms/v1_21_R0_1_SNAPSHOT/GetMetaData.java b/src/main/java/world/bentobox/boxed/nms/v1_21_R0_1_SNAPSHOT/GetMetaData.java
index d0634a0..36be817 100644
--- a/src/main/java/world/bentobox/boxed/nms/v1_21_R0_1_SNAPSHOT/GetMetaData.java
+++ b/src/main/java/world/bentobox/boxed/nms/v1_21_R0_1_SNAPSHOT/GetMetaData.java
@@ -1,14 +1,10 @@
package world.bentobox.boxed.nms.v1_21_R0_1_SNAPSHOT;
-import java.lang.reflect.Field;
-
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;
import net.minecraft.core.BlockPosition;
-import net.minecraft.nbt.NBTTagCompound;
-import net.minecraft.network.protocol.game.PacketPlayOutTileEntityData;
import net.minecraft.world.level.block.entity.TileEntity;
import world.bentobox.boxed.nms.AbstractMetaData;
@@ -20,18 +16,8 @@ public class GetMetaData extends AbstractMetaData {
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()));
- try {
- PacketPlayOutTileEntityData packet = ((PacketPlayOutTileEntityData) te.j()); // get update packet from NMS
- // object
- // here we should use reflection because "c" field isn't accessible
- Field f = packet.getClass().getDeclaredField("c"); // get field
- f.setAccessible(true); // make it available
- NBTTagCompound nbtTag = (NBTTagCompound) f.get(packet);
- return nbtTag.toString(); // this will show what you want
- } catch (Exception exc) {
- exc.printStackTrace();
- }
- return "Nothing";
+
+ return getData(te, "az_", "tag");
}
}
\ No newline at end of file
diff --git a/src/test/java/world/bentobox/boxed/SettingsTest.java b/src/test/java/world/bentobox/boxed/SettingsTest.java
index 8b07ac2..bfc2473 100644
--- a/src/test/java/world/bentobox/boxed/SettingsTest.java
+++ b/src/test/java/world/bentobox/boxed/SettingsTest.java
@@ -3,6 +3,7 @@ package world.bentobox.boxed;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
import java.util.Collections;
import java.util.List;
@@ -13,17 +14,30 @@ import org.bukkit.Difficulty;
import org.bukkit.entity.EntityType;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.reflect.Whitebox;
+
+import world.bentobox.bentobox.BentoBox;
/**
* @author tastybento
*
*/
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ BentoBox.class })
public class SettingsTest {
+ @Mock
+ private BentoBox plugin;
Settings s;
@Before
public void setUp() {
+ // Set up plugin
+ Whitebox.setInternalState(BentoBox.class, "instance", plugin);
s = new Settings();
}
/**
@@ -59,7 +73,8 @@ public class SettingsTest {
@Test
public void testSetIslandDistance() {
s.setIslandDistance(123);
- assertEquals(123, s.getIslandDistance());
+ assertEquals(112, s.getIslandDistance());
+ verify(plugin).logWarning("Boxed: Area radius is not a factor of 16. Rounding to 112");
}
/**