From 597fc082bea5ae82bf9ebf3f48c5c7e8d7487040 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 30 Dec 2018 14:54:37 -0800 Subject: [PATCH 01/10] WIP Added staggered pasting --- .../bentobox/bentobox/schems/Clipboard.java | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/schems/Clipboard.java b/src/main/java/world/bentobox/bentobox/schems/Clipboard.java index d52eaa5c3..6264571cd 100644 --- a/src/main/java/world/bentobox/bentobox/schems/Clipboard.java +++ b/src/main/java/world/bentobox/bentobox/schems/Clipboard.java @@ -10,6 +10,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -42,6 +43,7 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.material.Colorable; +import org.bukkit.scheduler.BukkitTask; import org.bukkit.util.Vector; import world.bentobox.bentobox.BentoBox; @@ -55,6 +57,9 @@ import world.bentobox.bentobox.util.Util; */ public class Clipboard { + // Speed of pasting + private static final int BLOCKS_PER_TICK = Integer.MAX_VALUE; + // Commonly used texts along this class. private static final String ATTACHED = "attached"; private static final String BLOCK = "blocks"; @@ -73,6 +78,8 @@ public class Clipboard { private File schemFolder; + private BukkitTask pastingTask; + public Clipboard(BentoBox plugin, File schemFolder) { super(); this.plugin = plugin; @@ -188,14 +195,26 @@ public class Clipboard { Location loc = island.getCenter().toVector().subtract(off).toLocation(world); // Paste if (blockConfig.contains(BLOCK)) { - blockConfig.getConfigurationSection(BLOCK).getKeys(false).forEach(b -> pasteBlock(world, island, loc, blockConfig.getConfigurationSection(BLOCK + "." + b))); + Iterator it = blockConfig.getConfigurationSection(BLOCK).getKeys(false).iterator(); + pastingTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { + int count = 0; + while (count < BLOCKS_PER_TICK && it.hasNext()) { + pasteBlock(world, island, loc, blockConfig.getConfigurationSection(BLOCK + "." + it.next())); + count++; + } + // If the pasting has been completed, then cancel and run any follow on task + if (!it.hasNext()) { + // Cancel task + pastingTask.cancel(); + if (task != null) { + // Run follow on task if it exists + Bukkit.getScheduler().runTaskLater(plugin, task, 2L); + } + } + }, 0L, 1L); } else { plugin.logError("Clipboard has no block data in it to paste!"); } - // Run follow on task if it exists - if (task != null) { - Bukkit.getScheduler().runTaskLater(plugin, task, 2L); - } } /** @@ -229,7 +248,7 @@ public class Clipboard { plugin.getIWM().getAddon(island.getWorld()).ifPresent(addon -> { lines.clear(); for (int i = 0; i < 4; i++) { - lines.add(ChatColor.translateAlternateColorCodes('&', plugin.getLocalesManager().getOrDefault(User.getInstance(island.getOwner()), + lines.add(ChatColor.translateAlternateColorCodes('&', plugin.getLocalesManager().getOrDefault(User.getInstance(island.getOwner()), addon.getDescription().getName().toLowerCase() + ".sign.line" + i,""))); } }); From c4a5eb2c887a29459e48f84bf2bdfdec1962bf97 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 30 Dec 2018 18:09:56 -0800 Subject: [PATCH 02/10] WIP pastes but attachments fall when split across paste boundaries --- .../world/bentobox/bentobox/Settings.java | 21 +++++ .../commands/island/IslandResetCommand.java | 1 + .../bentobox/managers/IslandsManager.java | 35 ++++----- .../bentobox/managers/island/NewIsland.java | 15 ++-- .../bentobox/bentobox/schems/Clipboard.java | 5 +- src/main/resources/config.yml | 3 + src/main/resources/locales/en-US.yml | 2 +- .../admin/AdminClearresetsallCommandTest.java | 1 + .../bentobox/schems/ClipboardTest.java | 77 +++++++++---------- 9 files changed, 92 insertions(+), 68 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/Settings.java b/src/main/java/world/bentobox/bentobox/Settings.java index ffacb0330..6eaa346da 100644 --- a/src/main/java/world/bentobox/bentobox/Settings.java +++ b/src/main/java/world/bentobox/bentobox/Settings.java @@ -146,6 +146,11 @@ public class Settings implements DataObject { @ConfigEntry(path = "island.name.max-length") private int nameMaxLength = 20; + @ConfigComment("Number of blocks to paste per tick when pasting a schem") + @ConfigComment("Smaller values will help reduce noticeable lag but will make pasting take longer") + @ConfigEntry(path = "island.paste-speed") + private int pasteSpeed = 1000; + // Ranks @ConfigEntry(path = "island.custom-ranks", experimental = true) private Map customRanks = new HashMap<>(); @@ -373,6 +378,20 @@ public class Settings implements DataObject { this.nameMaxLength = nameMaxLength; } + /** + * @param pasteSpeed the pasteSpeed to set + */ + public void setPasteSpeed(int pasteSpeed) { + this.pasteSpeed = pasteSpeed; + } + + /** + * @return paste speed in blocks per tick + */ + public int getPasteSpeed() { + return this.pasteSpeed; + } + public Map getCustomRanks() { return customRanks; } @@ -397,4 +416,6 @@ public class Settings implements DataObject { this.uniqueId = uniqueId; } + + } \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetCommand.java index 0baf1bb6d..cfa819d85 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandResetCommand.java @@ -90,6 +90,7 @@ public class IslandResetCommand extends ConfirmableCommand { // Reset the island Player player = user.getPlayer(); player.setGameMode(GameMode.SPECTATOR); + user.sendMessage("commands.island.create.creating-island"); // Get the player's old island Island oldIsland = getIslands().getIsland(getWorld(), player.getUniqueId()); // Remove them from this island (it still exists and will be deleted later) diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index ff7729d52..96923c628 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -564,6 +564,7 @@ public class IslandsManager { user.sendMessage("commands.island.go.teleported", TextVariables.NUMBER, String.valueOf(number)); } // Exit spectator mode if in it + if (player.getGameMode().equals(GameMode.SPECTATOR)) { player.setGameMode(plugin.getIWM().getDefaultGameMode(world)); } @@ -679,25 +680,23 @@ public class IslandsManager { * @param island to remove players from */ public void removePlayersFromIsland(Island island) { - // Teleport players away - for (Player player : Bukkit.getOnlinePlayers()) { - if (island.inIslandSpace(player.getLocation().getBlockX(), player.getLocation().getBlockZ())) { - // Teleport island players to their island home - if (hasIsland(island.getWorld(), player.getUniqueId()) || plugin.getIslands().inTeam(island.getWorld(), player.getUniqueId())) { - homeTeleport(island.getWorld(), player); + World w = island.getWorld(); + Bukkit.getOnlinePlayers().stream().filter(p -> island.onIsland(p.getLocation())).forEach(p -> { + // Teleport island players to their island home + if (!island.getMemberSet().contains(p.getUniqueId()) && (hasIsland(w, p.getUniqueId()) || inTeam(w, p.getUniqueId()))) { + homeTeleport(w, p); + } else { + // Move player to spawn + if (spawn.containsKey(w)) { + // go to island spawn + p.teleport(spawn.get(w).getSpawnPoint(w.getEnvironment())); } else { - // Move player to spawn - if (spawn.containsKey(island.getWorld())) { - // go to island spawn - player.teleport(spawn.get(island.getWorld()).getSpawnPoint(island.getWorld().getEnvironment())); - } else { - plugin.logWarning("During island deletion player " + player.getName() + " could not be sent home so was placed into spectator mode."); - player.setGameMode(GameMode.SPECTATOR); - player.getPlayer().setFlying(true); - } + plugin.logWarning("During island deletion player " + p.getName() + " could not be sent home so was placed into spectator mode."); + p.setGameMode(GameMode.SPECTATOR); + p.setFlying(true); } } - } + }); } /** @@ -797,13 +796,13 @@ public class IslandsManager { */ public void setOwner(User user, UUID targetUUID, Island island) { islandCache.setOwner(island, targetUUID); - user.sendMessage("commands.island.team.setowner.name-is-the-owner", "[name]", plugin.getPlayers().getName(targetUUID)); + user.sendMessage("commands.island.team.setowner.name-is-the-owner", "[name]", plugin.getPlayers().getName(targetUUID)); plugin.getIWM().getAddon(island.getWorld()).ifPresent(addon -> { User target = User.getInstance(targetUUID); // Tell target. If they are offline, then they may receive a message when they login target.sendMessage("commands.island.team.setowner.you-are-the-owner"); // Permission checks for range changes only work when the target is online - if (target.isOnline()) { + if (target.isOnline()) { // Check if new owner has a different range permission than the island size int range = target.getPermissionValue( addon.getPermissionPrefix() + "island.range", diff --git a/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java b/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java index c8fcb07bc..9c7ab9c86 100644 --- a/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java +++ b/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java @@ -46,11 +46,7 @@ public class NewIsland { this.reason = reason; this.world = world; this.name = name; - newIsland(); - if (oldIsland != null) { - // Delete the old island - plugin.getIslands().deleteIsland(oldIsland, true); - } + newIsland(oldIsland); } /** @@ -121,8 +117,9 @@ public class NewIsland { /** * Makes an island. + * @param oldIsland */ - public void newIsland() { + public void newIsland(Island oldIsland) { Location next = getNextIsland(); if (next == null) { plugin.logError("Failed to make island - no unoccupied spot found"); @@ -165,6 +162,12 @@ public class NewIsland { // Teleport player after this island is built plugin.getIslands().homeTeleport(world, user.getPlayer(), true); + + // Delete old island + if (oldIsland != null) { + // Delete the old island + plugin.getIslands().deleteIsland(oldIsland, true); + } }); // Make nether island if (plugin.getIWM().isNetherGenerate(world) && plugin.getIWM().isNetherIslands(world) && plugin.getIWM().getNetherWorld(world) != null) { diff --git a/src/main/java/world/bentobox/bentobox/schems/Clipboard.java b/src/main/java/world/bentobox/bentobox/schems/Clipboard.java index 6264571cd..a0db08086 100644 --- a/src/main/java/world/bentobox/bentobox/schems/Clipboard.java +++ b/src/main/java/world/bentobox/bentobox/schems/Clipboard.java @@ -58,7 +58,7 @@ import world.bentobox.bentobox.util.Util; public class Clipboard { // Speed of pasting - private static final int BLOCKS_PER_TICK = Integer.MAX_VALUE; + private int pasteSpeed = 200; // Commonly used texts along this class. private static final String ATTACHED = "attached"; @@ -87,6 +87,7 @@ public class Clipboard { schemFolder.mkdirs(); } this.schemFolder = schemFolder; + pasteSpeed = plugin.getSettings().getPasteSpeed(); } /** @@ -198,7 +199,7 @@ public class Clipboard { Iterator it = blockConfig.getConfigurationSection(BLOCK).getKeys(false).iterator(); pastingTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { int count = 0; - while (count < BLOCKS_PER_TICK && it.hasNext()) { + while (count < pasteSpeed && it.hasNext()) { pasteBlock(world, island, loc, blockConfig.getConfigurationSection(BLOCK + "." + it.next())); count++; } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 65179082b..f392ccfba 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -75,6 +75,9 @@ island: # These set the minimum and maximum size of a name. min-length: 4 max-length: 20 + # Number of blocks to paste per tick when pasting a schem + # Smaller values will help reduce noticeable lag but will make pasting take longer + paste-speed: 1000 # /!\ This feature is experimental and might not work as expected or might not work at all. custom-ranks: {} # These settings should not be edited diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 2d4e8866e..b8f5f1909 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -246,7 +246,7 @@ commands: parameters: "" too-many-islands: "&cThere are too many islands in this world: there isn't enough room for yours to be created." unable-create-island: "&cYour island could not be generated, please contact an administrator." - creating-island: "&aCreating your island..." + creating-island: "&aCreating your island, please wait a moment..." pick-world: "&cPick a world from [worlds]." unknown-schem: "&cThat schem has not been loaded yet." info: diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminClearresetsallCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminClearresetsallCommandTest.java index c7e5e9417..c1c24cad1 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminClearresetsallCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/AdminClearresetsallCommandTest.java @@ -108,6 +108,7 @@ public class AdminClearresetsallCommandTest { /** * Test method for . */ + @SuppressWarnings("deprecation") @Test public void testExecuteCheckConfirm() { AdminClearresetsallCommand itl = new AdminClearresetsallCommand(ac); diff --git a/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java b/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java index 056809897..781333f3d 100644 --- a/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java +++ b/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java @@ -47,6 +47,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.Settings; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; @@ -76,7 +77,7 @@ public class ClipboardTest { block = mock(Block.class); when(block.getType()).thenReturn(Material.GRASS); when(block.getLocation()).thenReturn(loc); - + BlockData bd = mock(BlockData.class); when(bd.getAsString()).thenReturn("Block_data"); when(block.getBlockData()).thenReturn(bd); @@ -87,7 +88,7 @@ public class ClipboardTest { when(loc.getBlockZ()).thenReturn(3); when(loc.getBlock()).thenReturn(block); when(loc.toVector()).thenReturn(new Vector(1,2,3)); - + loc2 = mock(Location.class); when(loc2.getWorld()).thenReturn(world); when(loc2.getBlockX()).thenReturn(2); @@ -95,7 +96,7 @@ public class ClipboardTest { when(loc2.getBlockZ()).thenReturn(4); when(loc2.getBlock()).thenReturn(block); // Living entities - + List ents = new ArrayList<>(); Pig pig = mock(Pig.class); Player player = mock(Player.class); @@ -109,7 +110,7 @@ public class ClipboardTest { when(player.getLocation()).thenReturn(loc); when(sheep.getLocation()).thenReturn(loc); when(horse.getLocation()).thenReturn(loc); - + when(pig.getType()).thenReturn(EntityType.PIG); when(player.getType()).thenReturn(EntityType.PLAYER); when(cow.getType()).thenReturn(EntityType.COW); @@ -122,7 +123,7 @@ public class ClipboardTest { HorseInventory inv = mock(HorseInventory.class); when(horse.getInventory()).thenReturn(inv); - + // UUIDs (I'm going to assume these will all be unique (prays to god of randomness) when(creeper.getUniqueId()).thenReturn(UUID.randomUUID()); when(player.getUniqueId()).thenReturn(UUID.randomUUID()); @@ -138,29 +139,34 @@ public class ClipboardTest { ents.add(sheep); ents.add(horse); when(world.getLivingEntities()).thenReturn(ents); - + user = mock(User.class); User.setPlugin(plugin); when(user.getLocation()).thenReturn(loc); - + // Scheduler PowerMockito.mockStatic(Bukkit.class); sched = mock(BukkitScheduler.class); when(Bukkit.getScheduler()).thenReturn(sched); - + + // Settings + Settings settings = mock(Settings.class); + when(settings.getPasteSpeed()).thenReturn(200); + when(plugin.getSettings()).thenReturn(settings); + } @Test public void testClipboard() { when(schemFolder.exists()).thenReturn(false); - new Clipboard(plugin, schemFolder); + new Clipboard(plugin, schemFolder); Mockito.verify(schemFolder).mkdirs(); } @Test public void testSetGetPos1() { - Clipboard cb = new Clipboard(plugin, schemFolder); + Clipboard cb = new Clipboard(plugin, schemFolder); assertNull(cb.getPos1()); cb.setPos1(loc); assertEquals(loc, cb.getPos1()); @@ -169,7 +175,7 @@ public class ClipboardTest { @Test public void testSetGetPos2() { - Clipboard cb = new Clipboard(plugin, schemFolder); + Clipboard cb = new Clipboard(plugin, schemFolder); assertNull(cb.getPos2()); cb.setPos2(loc); assertEquals(loc, cb.getPos2()); @@ -177,7 +183,7 @@ public class ClipboardTest { @Test public void testSetGetOrigin() { - Clipboard cb = new Clipboard(plugin, schemFolder); + Clipboard cb = new Clipboard(plugin, schemFolder); assertNull(cb.getOrigin()); cb.setOrigin(loc); assertEquals(loc, cb.getOrigin()); @@ -185,28 +191,28 @@ public class ClipboardTest { @Test public void testCopyNoPos1Pos2() { - Clipboard cb = new Clipboard(plugin, schemFolder); + Clipboard cb = new Clipboard(plugin, schemFolder); cb.copy(user, false); Mockito.verify(user).sendMessage(Mockito.eq("commands.admin.schem.need-pos1-pos2")); } - + @Test public void testCopyNoPos2() { - Clipboard cb = new Clipboard(plugin, schemFolder); + Clipboard cb = new Clipboard(plugin, schemFolder); cb.setPos1(loc); cb.copy(user, false); Mockito.verify(user).sendMessage(Mockito.eq("commands.admin.schem.need-pos1-pos2")); } - + @Test public void testCopy() { - Clipboard cb = new Clipboard(plugin, schemFolder); + Clipboard cb = new Clipboard(plugin, schemFolder); cb.setPos1(loc); cb.setPos2(loc2); cb.copy(user, false); Mockito.verify(user).sendMessage("commands.admin.schem.copied-blocks", TextVariables.NUMBER, "8"); } - + @Test public void testCopySigns() { when(block.getType()).thenReturn(Material.SIGN); @@ -214,7 +220,7 @@ public class ClipboardTest { String[] lines = {"line1", "line2", "line3", "line4"}; when(bs.getLines()).thenReturn(lines); when(block.getState()).thenReturn(bs); - Clipboard cb = new Clipboard(plugin, schemFolder); + Clipboard cb = new Clipboard(plugin, schemFolder); cb.setPos1(loc); cb.setPos2(loc2); cb.copy(user, false); @@ -232,7 +238,7 @@ public class ClipboardTest { when(inv.getContents()).thenReturn(contents); when(bs.getInventory()).thenReturn(inv); when(block.getState()).thenReturn(bs); - Clipboard cb = new Clipboard(plugin, schemFolder); + Clipboard cb = new Clipboard(plugin, schemFolder); cb.setPos1(loc); cb.setPos2(loc2); cb.copy(user, false); @@ -240,14 +246,14 @@ public class ClipboardTest { // Every block is a sign, so this should be called 8 times Mockito.verify(bs, Mockito.times(8)).getInventory(); } - + @Test public void testCopyCreatureSpawners() { when(block.getType()).thenReturn(Material.SPAWNER); CreatureSpawner bs = mock(CreatureSpawner.class); when(bs.getSpawnedType()).thenReturn(EntityType.CAVE_SPIDER); when(block.getState()).thenReturn(bs); - Clipboard cb = new Clipboard(plugin, schemFolder); + Clipboard cb = new Clipboard(plugin, schemFolder); cb.setPos1(loc); cb.setPos2(loc2); cb.copy(user, false); @@ -255,7 +261,7 @@ public class ClipboardTest { // Every block is a sign, so this should be called 8 times Mockito.verify(bs, Mockito.times(8)).getMaxNearbyEntities(); } - + @Test public void testCopyAir() { // No entities @@ -263,7 +269,7 @@ public class ClipboardTest { when(block.getType()).thenReturn(Material.AIR); BlockState bs = mock(BlockState.class); when(block.getState()).thenReturn(bs); - Clipboard cb = new Clipboard(plugin, schemFolder); + Clipboard cb = new Clipboard(plugin, schemFolder); cb.setPos1(loc); cb.setPos2(loc2); // Do not copy air @@ -273,7 +279,7 @@ public class ClipboardTest { Mockito.verify(user).sendMessage("commands.admin.schem.copied-blocks", TextVariables.NUMBER, "8"); } - + @Test public void testPasteIslandNoData() { Clipboard cb = new Clipboard(plugin, schemFolder); @@ -281,10 +287,10 @@ public class ClipboardTest { when(island.getCenter()).thenReturn(loc); cb.pasteIsland(world, island, () -> {}); Mockito.verify(plugin).logError(Mockito.eq("Clipboard has no block data in it to paste!")); - // Verify the task is run - Mockito.verify(sched).runTaskLater(Mockito.eq(plugin), Mockito.any(Runnable.class), Mockito.eq(2L)); + // Verify the task is run + Mockito.verify(sched, Mockito.never()).runTaskTimer(Mockito.any(), Mockito.any(Runnable.class), Mockito.eq(0L), Mockito.eq(1L)); } - + @Test public void testPasteIslandWithData() { Clipboard cb = new Clipboard(plugin, schemFolder); @@ -294,19 +300,8 @@ public class ClipboardTest { cb.setPos2(loc2); cb.copy(user, false); cb.pasteIsland(world, island, () -> {}); - // This is set just once because the coords of the block are always the same - Mockito.verify(block).setBlockData(Mockito.any()); - // Verify the entities are spawned - Mockito.verify(world).spawnEntity(Mockito.eq(null), Mockito.eq(EntityType.PIG)); - Mockito.verify(world).spawnEntity(Mockito.eq(null), Mockito.eq(EntityType.CREEPER)); - Mockito.verify(world).spawnEntity(Mockito.eq(null), Mockito.eq(EntityType.HORSE)); - Mockito.verify(world).spawnEntity(Mockito.eq(null), Mockito.eq(EntityType.SHEEP)); - Mockito.verify(world).spawnEntity(Mockito.eq(null), Mockito.eq(EntityType.COW)); - // Player should NOT spawn!! - Mockito.verify(world, Mockito.never()).spawnEntity(Mockito.eq(null), Mockito.eq(EntityType.PLAYER)); - // Verify the task is run - Mockito.verify(sched).runTaskLater(Mockito.eq(plugin), Mockito.any(Runnable.class), Mockito.eq(2L)); + Mockito.verify(sched).runTaskTimer(Mockito.any(), Mockito.any(Runnable.class), Mockito.eq(0L), Mockito.eq(1L)); } @Test @@ -315,7 +310,7 @@ public class ClipboardTest { cb.pasteClipboard(loc); Mockito.verify(plugin).logError(Mockito.eq("Clipboard has no block data in it to paste!")); } - + @Test public void testPasteClipboard() { Clipboard cb = new Clipboard(plugin, schemFolder); From 38e82ee617e6aa7f82eae60f67d222bc87e19469 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 30 Dec 2018 20:34:50 -0800 Subject: [PATCH 03/10] WIP: Enables async-style pasting of schems. Speed of schem pasting is in the config.yml of BentoBox. Old schems will work and paste async, but attached blocks, e.g., torches, may fall off due to being pasted ticks before the supporting block is pasted. Newer schems will not have this issue. Further work is needed to optimize entity pasting. Known issue: if a player logs out during the pasting, things go wrong until the next reload. Needs investigation and mitigation. There's no mitigation against the server shutting down or crashing mid-paste. --- .../world/bentobox/bentobox/BentoBox.java | 5 +- .../bentobox/bentobox/schems/Clipboard.java | 73 +++++++++++++------ .../bentobox/schems/ClipboardTest.java | 7 ++ 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java index ec242204b..50a49b9c1 100644 --- a/src/main/java/world/bentobox/bentobox/BentoBox.java +++ b/src/main/java/world/bentobox/bentobox/BentoBox.java @@ -201,10 +201,7 @@ public class BentoBox extends JavaPlugin { if (islandsManager != null) { islandsManager.shutdown(); } - // Save settings - if (settings != null) { - new Config<>(this, Settings.class).saveConfigObject(settings); - } + // Save settings - not required } /** diff --git a/src/main/java/world/bentobox/bentobox/schems/Clipboard.java b/src/main/java/world/bentobox/bentobox/schems/Clipboard.java index a0db08086..609a4fa24 100644 --- a/src/main/java/world/bentobox/bentobox/schems/Clipboard.java +++ b/src/main/java/world/bentobox/bentobox/schems/Clipboard.java @@ -42,6 +42,7 @@ import org.bukkit.entity.Tameable; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; +import org.bukkit.material.Attachable; import org.bukkit.material.Colorable; import org.bukkit.scheduler.BukkitTask; import org.bukkit.util.Vector; @@ -61,7 +62,7 @@ public class Clipboard { private int pasteSpeed = 200; // Commonly used texts along this class. - private static final String ATTACHED = "attached"; + private static final String ATTACHED = "attached."; private static final String BLOCK = "blocks"; private static final String BEDROCK = "bedrock"; private static final String INVENTORY = "inventory"; @@ -197,14 +198,32 @@ public class Clipboard { // Paste if (blockConfig.contains(BLOCK)) { Iterator it = blockConfig.getConfigurationSection(BLOCK).getKeys(false).iterator(); + Iterator attachmentsIt = blockConfig.contains(ATTACHED) ? blockConfig.getConfigurationSection(ATTACHED).getKeys(false).iterator() : null; pastingTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { int count = 0; + boolean done = false; while (count < pasteSpeed && it.hasNext()) { - pasteBlock(world, island, loc, blockConfig.getConfigurationSection(BLOCK + "." + it.next())); + pasteBlock(world, island, loc, blockConfig.getConfigurationSection(BLOCK + "." + it.next()), false); count++; } - // If the pasting has been completed, then cancel and run any follow on task + // If the blocks are done, paste attachments. if (!it.hasNext()) { + // Paste attachments + if (attachmentsIt != null) { + while (count < pasteSpeed && attachmentsIt.hasNext()) { + pasteBlock(world, island, loc, blockConfig.getConfigurationSection(ATTACHED + "." + attachmentsIt.next()), false); + count++; + } + if (!attachmentsIt.hasNext()) { + done = true; + } + } else { + done = true; + } + } + if (done) { + // Paste entities (all at once) + blockConfig.getConfigurationSection(BLOCK).getKeys(false).stream().filter(xyz -> blockConfig.getConfigurationSection(BLOCK + "." + xyz).contains(ENTITY)).forEach(e -> pasteBlock(world, island, loc, blockConfig.getConfigurationSection(BLOCK + "." + e), true)); // Cancel task pastingTask.cancel(); if (task != null) { @@ -224,7 +243,7 @@ public class Clipboard { */ public void pasteClipboard(Location location) { if (blockConfig.contains(BLOCK)) { - blockConfig.getConfigurationSection(BLOCK).getKeys(false).forEach(b -> pasteBlock(location.getWorld(), null, location, blockConfig.getConfigurationSection(BLOCK + "." + b))); + blockConfig.getConfigurationSection(BLOCK).getKeys(false).forEach(b -> pasteBlock(location.getWorld(), null, location, blockConfig.getConfigurationSection(BLOCK + "." + b), true)); } else { plugin.logError("Clipboard has no block data in it to paste!"); } @@ -268,7 +287,7 @@ public class Clipboard { } - private void pasteBlock(World world, Island island, Location location, ConfigurationSection config) { + private void pasteBlock(World world, Island island, Location location, ConfigurationSection config, boolean pasteEntities) { String[] pos = config.getName().split(","); int x = location.getBlockX() + Integer.valueOf(pos[0]); int y = location.getBlockY() + Integer.valueOf(pos[1]); @@ -277,14 +296,10 @@ public class Clipboard { Block block = world.getBlockAt(x, y, z); String blockData = config.getString("bd"); if (blockData != null) { - if (config.getBoolean(ATTACHED)) { - plugin.getServer().getScheduler().runTask(plugin, () -> setBlock(island, block, config, blockData)); - } else { - setBlock(island, block, config, blockData); - } + setBlock(island, block, config, blockData); } - // Entities - if (config.isConfigurationSection(ENTITY)) { + // Entities - only paste here if this is a clipboard paste, otherwise, wait until island is pasted + if (pasteEntities && config.isConfigurationSection(ENTITY)) { setEntity(block.getLocation(), config); } } @@ -305,7 +320,7 @@ public class Clipboard { ConfigurationSection en = config.getConfigurationSection(ENTITY); en.getKeys(false).forEach(k -> { ConfigurationSection ent = en.getConfigurationSection(k); - Location center = location.add(new Vector(0.5, 0.0, 0.5)); + Location center = location.add(new Vector(0.5, 1.0, 0.5)); LivingEntity e = (LivingEntity)location.getWorld().spawnEntity(center, EntityType.valueOf(ent.getString("type", "PIG"))); if (e != null) { e.setCustomName(ent.getString("name")); @@ -433,15 +448,35 @@ public class Clipboard { return true; } + // Block state + BlockState bs = block.getState(); + // Set block data - s.set("bd", block.getBlockData().getAsString()); + if (bs.getData() instanceof Attachable) { + plugin.logDebug("Attachable"); + ConfigurationSection a = blockConfig.createSection(ATTACHED + pos); + a.set("bd", block.getBlockData().getAsString()); + // Placeholder + s.set("bd", "minecraft:stone"); + // Signs + if (bs instanceof Sign) { + Sign sign = (Sign)bs; + a.set("lines", Arrays.asList(sign.getLines())); + } + return true; + } else { + s.set("bd", block.getBlockData().getAsString()); + // Signs + if (bs instanceof Sign) { + Sign sign = (Sign)bs; + s.set("lines", Arrays.asList(sign.getLines())); + } + } if (block.getType().equals(Material.BEDROCK)) { blockConfig.set(BEDROCK, x + "," + y + "," + z); } - // Block state - BlockState bs = block.getState(); // Chests if (bs instanceof InventoryHolder) { InventoryHolder ih = (InventoryHolder)bs; @@ -452,11 +487,7 @@ public class Clipboard { } } } - // Signs - if (bs instanceof Sign) { - Sign sign = (Sign)bs; - s.set("lines", Arrays.asList(sign.getLines())); - } + if (bs instanceof CreatureSpawner) { CreatureSpawner spawner = (CreatureSpawner)bs; s.set("spawnedType",spawner.getSpawnedType().name()); diff --git a/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java b/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java index 781333f3d..6c8a4c83a 100644 --- a/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java +++ b/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java @@ -36,6 +36,7 @@ import org.bukkit.entity.Sheep; import org.bukkit.inventory.HorseInventory; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.bukkit.material.MaterialData; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.util.Vector; import org.junit.Before; @@ -154,6 +155,12 @@ public class ClipboardTest { when(settings.getPasteSpeed()).thenReturn(200); when(plugin.getSettings()).thenReturn(settings); + // Default block state + BlockState bs = mock(BlockState.class); + when(block.getState()).thenReturn(bs); + MaterialData md = mock(MaterialData.class); + when(bs.getData()).thenReturn(md); + } @Test From 2528442d6b454c4fb0510054d8fc139fab69ee82 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 31 Dec 2018 11:24:24 -0800 Subject: [PATCH 04/10] Rewrote using state approach to be easier to understand. Backwards compatible with current schems. New schems required to avoid torch popping etc. Look for updates to AcidIsland and BSB. --- .../bentobox/managers/island/NewIsland.java | 14 +- .../bentobox/bentobox/schems/Clipboard.java | 134 +++++++++++------- .../bentobox/schems/ClipboardTest.java | 8 +- 3 files changed, 95 insertions(+), 61 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java b/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java index 9c7ab9c86..eb6f33c5d 100644 --- a/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java +++ b/src/main/java/world/bentobox/bentobox/managers/island/NewIsland.java @@ -157,12 +157,16 @@ public class NewIsland { plugin.getPlayers().setHomeLocation(user, island.getSpawnPoint(Environment.NORMAL), 1); } // Stop the player from falling or moving if they are - user.getPlayer().setVelocity(new Vector(0,0,0)); - user.getPlayer().setFallDistance(0F); - - // Teleport player after this island is built - plugin.getIslands().homeTeleport(world, user.getPlayer(), true); + if (user.isOnline()) { + user.getPlayer().setVelocity(new Vector(0,0,0)); + user.getPlayer().setFallDistance(0F); + // Teleport player after this island is built + plugin.getIslands().homeTeleport(world, user.getPlayer(), true); + } else { + // Remove the player again to completely clear the data + User.removePlayer(user.getPlayer()); + } // Delete old island if (oldIsland != null) { // Delete the old island diff --git a/src/main/java/world/bentobox/bentobox/schems/Clipboard.java b/src/main/java/world/bentobox/bentobox/schems/Clipboard.java index 609a4fa24..123fbabf9 100644 --- a/src/main/java/world/bentobox/bentobox/schems/Clipboard.java +++ b/src/main/java/world/bentobox/bentobox/schems/Clipboard.java @@ -58,12 +58,23 @@ import world.bentobox.bentobox.util.Util; */ public class Clipboard { + enum PasteState { + BLOCKS, + BLOCKS_DONE, + ATTACHMENTS, + ATTACHMENTS_DONE, + ENTITIES, + DONE + } + // Speed of pasting - private int pasteSpeed = 200; + private int pasteSpeed; + private PasteState pasteState; // Commonly used texts along this class. private static final String ATTACHED = "attached."; - private static final String BLOCK = "blocks"; + private static final String ENTITIES = "entities."; + private static final String BLOCK = "blocks."; private static final String BEDROCK = "bedrock"; private static final String INVENTORY = "inventory"; private static final String ENTITY = "entity"; @@ -89,6 +100,7 @@ public class Clipboard { } this.schemFolder = schemFolder; pasteSpeed = plugin.getSettings().getPasteSpeed(); + pasteState = PasteState.BLOCKS; } /** @@ -196,45 +208,61 @@ public class Clipboard { // Calculate location for pasting Location loc = island.getCenter().toVector().subtract(off).toLocation(world); // Paste - if (blockConfig.contains(BLOCK)) { - Iterator it = blockConfig.getConfigurationSection(BLOCK).getKeys(false).iterator(); - Iterator attachmentsIt = blockConfig.contains(ATTACHED) ? blockConfig.getConfigurationSection(ATTACHED).getKeys(false).iterator() : null; - pastingTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { - int count = 0; - boolean done = false; - while (count < pasteSpeed && it.hasNext()) { - pasteBlock(world, island, loc, blockConfig.getConfigurationSection(BLOCK + "." + it.next()), false); - count++; - } - // If the blocks are done, paste attachments. - if (!it.hasNext()) { - // Paste attachments - if (attachmentsIt != null) { - while (count < pasteSpeed && attachmentsIt.hasNext()) { - pasteBlock(world, island, loc, blockConfig.getConfigurationSection(ATTACHED + "." + attachmentsIt.next()), false); - count++; - } - if (!attachmentsIt.hasNext()) { - done = true; - } - } else { - done = true; - } - } - if (done) { - // Paste entities (all at once) - blockConfig.getConfigurationSection(BLOCK).getKeys(false).stream().filter(xyz -> blockConfig.getConfigurationSection(BLOCK + "." + xyz).contains(ENTITY)).forEach(e -> pasteBlock(world, island, loc, blockConfig.getConfigurationSection(BLOCK + "." + e), true)); - // Cancel task - pastingTask.cancel(); - if (task != null) { - // Run follow on task if it exists - Bukkit.getScheduler().runTaskLater(plugin, task, 2L); - } - } - }, 0L, 1L); - } else { + if (!blockConfig.contains(BLOCK)) { plugin.logError("Clipboard has no block data in it to paste!"); + return; } + // Iterators for the various schem sections + Iterator it = blockConfig.getConfigurationSection(BLOCK).getKeys(false).iterator(); + Iterator it2 = blockConfig.contains(ATTACHED) ? blockConfig.getConfigurationSection(ATTACHED).getKeys(false).iterator() : null; + Iterator it3 = blockConfig.contains(ENTITIES) ? blockConfig.getConfigurationSection(ENTITIES).getKeys(false).iterator() : null; + + // Initial state & speed + pasteState = PasteState.BLOCKS; + pasteSpeed = plugin.getSettings().getPasteSpeed(); + + pastingTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { + int count = 0; + while (pasteState.equals(PasteState.BLOCKS) && count < pasteSpeed && it.hasNext()) { + pasteBlock(world, island, loc, blockConfig.getConfigurationSection(BLOCK + it.next())); + count++; + } + while (it2 != null && pasteState.equals(PasteState.ATTACHMENTS) && count < pasteSpeed && it2.hasNext()) { + pasteBlock(world, island, loc, blockConfig.getConfigurationSection(ATTACHED + it2.next())); + count++; + } + while (it3 != null && pasteState.equals(PasteState.ENTITIES) && count < pasteSpeed && it3.hasNext()) { + pasteEntity(world, island, loc, blockConfig.getConfigurationSection(ENTITIES + it3.next())); + count++; + } + // STATE SHIFT + if (pasteState.equals(PasteState.BLOCKS) && !it.hasNext()) { + // Blocks done. + if (it2 == null && it3 == null) { + // No attachments or entities + pasteState = PasteState.DONE; + } else { + // Next paste attachments, otherwise skip to entities + pasteState = it2 != null ? PasteState.ATTACHMENTS : PasteState.ENTITIES; + } + } + if (pasteState.equals(PasteState.ATTACHMENTS) && !it2.hasNext()) { + // Attachments done. Next paste entities, otherwise done + pasteState = it3 != null ? PasteState.ENTITIES : PasteState.DONE; + } + if (pasteState.equals(PasteState.ENTITIES) && !it3.hasNext()) { + pasteState = PasteState.DONE; + } + if (pasteState.equals(PasteState.DONE)) { + // All done. Cancel task + pastingTask.cancel(); + if (task != null) { + // Run follow on task if it exists + Bukkit.getScheduler().runTaskLater(plugin, task, 2L); + } + } + }, 0L, 1L); + } /** @@ -243,7 +271,7 @@ public class Clipboard { */ public void pasteClipboard(Location location) { if (blockConfig.contains(BLOCK)) { - blockConfig.getConfigurationSection(BLOCK).getKeys(false).forEach(b -> pasteBlock(location.getWorld(), null, location, blockConfig.getConfigurationSection(BLOCK + "." + b), true)); + blockConfig.getConfigurationSection(BLOCK).getKeys(false).forEach(b -> pasteBlock(location.getWorld(), null, location, blockConfig.getConfigurationSection(BLOCK + "." + b))); } else { plugin.logError("Clipboard has no block data in it to paste!"); } @@ -287,7 +315,7 @@ public class Clipboard { } - private void pasteBlock(World world, Island island, Location location, ConfigurationSection config, boolean pasteEntities) { + private void pasteBlock(World world, Island island, Location location, ConfigurationSection config) { String[] pos = config.getName().split(","); int x = location.getBlockX() + Integer.valueOf(pos[0]); int y = location.getBlockY() + Integer.valueOf(pos[1]); @@ -298,12 +326,20 @@ public class Clipboard { if (blockData != null) { setBlock(island, block, config, blockData); } - // Entities - only paste here if this is a clipboard paste, otherwise, wait until island is pasted - if (pasteEntities && config.isConfigurationSection(ENTITY)) { - setEntity(block.getLocation(), config); + // Entities (legacy) + if (config.isConfigurationSection(ENTITY)) { + setEntity(block.getLocation(), config.getConfigurationSection(ENTITY)); } } + private void pasteEntity(World world, Island island, Location location, ConfigurationSection config) { + String[] pos = config.getName().split(","); + int x = location.getBlockX() + Integer.valueOf(pos[0]); + int y = location.getBlockY() + Integer.valueOf(pos[1]); + int z = location.getBlockZ() + Integer.valueOf(pos[2]); + setEntity(new Location(world, x, y, z), config); + } + private void setBlock(Island island, Block block, ConfigurationSection config, String blockData) { // Set the block data block.setBlockData(Bukkit.createBlockData(blockData)); @@ -316,8 +352,7 @@ public class Clipboard { * @param location - locaton * @param config - config section */ - private void setEntity(Location location, ConfigurationSection config) { - ConfigurationSection en = config.getConfigurationSection(ENTITY); + private void setEntity(Location location, ConfigurationSection en) { en.getKeys(false).forEach(k -> { ConfigurationSection ent = en.getConfigurationSection(k); Location center = location.add(new Vector(0.5, 1.0, 0.5)); @@ -408,7 +443,7 @@ public class Clipboard { // Set entities for (LivingEntity e: entities) { - ConfigurationSection en = s.createSection("entity." + e.getUniqueId()); + ConfigurationSection en = blockConfig.createSection(ENTITIES + pos + "." + e.getUniqueId()); en.set("type", e.getType().name()); en.set("name", e.getCustomName()); if (e instanceof Colorable) { @@ -453,11 +488,10 @@ public class Clipboard { // Set block data if (bs.getData() instanceof Attachable) { - plugin.logDebug("Attachable"); ConfigurationSection a = blockConfig.createSection(ATTACHED + pos); a.set("bd", block.getBlockData().getAsString()); - // Placeholder - s.set("bd", "minecraft:stone"); + // Placeholder for attachment + s.set("bd", "minecraft:air"); // Signs if (bs instanceof Sign) { Sign sign = (Sign)bs; diff --git a/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java b/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java index 6c8a4c83a..3066432bd 100644 --- a/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java +++ b/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java @@ -53,6 +53,7 @@ import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; +@SuppressWarnings("deprecation") @RunWith(PowerMockRunner.class) @PrepareForTest({Bukkit.class}) public class ClipboardTest { @@ -327,12 +328,7 @@ public class ClipboardTest { cb.pasteClipboard(loc); // This is set just once because the coords of the block are always the same Mockito.verify(block).setBlockData(Mockito.any()); - // Verify the entities are spawned - Mockito.verify(world).spawnEntity(Mockito.eq(null), Mockito.eq(EntityType.PIG)); - Mockito.verify(world).spawnEntity(Mockito.eq(null), Mockito.eq(EntityType.CREEPER)); - Mockito.verify(world).spawnEntity(Mockito.eq(null), Mockito.eq(EntityType.HORSE)); - Mockito.verify(world).spawnEntity(Mockito.eq(null), Mockito.eq(EntityType.SHEEP)); - Mockito.verify(world).spawnEntity(Mockito.eq(null), Mockito.eq(EntityType.COW)); + // TODO Verify the entities are spawned // Player should NOT spawn!! Mockito.verify(world, Mockito.never()).spawnEntity(Mockito.eq(null), Mockito.eq(EntityType.PLAYER)); } From 0da0d6c6f75f38718ae02d442f45a1268f8c3ea3 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 31 Dec 2018 11:31:35 -0800 Subject: [PATCH 05/10] Removed unneeded enum values. Improved const names. --- .../bentobox/bentobox/schems/Clipboard.java | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/schems/Clipboard.java b/src/main/java/world/bentobox/bentobox/schems/Clipboard.java index 123fbabf9..6b9da0d2c 100644 --- a/src/main/java/world/bentobox/bentobox/schems/Clipboard.java +++ b/src/main/java/world/bentobox/bentobox/schems/Clipboard.java @@ -60,9 +60,7 @@ public class Clipboard { enum PasteState { BLOCKS, - BLOCKS_DONE, ATTACHMENTS, - ATTACHMENTS_DONE, ENTITIES, DONE } @@ -72,9 +70,9 @@ public class Clipboard { private PasteState pasteState; // Commonly used texts along this class. - private static final String ATTACHED = "attached."; - private static final String ENTITIES = "entities."; - private static final String BLOCK = "blocks."; + private static final String ATTACHED_YAML_PREFIX = "attached."; + private static final String ENTITIES_YAML_PREFIX = "entities."; + private static final String BLOCKS_YAML_PREFIX = "blocks."; private static final String BEDROCK = "bedrock"; private static final String INVENTORY = "inventory"; private static final String ENTITY = "entity"; @@ -208,14 +206,14 @@ public class Clipboard { // Calculate location for pasting Location loc = island.getCenter().toVector().subtract(off).toLocation(world); // Paste - if (!blockConfig.contains(BLOCK)) { + if (!blockConfig.contains(BLOCKS_YAML_PREFIX)) { plugin.logError("Clipboard has no block data in it to paste!"); return; } // Iterators for the various schem sections - Iterator it = blockConfig.getConfigurationSection(BLOCK).getKeys(false).iterator(); - Iterator it2 = blockConfig.contains(ATTACHED) ? blockConfig.getConfigurationSection(ATTACHED).getKeys(false).iterator() : null; - Iterator it3 = blockConfig.contains(ENTITIES) ? blockConfig.getConfigurationSection(ENTITIES).getKeys(false).iterator() : null; + Iterator it = blockConfig.getConfigurationSection(BLOCKS_YAML_PREFIX).getKeys(false).iterator(); + Iterator it2 = blockConfig.contains(ATTACHED_YAML_PREFIX) ? blockConfig.getConfigurationSection(ATTACHED_YAML_PREFIX).getKeys(false).iterator() : null; + Iterator it3 = blockConfig.contains(ENTITIES_YAML_PREFIX) ? blockConfig.getConfigurationSection(ENTITIES_YAML_PREFIX).getKeys(false).iterator() : null; // Initial state & speed pasteState = PasteState.BLOCKS; @@ -224,15 +222,15 @@ public class Clipboard { pastingTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { int count = 0; while (pasteState.equals(PasteState.BLOCKS) && count < pasteSpeed && it.hasNext()) { - pasteBlock(world, island, loc, blockConfig.getConfigurationSection(BLOCK + it.next())); + pasteBlock(world, island, loc, blockConfig.getConfigurationSection(BLOCKS_YAML_PREFIX + it.next())); count++; } while (it2 != null && pasteState.equals(PasteState.ATTACHMENTS) && count < pasteSpeed && it2.hasNext()) { - pasteBlock(world, island, loc, blockConfig.getConfigurationSection(ATTACHED + it2.next())); + pasteBlock(world, island, loc, blockConfig.getConfigurationSection(ATTACHED_YAML_PREFIX + it2.next())); count++; } while (it3 != null && pasteState.equals(PasteState.ENTITIES) && count < pasteSpeed && it3.hasNext()) { - pasteEntity(world, island, loc, blockConfig.getConfigurationSection(ENTITIES + it3.next())); + pasteEntity(world, island, loc, blockConfig.getConfigurationSection(ENTITIES_YAML_PREFIX + it3.next())); count++; } // STATE SHIFT @@ -270,8 +268,8 @@ public class Clipboard { * @param location - location */ public void pasteClipboard(Location location) { - if (blockConfig.contains(BLOCK)) { - blockConfig.getConfigurationSection(BLOCK).getKeys(false).forEach(b -> pasteBlock(location.getWorld(), null, location, blockConfig.getConfigurationSection(BLOCK + "." + b))); + if (blockConfig.contains(BLOCKS_YAML_PREFIX)) { + blockConfig.getConfigurationSection(BLOCKS_YAML_PREFIX).getKeys(false).forEach(b -> pasteBlock(location.getWorld(), null, location, blockConfig.getConfigurationSection(BLOCKS_YAML_PREFIX + "." + b))); } else { plugin.logError("Clipboard has no block data in it to paste!"); } @@ -439,11 +437,11 @@ public class Clipboard { String pos = x + "," + y + "," + z; // Position defines the section - ConfigurationSection s = blockConfig.createSection(BLOCK + "." + pos); + ConfigurationSection s = blockConfig.createSection(BLOCKS_YAML_PREFIX + "." + pos); // Set entities for (LivingEntity e: entities) { - ConfigurationSection en = blockConfig.createSection(ENTITIES + pos + "." + e.getUniqueId()); + ConfigurationSection en = blockConfig.createSection(ENTITIES_YAML_PREFIX + pos + "." + e.getUniqueId()); en.set("type", e.getType().name()); en.set("name", e.getCustomName()); if (e instanceof Colorable) { @@ -488,7 +486,7 @@ public class Clipboard { // Set block data if (bs.getData() instanceof Attachable) { - ConfigurationSection a = blockConfig.createSection(ATTACHED + pos); + ConfigurationSection a = blockConfig.createSection(ATTACHED_YAML_PREFIX + pos); a.set("bd", block.getBlockData().getAsString()); // Placeholder for attachment s.set("bd", "minecraft:air"); From 665ad1e0a40646417753437c9444c6dd91004dfb Mon Sep 17 00:00:00 2001 From: tastybento Date: Tue, 1 Jan 2019 14:01:01 -0800 Subject: [PATCH 06/10] Final touches --- .../world/bentobox/bentobox/schems/Clipboard.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/schems/Clipboard.java b/src/main/java/world/bentobox/bentobox/schems/Clipboard.java index 6b9da0d2c..961103f0a 100644 --- a/src/main/java/world/bentobox/bentobox/schems/Clipboard.java +++ b/src/main/java/world/bentobox/bentobox/schems/Clipboard.java @@ -206,6 +206,10 @@ public class Clipboard { // Calculate location for pasting Location loc = island.getCenter().toVector().subtract(off).toLocation(world); // Paste + paste(world, island, loc, task); + } + + private void paste(World world, Island island, Location loc, Runnable task) { if (!blockConfig.contains(BLOCKS_YAML_PREFIX)) { plugin.logError("Clipboard has no block data in it to paste!"); return; @@ -261,6 +265,7 @@ public class Clipboard { } }, 0L, 1L); + } /** @@ -269,7 +274,8 @@ public class Clipboard { */ public void pasteClipboard(Location location) { if (blockConfig.contains(BLOCKS_YAML_PREFIX)) { - blockConfig.getConfigurationSection(BLOCKS_YAML_PREFIX).getKeys(false).forEach(b -> pasteBlock(location.getWorld(), null, location, blockConfig.getConfigurationSection(BLOCKS_YAML_PREFIX + "." + b))); + paste(location.getWorld(), null, location, null); + //blockConfig.getConfigurationSection(BLOCKS_YAML_PREFIX).getKeys(false).forEach(b -> pasteBlock(location.getWorld(), null, location, blockConfig.getConfigurationSection(BLOCKS_YAML_PREFIX + "." + b))); } else { plugin.logError("Clipboard has no block data in it to paste!"); } @@ -353,7 +359,8 @@ public class Clipboard { private void setEntity(Location location, ConfigurationSection en) { en.getKeys(false).forEach(k -> { ConfigurationSection ent = en.getConfigurationSection(k); - Location center = location.add(new Vector(0.5, 1.0, 0.5)); + // Center, and just a bit high + Location center = location.add(new Vector(0.5, 0.5, 0.5)); LivingEntity e = (LivingEntity)location.getWorld().spawnEntity(center, EntityType.valueOf(ent.getString("type", "PIG"))); if (e != null) { e.setCustomName(ent.getString("name")); From f475ebf3d222839f5f50dd4119aeef81c6274ae3 Mon Sep 17 00:00:00 2001 From: tastybento Date: Tue, 1 Jan 2019 14:04:26 -0800 Subject: [PATCH 07/10] Putting the config saving back in. --- src/main/java/world/bentobox/bentobox/BentoBox.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java index ccbd90629..527ea6cd5 100644 --- a/src/main/java/world/bentobox/bentobox/BentoBox.java +++ b/src/main/java/world/bentobox/bentobox/BentoBox.java @@ -214,7 +214,10 @@ public class BentoBox extends JavaPlugin { if (islandsManager != null) { islandsManager.shutdown(); } - // Save settings - not required + // Save settings - ensures admins always have the latest config file + if (settings != null) { + new Config<>(this, Settings.class).saveConfigObject(settings); + } } /** From 9b44dbb1132b72a0483362aab8ea8e9923e236dc Mon Sep 17 00:00:00 2001 From: tastybento Date: Tue, 1 Jan 2019 14:11:41 -0800 Subject: [PATCH 08/10] Fixes tests. I made pasting the clipboard async as well so this affected the test. --- .../java/world/bentobox/bentobox/schems/ClipboardTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java b/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java index 3066432bd..17b905a9b 100644 --- a/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java +++ b/src/test/java/world/bentobox/bentobox/schems/ClipboardTest.java @@ -326,9 +326,8 @@ public class ClipboardTest { cb.setPos2(loc2); cb.copy(user, false); cb.pasteClipboard(loc); - // This is set just once because the coords of the block are always the same - Mockito.verify(block).setBlockData(Mockito.any()); - // TODO Verify the entities are spawned + // Verify the task is run + Mockito.verify(sched).runTaskTimer(Mockito.any(), Mockito.any(Runnable.class), Mockito.eq(0L), Mockito.eq(1L)); // Player should NOT spawn!! Mockito.verify(world, Mockito.never()).spawnEntity(Mockito.eq(null), Mockito.eq(EntityType.PLAYER)); } From 5b6bd72370922f3eedad57cacd8a7f9686114858 Mon Sep 17 00:00:00 2001 From: tastybento Date: Tue, 1 Jan 2019 17:47:25 -0800 Subject: [PATCH 09/10] Added null checking protection. This may help if a database is becoming corrupted. --- .../database/mysql/MySQLDatabaseHandler.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandler.java b/src/main/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandler.java index 27e4a383b..718f15399 100644 --- a/src/main/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandler.java +++ b/src/main/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandler.java @@ -10,10 +10,11 @@ import java.util.ArrayList; import java.util.List; import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.database.json.AbstractJSONDatabaseHandler; import world.bentobox.bentobox.database.DatabaseConnector; +import world.bentobox.bentobox.database.json.AbstractJSONDatabaseHandler; import world.bentobox.bentobox.database.objects.DataObject; /** @@ -72,7 +73,17 @@ public class MySQLDatabaseHandler extends AbstractJSONDatabaseHandler { // Load all the results Gson gson = getGson(); while (resultSet.next()) { - list.add(gson.fromJson(resultSet.getString("json"), dataObject)); + String json = resultSet.getString("json"); + if (json != null) { + try { + T gsonResult = gson.fromJson(json, dataObject); + if (gsonResult != null) { + list.add(gsonResult); + } + } catch (JsonSyntaxException ex) { + plugin.logError("Could not load object " + ex.getMessage()); + } + } } } } catch (SQLException e) { From b8312db655c9f66773921212b4a85c8dcf921c57 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 2 Jan 2019 10:55:11 -0800 Subject: [PATCH 10/10] Fixes double home teleport when resetting island. --- .../java/world/bentobox/bentobox/managers/IslandsManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index 96923c628..c2fa2cda0 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -681,7 +681,9 @@ public class IslandsManager { */ public void removePlayersFromIsland(Island island) { World w = island.getWorld(); - Bukkit.getOnlinePlayers().stream().filter(p -> island.onIsland(p.getLocation())).forEach(p -> { + Bukkit.getOnlinePlayers().stream() + .filter(p -> p.getGameMode().equals(plugin.getIWM().getDefaultGameMode(island.getWorld()))) + .filter(p -> island.onIsland(p.getLocation())).forEach(p -> { // Teleport island players to their island home if (!island.getMemberSet().contains(p.getUniqueId()) && (hasIsland(w, p.getUniqueId()) || inTeam(w, p.getUniqueId()))) { homeTeleport(w, p);