From 7ff435f7f3e7705ef56879d11ac24507eedd1ba3 Mon Sep 17 00:00:00 2001 From: Tau Date: Fri, 16 Dec 2022 00:19:45 +1100 Subject: [PATCH 1/2] use non-stack-overflowy method of finding connected blocks if we only need one portal don't bother checking for more. Closes #38 : resolved --- .../prism/listeners/PrismBlockEvents.java | 9 +- .../prism/utils/block/Utilities.java | 132 ++++++++++++------ 2 files changed, 92 insertions(+), 49 deletions(-) diff --git a/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismBlockEvents.java b/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismBlockEvents.java index cf7abf47a..274028c5d 100644 --- a/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismBlockEvents.java +++ b/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismBlockEvents.java @@ -235,11 +235,10 @@ public void onBlockBreak(final BlockBreakEvent event) { logBlockRelationshipsForBlock(player, block); // if obsidian, log portal blocks - if (block.getType().equals(Material.OBSIDIAN)) { - final ArrayList blocks = Utilities.findConnectedBlocksOfType(Material.NETHER_PORTAL, block, null); - if (!blocks.isEmpty()) { - // Only log 1 portal break, we don't need all 8 - RecordingQueue.addToQueue(ActionFactory.createBlock("block-break", blocks.get(0), player)); + if (block.getType() == Material.OBSIDIAN) { + Block portal = Utilities.findFirstSurroundingBlockOfType(block, Material.NETHER_PORTAL, Utilities.CARDINAL_Y_FACES); + if (portal != null) { + RecordingQueue.addToQueue(ActionFactory.createBlock("block-break", portal, player)); } } diff --git a/Prism/src/main/java/network/darkhelmet/prism/utils/block/Utilities.java b/Prism/src/main/java/network/darkhelmet/prism/utils/block/Utilities.java index db3ccd673..307878cc9 100644 --- a/Prism/src/main/java/network/darkhelmet/prism/utils/block/Utilities.java +++ b/Prism/src/main/java/network/darkhelmet/prism/utils/block/Utilities.java @@ -22,10 +22,8 @@ import org.bukkit.entity.EntityType; import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumMap; -import java.util.Locale; +import java.util.*; +import java.util.function.Predicate; public class Utilities { @@ -39,6 +37,9 @@ public class Utilities { */ private static final EnumMap baseMaterials = new EnumMap<>(Material.class); + public static BlockFace[] CARDINAL_FACES = new BlockFace[] {BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST}; + public static BlockFace[] CARDINAL_Y_FACES = new BlockFace[] {BlockFace.UP, BlockFace.DOWN, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST}; + static { baseMaterials.put(Material.GRASS_BLOCK, Material.DIRT); baseMaterials.put(Material.MYCELIUM, Material.DIRT); @@ -279,7 +280,7 @@ public static ArrayList findSideFaceAttachedBlocks(final Block block) { } /** - * Searches around a block for the first block of the given material. + * Searches around a block in all {@link Utilities#CARDINAL_FACES} directions for the first block of the given material. * * @param block the block to search around * @param m the material of the surrounding block to look for @@ -287,21 +288,24 @@ public static ArrayList findSideFaceAttachedBlocks(final Block block) { */ @SuppressWarnings("unused") public static Block findFirstSurroundingBlockOfType(Block block, Material m) { - Block blockToCheck = block.getRelative(BlockFace.EAST); - if (blockToCheck.getType().equals(m)) { - return blockToCheck; - } - blockToCheck = block.getRelative(BlockFace.WEST); - if (blockToCheck.getType().equals(m)) { - return blockToCheck; - } - blockToCheck = block.getRelative(BlockFace.NORTH); - if (blockToCheck.getType().equals(m)) { - return blockToCheck; - } - blockToCheck = block.getRelative(BlockFace.SOUTH); - if (blockToCheck.getType().equals(m)) { - return blockToCheck; + return findFirstSurroundingBlockOfType(block, m, CARDINAL_FACES); + } + + /** + * Searches around a block in all specified directions for the first block of the given material. + * + * @param block the block to search around + * @param m the material of the surrounding block to look for + * @param directions the array of directions to search in. See {@link Utilities#CARDINAL_FACES} and {@link Utilities#CARDINAL_Y_FACES} + * @return the first surrounding block of the given material found + */ + @SuppressWarnings("unused") + public static Block findFirstSurroundingBlockOfType(Block block, Material m, BlockFace[] directions) { + for (BlockFace face : directions) { + Block check = block.getRelative(face); + if (check.getType() == m) { + return check; + } } return null; } @@ -583,40 +587,80 @@ public static boolean materialRequiresSoil(Material m) { * @param currBlock block * @param foundLocations List * @return List + * + * @apiNote Deprecated method. Use {@link #getFuzzyConnectedBlocks(Block, int, Material, int)} */ - public static ArrayList findConnectedBlocksOfType(Material type, Block currBlock, - final ArrayList foundLocations) { + @Deprecated + public static ArrayList findConnectedBlocksOfType(Material type, Block currBlock, final ArrayList foundLocations) { + return new ArrayList(getFuzzyConnectedBlocks(currBlock, 0, type, 8192)); + } - ArrayList foundBlocks = new ArrayList<>(); - ArrayList locations; - if (foundLocations == null) { - locations = new ArrayList<>(); - } else { - locations = foundLocations; - } + /** + * Checks & returns all blocks around block if they match the given predicate
+ * This is done in a "fuzzy" fashion controlled by xFuzz, yFuzz and zFuzz respectively. This allows blocks not directly connected to be added to the results. Set to zero to disable. + * @param block the block to start checking from + * @param fuzz fuzziness of the check + * @param type the type of {@link Block}s to consider connected. Usually {@code block.getType()} + * @param max the maximum number of blocks to compute + * @return A {@link Set} of {@link Block}s that is considered connected by the given {@link Predicate} or an empty {@link Set} if there were no results + * + * @author TauCubed + */ + public static Set getFuzzyConnectedBlocks(Block block, int fuzz, Material type, int max) { + return getFuzzyConnectedBlocks(block, fuzz, fuzz, fuzz, check -> check.getType() == type, max); + } + + /** + * Checks & returns all blocks around block if they match the given predicate
+ * This is done in a "fuzzy" fashion controlled by xFuzz, yFuzz and zFuzz respectively. This allows blocks not directly connected to be added to the results. Set to zero to disable. + * @param block the block to start checking from + * @param fuzz fuzziness of the check + * @param check the predicate used to check if the block should be considered "connected" + * @param max fhe maximum number of blocks to compute + * @return A {@link Set} of {@link Block}s that is considered connected by the given {@link Predicate} or an empty {@link Set} if there were no results + * + * @author TauCubed + */ + public static Set getFuzzyConnectedBlocks(Block block, int fuzz, Predicate check, int max) { + return getFuzzyConnectedBlocks(block, fuzz, fuzz, fuzz, check, max); + } - locations.add(currBlock.getLocation()); - - for (int x = -1; x <= 1; x++) { - for (int z = -1; z <= 1; z++) { - for (int y = -1; y <= 1; y++) { - Block newBlock = currBlock.getRelative(x, y, z); - // ensure it matches the type and wasn't already found - if (newBlock.getType() == type && !locations.contains(newBlock.getLocation())) { - foundBlocks.add(newBlock); - ArrayList additionalBlocks = findConnectedBlocksOfType(type, newBlock, locations); - if (additionalBlocks.size() > 0) { - foundBlocks.addAll(additionalBlocks); + /** + * Checks & returns all blocks around block if they match the given predicate
+ * This is done in a "fuzzy" fashion controlled by xFuzz, yFuzz and zFuzz respectively. This allows blocks not directly connected to be added to the results. Set to zero to disable. + * @param block the block to start checking from + * @param xFuzz X directional fuzziness of the check + * @param yFuzz Y directional fuzziness of the check + * @param zFuzz Z directional fuzziness of the check + * @param check the predicate used to check if the block should be considered "connected" + * @param max the maximum number of blocks to compute + * @return A {@link Set} of {@link Block}s that is considered connected by the given {@link Predicate} or an empty {@link Set} if there were no results + * + * @author TauCubed + */ + public static Set getFuzzyConnectedBlocks(Block block, int xFuzz, int yFuzz, int zFuzz, Predicate check, int max) { + Set results = new HashSet<>(); + LinkedList pending = new LinkedList<>(); + + // add the first block to search from + pending.add(block); + + while ((block = pending.poll()) != null && results.size() < max) { + for (int modX = -xFuzz; modX <= xFuzz; modX++) { + for (int modY = -yFuzz; modY <= yFuzz; modY++) { + for (int modZ = -zFuzz; modZ <= zFuzz; modZ++) { + Block nearby = block.getRelative(modX, modY, modZ); + if (check.test(nearby) && results.add(nearby)) { + pending.add(nearby); } } } } } - - return foundBlocks; - + return results; } + /** * Get Block below of same type. * From 026c101d987070d0f566a90ccb34698093848761 Mon Sep 17 00:00:00 2001 From: Tau Date: Sat, 17 Dec 2022 16:59:43 +1100 Subject: [PATCH 2/2] Fix nether portals being double-logged IMO the attached checks need an overhaul. --- .../prism/listeners/PrismBlockEvents.java | 29 +++++---------- .../prism/utils/block/TabLibraryHelper.java | 5 +++ .../prism/utils/block/Utilities.java | 37 ++++++++++++++++--- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismBlockEvents.java b/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismBlockEvents.java index 274028c5d..e5e0d8cff 100644 --- a/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismBlockEvents.java +++ b/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismBlockEvents.java @@ -161,20 +161,17 @@ private void relatedBlockCallback(Block block, Consumer breakCallback, Co } } - // Find a list of side-face attached blocks that will detach - ArrayList detachedBlocks = Utilities.findSideFaceAttachedBlocks(block); - if (detachedBlocks.size() > 0) { - for (final Block b : detachedBlocks) { - breakCallback.accept(b); - } + // Find all attached blocks that will detach when the block is broken + for (final Block b : Utilities.findSideFaceAttachedBlocks(block)) { + breakCallback.accept(b); } - // Find a list of top-side attached blocks that will detach - detachedBlocks = Utilities.findTopFaceAttachedBlocks(block); - if (detachedBlocks.size() > 0) { - for (final Block b : detachedBlocks) { - breakCallback.accept(b); - } + for (final Block b : Utilities.findTopFaceAttachedBlocks(block)) { + breakCallback.accept(b); + } + + for (final Block b : Utilities.findBottomFaceAttachedBlocks(block)) { + breakCallback.accept(b); } // Find a list of all hanging entities on this block @@ -234,14 +231,6 @@ public void onBlockBreak(final BlockBreakEvent event) { // check for block relationships logBlockRelationshipsForBlock(player, block); - // if obsidian, log portal blocks - if (block.getType() == Material.OBSIDIAN) { - Block portal = Utilities.findFirstSurroundingBlockOfType(block, Material.NETHER_PORTAL, Utilities.CARDINAL_Y_FACES); - if (portal != null) { - RecordingQueue.addToQueue(ActionFactory.createBlock("block-break", portal, player)); - } - } - // Pass to the break alerter if (!player.hasPermission("prism.alerts.use.break.ignore") && !player.hasPermission("prism.alerts.ignore")) { diff --git a/Prism/src/main/java/network/darkhelmet/prism/utils/block/TabLibraryHelper.java b/Prism/src/main/java/network/darkhelmet/prism/utils/block/TabLibraryHelper.java index fe2d06e6d..867dc90ca 100644 --- a/Prism/src/main/java/network/darkhelmet/prism/utils/block/TabLibraryHelper.java +++ b/Prism/src/main/java/network/darkhelmet/prism/utils/block/TabLibraryHelper.java @@ -112,6 +112,11 @@ public class TabLibraryHelper { Tag.FLOWER_POTS) .append(MaterialTag.ALL_PLANTS); + // Material that will detach from the bottom of a block when that block is broken + protected static final MaterialTag fallsOffBottom = new MaterialTag( + Material.NETHER_PORTAL + ); + // Material that will be detached by flowing water/lava protected static final MaterialTag flowBreaks = new MaterialTag( diff --git a/Prism/src/main/java/network/darkhelmet/prism/utils/block/Utilities.java b/Prism/src/main/java/network/darkhelmet/prism/utils/block/Utilities.java index 307878cc9..826b81bd4 100644 --- a/Prism/src/main/java/network/darkhelmet/prism/utils/block/Utilities.java +++ b/Prism/src/main/java/network/darkhelmet/prism/utils/block/Utilities.java @@ -323,7 +323,7 @@ public static boolean isSideFaceDetachableMaterial(Material m) { } /** - * Searches for detachable blocks on the four acceptable sides of a block. + * Searches for detachable blocks on the top of a block. * * @param block Block * @return ArrayList of Blocks @@ -337,10 +337,7 @@ public static ArrayList findTopFaceAttachedBlocks(final Block block) { detachingBlocks.add(blockToCheck); if (blockToCheck.getType().equals(Material.CACTUS) || blockToCheck.getType().equals(Material.SUGAR_CANE)) { // For cactus and sugar cane, we can even have blocks above - ArrayList additionalBlocks = findTopFaceAttachedBlocks(blockToCheck); - if (!additionalBlocks.isEmpty()) { - detachingBlocks.addAll(additionalBlocks); - } + detachingBlocks.addAll(findTopFaceAttachedBlocks(blockToCheck)); } } @@ -348,6 +345,25 @@ public static ArrayList findTopFaceAttachedBlocks(final Block block) { } + /** + * Searches for detachable blocks on the bottom of a block. + * + * @param block Block + * @return ArrayList of Blocks + */ + public static ArrayList findBottomFaceAttachedBlocks(final Block block) { + ArrayList detachingBlocks = new ArrayList<>(); + + // Find any block below this block that will detach + Block blockToCheck = block.getRelative(BlockFace.DOWN); + if (Utilities.isBottomFaceDetachableMaterial(blockToCheck.getType())) { + detachingBlocks.add(blockToCheck); + } + + return detachingBlocks; + + } + /** * Determine whether or not a block is going to detach from the top of a block. * @@ -359,6 +375,17 @@ public static boolean isTopFaceDetachableMaterial(Material m) { return TabLibraryHelper.fallsOffTop.isTagged(m); } + /** + * Determine whether or not a block is going to detach from the top of a block. + * + * @param m the material to check for detaching + * @return boolean - whether a block with a given material will detach from the top of a block. + **/ + @SuppressWarnings("WeakerAccess") + public static boolean isBottomFaceDetachableMaterial(Material m) { + return TabLibraryHelper.fallsOffBottom.isTagged(m); + } + /** * Determine whether or not a block location is filled by a material that means * an attachable material is now detached.