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"); } /**