From 7ff435f7f3e7705ef56879d11ac24507eedd1ba3 Mon Sep 17 00:00:00 2001 From: Tau Date: Fri, 16 Dec 2022 00:19:45 +1100 Subject: [PATCH] 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. *