From 0589b21af395a87495be532c0f48893cf541c01a Mon Sep 17 00:00:00 2001 From: Sefiraat Date: Fri, 24 Nov 2023 21:40:19 +0000 Subject: [PATCH] Deny placement when deletion is queued (#3911) Co-authored-by: Daniel Walsh Co-authored-by: J3fftw <44972470+J3fftw1@users.noreply.github.com> --- .../listeners/BlockListener.java | 24 +++++-- src/main/resources/languages/en/messages.yml | 1 + .../events/TestSlimefunBlockBreakEvent.java | 26 +++++++- .../events/TestSlimefunBlockPlaceEvent.java | 66 ++++++++++++++++--- .../slimefun4/test/TestUtilities.java | 13 ++++ 5 files changed, 113 insertions(+), 17 deletions(-) diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/BlockListener.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/BlockListener.java index f5cd29811..58d066aae 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/BlockListener.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/BlockListener.java @@ -90,20 +90,34 @@ public class BlockListener implements Listener { Slimefun.getProtectionManager().logAction(e.getPlayer(), e.getBlock(), Interaction.PLACE_BLOCK); } if (sfItem != null && !(sfItem instanceof NotPlaceable)) { - if (!sfItem.canUse(e.getPlayer(), true)) { + Player player = e.getPlayer(); + + if (!sfItem.canUse(player, true)) { e.setCancelled(true); } else { - SlimefunBlockPlaceEvent placeEvent = new SlimefunBlockPlaceEvent(e.getPlayer(), item, e.getBlock(), sfItem); + Block block = e.getBlockPlaced(); + + /* + * Resolves an issue when placing a block in a location currently in the deletion queue + * TODO This can be safely removed if/when the deletion no longer has a delay associated with it. + */ + if (Slimefun.getTickerTask().isDeletedSoon(block.getLocation())) { + Slimefun.getLocalization().sendMessage(player, "messages.await-deletion"); + e.setCancelled(true); + return; + } + + SlimefunBlockPlaceEvent placeEvent = new SlimefunBlockPlaceEvent(player, item, block, sfItem); Bukkit.getPluginManager().callEvent(placeEvent); if (placeEvent.isCancelled()) { e.setCancelled(true); } else { - if (Slimefun.getBlockDataService().isTileEntity(e.getBlock().getType())) { - Slimefun.getBlockDataService().setBlockData(e.getBlock(), sfItem.getId()); + if (Slimefun.getBlockDataService().isTileEntity(block.getType())) { + Slimefun.getBlockDataService().setBlockData(block, sfItem.getId()); } - BlockStorage.addBlockInfo(e.getBlock(), "id", sfItem.getId(), true); + BlockStorage.addBlockInfo(block, "id", sfItem.getId(), true); sfItem.callItemHandler(BlockPlaceHandler.class, handler -> handler.onPlayerPlace(e)); } } diff --git a/src/main/resources/languages/en/messages.yml b/src/main/resources/languages/en/messages.yml index f9c32a811..81d5115c3 100644 --- a/src/main/resources/languages/en/messages.yml +++ b/src/main/resources/languages/en/messages.yml @@ -172,6 +172,7 @@ messages: bee-suit-slow-fall: '&eYour Bee Wings will help you to get back to the ground safe and slow' deprecated-item: '&4This item has been deprecated and will be removed from Slimefun soon.' researching-is-disabled: '&cResearching has been disabled on this server. Everything is unlocked by default!' + await-deletion: '&cYou cannot place a Slimefun block so soon after breaking one. Try again shortly.' multi-tool: mode-change: '&b%device% mode changed to: &9%mode%' diff --git a/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockBreakEvent.java b/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockBreakEvent.java index 2f94729d3..9eb922521 100644 --- a/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockBreakEvent.java +++ b/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockBreakEvent.java @@ -58,7 +58,7 @@ class TestSlimefunBlockBreakEvent { Player player = new PlayerMock(server, "SomePlayer"); World world = server.addSimpleWorld("my_world"); - Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, 1, 1, 1)); + Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, TestUtilities.randomInt(), 100, TestUtilities.randomInt())); Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); BlockStorage.addBlockInfo(block, "id", "FOOD_COMPOSTER"); @@ -75,7 +75,7 @@ class TestSlimefunBlockBreakEvent { player.getInventory().setItemInMainHand(itemStack); World world = server.addSimpleWorld("my_world"); - Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, 1, 1, 1)); + Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, TestUtilities.randomInt(), 100, TestUtilities.randomInt())); Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); BlockStorage.addBlockInfo(block, "id", "FOOD_COMPOSTER"); @@ -106,7 +106,7 @@ class TestSlimefunBlockBreakEvent { player.getInventory().setItemInMainHand(itemStack); World world = server.addSimpleWorld("my_world"); - Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, 1, 1, 1)); + Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, TestUtilities.randomInt(), 100, TestUtilities.randomInt())); Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); BlockStorage.addBlockInfo(block, "id", "FOOD_COMPOSTER"); @@ -119,4 +119,24 @@ class TestSlimefunBlockBreakEvent { return true; }); } + + @Test + @DisplayName("Test that breaking a Slimefun block gets queued for deletion") + void testBlockBreaksGetQueuedForDeletion() { + Player player = new PlayerMock(server, "SomePlayer"); + ItemStack itemStack = new ItemStack(Material.IRON_PICKAXE); + player.getInventory().setItemInMainHand(itemStack); + + World world = server.addSimpleWorld("my_world"); + Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, TestUtilities.randomInt(), 100, TestUtilities.randomInt())); + + Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); + BlockStorage.addBlockInfo(block, "id", "FOOD_COMPOSTER"); + + BlockBreakEvent blockBreakEvent = new BlockBreakEvent(block, player); + server.getPluginManager().callEvent(blockBreakEvent); + server.getPluginManager().assertEventFired(SlimefunBlockBreakEvent.class, e -> true); + + Assertions.assertTrue(Slimefun.getTickerTask().isDeletedSoon(block.getLocation())); + } } diff --git a/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockPlaceEvent.java b/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockPlaceEvent.java index b599f13eb..f6d8d2868 100644 --- a/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockPlaceEvent.java +++ b/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockPlaceEvent.java @@ -16,6 +16,7 @@ import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; @@ -61,9 +62,10 @@ public class TestSlimefunBlockPlaceEvent { player.getInventory().setItemInMainHand(itemStack); World world = server.addSimpleWorld("my_world"); - Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, 1, 1, 1)); - Block blockAgainst = new BlockMock(Material.GRASS, new Location(world, 1, 0, 1)); - BlockStorage.clearBlockInfo(block); + int x = TestUtilities.randomInt(); + int z = TestUtilities.randomInt(); + Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, x, 0, z)); + Block blockAgainst = new BlockMock(Material.GRASS, new Location(world, x, 1, z)); Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); @@ -83,9 +85,10 @@ public class TestSlimefunBlockPlaceEvent { player.getInventory().setItemInMainHand(itemStack); World world = server.addSimpleWorld("my_world"); - Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, 1, 1, 1)); - Block blockAgainst = new BlockMock(Material.GRASS, new Location(world, 1, 0, 1)); - BlockStorage.clearBlockInfo(block); + int x = TestUtilities.randomInt(); + int z = TestUtilities.randomInt(); + Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, x, 0, z)); + Block blockAgainst = new BlockMock(Material.GRASS, new Location(world, x, 1, z)); Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); @@ -119,9 +122,10 @@ public class TestSlimefunBlockPlaceEvent { player.getInventory().setItemInMainHand(itemStack); World world = server.addSimpleWorld("my_world"); - Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, 1, 1, 1)); - Block blockAgainst = new BlockMock(Material.GRASS, new Location(world, 1, 0, 1)); - BlockStorage.clearBlockInfo(block); + int x = TestUtilities.randomInt(); + int z = TestUtilities.randomInt(); + Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, x, 0, z)); + Block blockAgainst = new BlockMock(Material.GRASS, new Location(world, x, 1, z)); Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); @@ -136,4 +140,48 @@ public class TestSlimefunBlockPlaceEvent { return true; }); } + + @Test + @DisplayName("Test that you cannot place before a SlimefunBlock is fully cleared") + void testBlockPlacementBeforeFullDeletion() { + Player player = new PlayerMock(server, "SomePlayer"); + ItemStack itemStack = slimefunItem.getItem(); + player.getInventory().setItemInMainHand(itemStack); + + // Place first block + World world = server.addSimpleWorld("my_world"); + int x = TestUtilities.randomInt(); + int z = TestUtilities.randomInt(); + Block firstBlock = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, x, 0, z)); + Block firstBlockAgainst = new BlockMock(Material.GRASS, new Location(world, x, 1, z)); + + Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); + + BlockPlaceEvent firstBlockPlaceEvent = new BlockPlaceEvent( + firstBlock, firstBlock.getState(), firstBlockAgainst, itemStack, player, true, EquipmentSlot.HAND + ); + + server.getPluginManager().callEvent(firstBlockPlaceEvent); + server.getPluginManager().assertEventFired(SlimefunBlockPlaceEvent.class, e -> { + Assertions.assertFalse(e.isCancelled()); + return true; + }); + + // Break block + server.getPluginManager().callEvent(new BlockBreakEvent(firstBlock, player)); + server.getPluginManager().assertEventFired(SlimefunBlockBreakEvent.class, e -> true); + + // Assert that the block is not fully deleted + Assertions.assertTrue(Slimefun.getTickerTask().isDeletedSoon(firstBlock.getLocation())); + + // Place second block in the same location + Block secondBlock = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, x, 0, z)); + Block secondBlockAgainst = new BlockMock(Material.GRASS, new Location(world, x, 1, z)); + + BlockPlaceEvent secondBlockPlaceEvent = new BlockPlaceEvent( + secondBlock, secondBlock.getState(), secondBlockAgainst, itemStack, player, true, EquipmentSlot.HAND + ); + server.getPluginManager().callEvent(secondBlockPlaceEvent); + Assertions.assertTrue(secondBlockPlaceEvent.isCancelled()); + } } diff --git a/src/test/java/io/github/thebusybiscuit/slimefun4/test/TestUtilities.java b/src/test/java/io/github/thebusybiscuit/slimefun4/test/TestUtilities.java index 0f520306a..56f517da2 100644 --- a/src/test/java/io/github/thebusybiscuit/slimefun4/test/TestUtilities.java +++ b/src/test/java/io/github/thebusybiscuit/slimefun4/test/TestUtilities.java @@ -2,6 +2,7 @@ package io.github.thebusybiscuit.slimefun4.test; import static org.mockito.Mockito.when; +import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -30,6 +31,8 @@ import io.github.thebusybiscuit.slimefun4.test.mocks.MockSlimefunItem; public final class TestUtilities { + private static final Random random = new Random(); + private TestUtilities() {} @ParametersAreNonnullByDefault @@ -76,4 +79,14 @@ public final class TestUtilities { latch.await(2, TimeUnit.SECONDS); return ref.get(); } + + @ParametersAreNonnullByDefault + public static @Nonnull int randomInt() { + return random.nextInt(Integer.MAX_VALUE); + } + + @ParametersAreNonnullByDefault + public static @Nonnull int randomInt(int upperBound) { + return random.nextInt(upperBound); + } }