/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.utils.astar; import com.jcwhatever.nucleus.utils.PreCon; import com.jcwhatever.nucleus.utils.astar.basic.AStarNodeContainer; import com.jcwhatever.nucleus.utils.coords.Coords3Di; import com.jcwhatever.nucleus.utils.coords.LocationUtils; import com.jcwhatever.nucleus.utils.materials.Materials; import org.bukkit.Location; import org.bukkit.block.Block; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Set; /** * Gets all locations that can be pathed to from the start point within * the specified range. * * <p>Meant to be used as a means of caching valid mob destination * locations from a fixed path start point to remove the need for using * A-Star pathing in real time for validation purposes.</p> * * <p> Uses a provided AStar implementation for final validation of destinations. </p> * * <p>Not intended for real-time use.</p> */ public class PathAreaFinder { /** * Search for valid path destinations around the specified * path start point. * * @param start The path start location. */ public PathAreaResults search(AStar astar, Location start) { PreCon.notNull(start); start = LocationUtils.getBlockLocation(start); LocationUtils.findSurfaceBelow(start, start); FinderContext context = new FinderContext(astar, start); // Add start node to open nodes context.validNodes.add(start); // Add valid adjacent nodes to open list searchAdjacent(context, start); Iterator<Location> iterator = context.validNodes.iterator(); while (iterator.hasNext()) { Location location = iterator.next(); if (context.search(location).getStatus() != AStarResult.AStarResultStatus.RESOLVED) { iterator.remove(); context.invalidNodes.add(location); } } return new PathAreaResults(context); } /* * Search for valid nodes adjacent to the specified node. */ private void searchAdjacent(FinderContext context, Location node) { LinkedList<StackItem> stack = new LinkedList<>(); stack.push(new StackItem(node)); StackItem curr = stack.peek(); byte dropHeight = (byte)(-context.astar.getMaxDropHeight()); while (!stack.isEmpty()) { start: { for (; curr.y >= dropHeight; curr.y--) { for (; curr.x <= 1; curr.x++) { for (; curr.z <= 1; curr.z++) { if (!curr.columns[curr.x + 1][curr.z + 1]) continue; // get instance of candidate node Location candidate = curr.node.clone().add(curr.x, curr.y, curr.z); // check if candidate is already checked if (context.invalidNodes.contains(candidate)) { continue; } if (context.validNodes.contains(candidate)) { curr.columns[curr.x + 1][curr.z + 1] = false; continue; } int xRange = Math.abs(context.start.getBlockX() - candidate.getBlockX()); int yRange = Math.abs(context.start.getBlockY() - candidate.getBlockY()); int zRange = Math.abs(context.start.getBlockZ() - candidate.getBlockZ()); // check x & z range if ((context.astar.getRange() - xRange < 0) || (context.astar.getRange() - zRange < 0)) { curr.columns[curr.x + 1][curr.z + 1] = false; continue; } // check y range if ((context.astar.getRange() - yRange < 0)) { continue; } // Check for diagonal obstruction if (curr.x != 0 && curr.z != 0 && curr.y >= 0) { Location diagX = curr.node.clone().add(curr.x, curr.y, (short) 0), diagZ = curr.node.clone().add((short) 0, curr.y, curr.z); if (!isValid(diagX) && !isValid(diagZ)) { curr.columns[curr.x + 1][curr.z + 1] = false; continue; } } // check candidate to see if its valid if (!isValid(candidate)) { // invalidate column if material is NOT transparent if (!Materials.isTransparent(candidate.getBlock().getType())) { curr.columns[curr.x + 1][curr.z + 1] = false; } context.invalidNodes.add(candidate); continue; } context.validNodes.add(candidate); //searchAdjacent(context, candidate); curr.z++; stack.push(new StackItem(candidate)); curr = stack.peek(); break start; } curr.z = -1; } curr.x = -1; } stack.pop(); if (stack.isEmpty()) { return; } else { curr = stack.peek(); } } // "start" break location } } /* * Determine if a node is a valid location. */ private boolean isValid(Location loc) { Block block = loc.getBlock(); // check if block is a surface if (!Materials.isSurface(block.getType())) return false; Block above = block.getRelative(0, 1, 0); if (!Materials.isTransparent(above.getType())) return false; Block above1 = block.getRelative(0, 2, 0); return Materials.isTransparent(above1.getType()); } /** * Path area search results. */ public static class PathAreaResults { private final Set<Location> _valid; private final Set<Location> _invalid; /** * Constructor. * * @param context The path search context. */ PathAreaResults (FinderContext context) { _valid = context.validNodes; _invalid = context.invalidNodes; } /** * Get the valid path destinations found. */ public Set<Location> getValid() { return _valid; } /** * Get invalid path destinations found. */ public Set<Location> getInvalid() { return _invalid; } } private static class FinderContext { AStar astar; Location start; Set<Location> invalidNodes; Set<Location> validNodes; FinderContext(AStar astar, Location start) { this.astar = astar; this.start = start; double range = astar.getRange(); this.invalidNodes = new HashSet<Location>((int)(range * range * range)); this.validNodes = new HashSet<Location>((int)(range * range * range)); } AStarResult search(Location location) { return astar.search(Coords3Di.fromLocation(start), Coords3Di.fromLocation(location), new AStarNodeContainer()); } } private static class StackItem { final Location node; byte x = -1; byte y = 1; byte z = -1; // column validations, work from top down, skip columns that are false boolean[][] columns = new boolean[][] { { true, true, true }, { true, false, true }, { true, true, true } }; StackItem(Location node) { this.node = node; } } }