/* * 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.internal.managed.astar; import com.jcwhatever.nucleus.managed.astar.IAStarSettings; import com.jcwhatever.nucleus.managed.astar.IAStarResult; import com.jcwhatever.nucleus.managed.astar.IAStarResult.ResultStatus; import com.jcwhatever.nucleus.managed.astar.area.IPathAreaFinder; import com.jcwhatever.nucleus.managed.astar.area.IPathAreaResult; import com.jcwhatever.nucleus.managed.astar.examiners.AStarWorldExaminer; import com.jcwhatever.nucleus.managed.astar.examiners.IAStarNodeExaminer; import com.jcwhatever.nucleus.managed.astar.nodes.AStarNode; import com.jcwhatever.nucleus.utils.PreCon; import com.jcwhatever.nucleus.utils.coords.Coords3Di; import com.jcwhatever.nucleus.utils.coords.ICoords3Di; import com.jcwhatever.nucleus.utils.coords.LocationUtils; import com.jcwhatever.nucleus.utils.coords.MutableCoords3Di; import com.jcwhatever.nucleus.utils.materials.Materials; import org.bukkit.Location; import org.bukkit.World; 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 AStar for final validation of destinations. </p> * * <p>Not intended for real-time use.</p> */ class PathAreaFinder implements IPathAreaFinder { private static final PathAreaFinder INSTANCE = new PathAreaFinder(); static PathAreaFinder get() { return INSTANCE; } @Override public IPathAreaResult search(Location start, IAStarSettings settings) { return search(settings, new AStarWorldExaminer<AStarNode>(start.getWorld()), start); } /** * Search for valid path destinations around the specified * path start point. * * @param start The path start location. */ PathAreaResults search(IAStarSettings settings, IAStarNodeExaminer<AStarNode> examiner, Location start) { PreCon.notNull(start); start = LocationUtils.getBlockLocation(start); LocationUtils.findSurfaceBelow(start, start); Coords3Di startCoords = Coords3Di.fromLocation(start); FinderContext context = new FinderContext(settings, examiner, startCoords, start.getWorld()); // Add start node to open nodes context.validNodes.add(startCoords); // Add valid adjacent nodes to open list searchAdjacent(context, startCoords); Iterator<ICoords3Di> iterator = context.validNodes.iterator(); while (iterator.hasNext()) { ICoords3Di coord = iterator.next(); if (context.search(coord).getStatus() != ResultStatus.RESOLVED) { iterator.remove(); context.invalidNodes.add(coord); } } return new PathAreaResults(context); } /* * Search for valid nodes adjacent to the specified node. */ private void searchAdjacent(FinderContext context, ICoords3Di node) { LinkedList<StackItem> stack = new LinkedList<>(); stack.push(new StackItem(node)); StackItem curr = stack.peek(); byte dropHeight = (byte)(-context.settings.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 Coords3Di candidate = new Coords3Di(node, 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.getX() - candidate.getX()); int yRange = Math.abs(context.start.getY() - candidate.getY()); int zRange = Math.abs(context.start.getZ() - candidate.getZ()); // check x & z range if ((context.settings.getRange() - xRange < 0) || (context.settings.getRange() - zRange < 0)) { curr.columns[curr.x + 1][curr.z + 1] = false; continue; } // check y range if ((context.settings.getRange() - yRange < 0)) { continue; } // Check for diagonal obstruction if (curr.x != 0 && curr.z != 0 && curr.y >= 0) { Coords3Di diagX = tempNode(node, curr.x, curr.y, (short) 0, context.diagX), diagZ = tempNode(node, (short) 0, curr.y, curr.z, context.diagZ); if (!isValid(diagX, context) && !isValid(diagZ, context)) { curr.columns[curr.x + 1][curr.z + 1] = false; continue; } } // check candidate to see if its valid if (!isValid(candidate, context)) { // invalidate column if material is NOT transparent if (!Materials.isTransparent(candidate.getBlock(context.world).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(Coords3Di loc, FinderContext context) { Block block = loc.getBlock(context.world); // 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()); } private Coords3Di tempNode(ICoords3Di parent, int deltaX, int deltaY, int deltaZ, MutableCoords3Di output) { output.setX(parent.getX() + deltaX); output.setY(parent.getY() + deltaY); output.setZ(parent.getZ() + deltaZ); return output; } /** * Path area search results. */ public static class PathAreaResults implements IPathAreaResult { final World world; final Set<ICoords3Di> valid; final Set<ICoords3Di> invalid; /** * Constructor. * * @param context The path search context. */ PathAreaResults (FinderContext context) { world = context.world; valid = context.validNodes; invalid = context.invalidNodes; } @Override public World getWorld() { return world; } @Override public Set<ICoords3Di> getValid() { return valid; } @Override public Set<ICoords3Di> getInvalid() { return invalid; } } private static class FinderContext { final World world; final IAStarSettings settings; final IAStarNodeExaminer<AStarNode> examiner; final ICoords3Di start; final Set<ICoords3Di> invalidNodes; final Set<ICoords3Di> validNodes; final MutableCoords3Di diagX = new MutableCoords3Di(); final MutableCoords3Di diagZ = new MutableCoords3Di(); FinderContext(IAStarSettings settings, IAStarNodeExaminer<AStarNode> examiner, ICoords3Di start, World world) { this.world = world; this.settings = settings; this.examiner = examiner; this.start = start; double range = settings.getRange(); this.invalidNodes = new HashSet<>((int)(range * range * range)); this.validNodes = new HashSet<>((int)(range * range * range)); } IAStarResult<AStarNode> search(ICoords3Di coords) { AStarNode startNode = new AStarNode(start); AStarNode destNode = new AStarNode(coords); AStarContext<AStarNode> context = new AStarContext<AStarNode>( startNode, destNode, examiner, settings); return AStarCoordsSearch.<AStarNode>get().search(context); } } private static class StackItem { final ICoords3Di 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(ICoords3Di node) { this.node = node; } } }