package squidpony.performance.alternate; import squidpony.annotation.Beta; import squidpony.squidai.DijkstraMap.Measurement; import squidpony.squidgrid.Adjacency; import squidpony.squidgrid.Adjacency.BasicAdjacency; import squidpony.squidgrid.Radius; import squidpony.squidmath.CrossHash; import squidpony.squidmath.IntDoubleOrderedMap; import squidpony.squidmath.IntVLA; import squidpony.squidmath.RNG; import java.io.Serializable; /** * An alternative to AStarSearch when you want to fully explore a search space, or when you want a gradient * floodfill, with customizable rules for what is considered adjacent. This can be used for games where * rotation matters (and possibly costs movement), for games with thin walls (where a wall between cells * prevents travel between those two cells even if the wall doesn't occupy a walkable cell), for games where * the edges between cells may have some requisite to travel across, like a vertical amount that must be * hopped up or down between cells, and for games that have portals between distant cells on the same map. * <br> * As a bit of introduction, the article http://www.roguebasin.com/index.php?title=Dijkstra_Maps_Visualized can * provide some useful information on how these work and how to visualize the information they can produce, while * http://www.roguebasin.com/index.php?title=The_Incredible_Power_of_Dijkstra_Maps is an inspiring list of the * various features Dijkstra Maps can enable. * <br> * If you can't remember how to spell this, just remember: Does It Just Know Stuff? That's Really Awesome! * Created by Tommy Ettinger on 4/4/2015. */ @Beta public class OldCustomDijkstraMap implements Serializable { private static final long serialVersionUID = -2456306898212944440L; public Adjacency adjacency; /** * Stores which parts of the map are accessible and which are not. Should not be changed unless the actual physical * terrain has changed. You should call initialize() with a new map instead of changing this directly. */ public double[] physicalMap; /** * The frequently-changing values that are often the point of using this class; goals will have a value of 0, and * any cells that can have a character reach a goal in n steps will have a value of n. Cells that cannot be * entered because they are solid will have a very high value equal to the WALL constant in this class, and cells * that cannot be entered because they cannot reach a goal will have a different very high value equal to the * DARK constant in this class. */ public double[] gradientMap; /** * This stores the entry cost multipliers for each cell; that is, a value of 1.0 is a normal, unmodified cell, but * a value of 0.5 can be entered easily (two cells of its cost can be entered for the cost of one 1.0 cell), and a * value of 2.0 can only be entered with difficulty (one cell of its cost can be entered for the cost of two 1.0 * cells). Unlike the measurement field, this does affect the length of paths, as well as the numbers assigned * to gradientMap during a scan. The values for walls are identical to the value used by gradientMap, that is, this * class' WALL static final field. Floors, however, are never given FLOOR as a value, and default to 1.0 . */ public int[] costMap = null; /** * The neighbors map, as produced by adjacency; can be modified by passing neighbors as the first argument to * {@link Adjacency#portal(int[][][], int, int, boolean)} if you want to create portals between non-adjacent cells. */ public int[][][] neighbors; /** * Height of the map. Exciting stuff. Don't change this, instead call initialize(). */ public int height; /** * Width of the map. Exciting stuff. Don't change this, instead call initialize(). */ public int width; /** * The latest path that was obtained by calling findPath(). It will not contain the value passed as a starting * cell; only steps that require movement will be included, and so if the path has not been found or a valid * path toward a goal is impossible, this ArrayList will be empty. */ public IntVLA path; /** * Goals are always marked with 0. */ public static final double GOAL = 0.0; /** * Floor cells, which include any walkable cell, are marked with a high number equal to 999200.0 . */ public static final double FLOOR = 999200.0; /** * Walls, which are solid no-entry cells, are marked with a high number equal to 999500.0 . */ public static final double WALL = 999500.0; /** * This is used to mark cells that the scan couldn't reach, and these dark cells are marked with a high number * equal to 999800.0 . */ public static final double DARK = 999800.0; /** * Goals that pathfinding will seek out. The Double value should almost always be 0.0 , the same as the static GOAL * constant in this class. */ public IntDoubleOrderedMap goals; private IntDoubleOrderedMap fresh, closed, open; /** * The RNG used to decide which one of multiple equally-short paths to take. */ public RNG rng; private int frustration = 0; private int[] reuse = new int[9]; private boolean initialized = false; private int mappedCount = 0; private double[] heuristics; /** * Construct a OldCustomDijkstraMap without a level to actually scan. If you use this constructor, you must call an * initialize() method before using this class. */ public OldCustomDijkstraMap() { rng = new RNG(); path = new IntVLA(); goals = new IntDoubleOrderedMap(); fresh = new IntDoubleOrderedMap(); closed = new IntDoubleOrderedMap(); open = new IntDoubleOrderedMap(); } /** * Construct a OldCustomDijkstraMap without a level to actually scan. This constructor allows you to specify an RNG before * it is ever used in this class. If you use this constructor, you must call an initialize() method before using * any other methods in the class. */ public OldCustomDijkstraMap(RNG random) { rng = random; path = new IntVLA(); goals = new IntDoubleOrderedMap(); fresh = new IntDoubleOrderedMap(); closed = new IntDoubleOrderedMap(); open = new IntDoubleOrderedMap(); } /** * Used to construct a OldCustomDijkstraMap from the output of another. * * @param level */ public OldCustomDijkstraMap(final double[] level, int width, int height) { this(level, new BasicAdjacency(width, height, Measurement.MANHATTAN)); } /** * Used to construct a OldCustomDijkstraMap from the output of another, specifying a distance calculation. * * @param level * @param adjacency */ public OldCustomDijkstraMap(final double[] level, Adjacency adjacency) { rng = new RNG(); this.adjacency = adjacency; path = new IntVLA(); goals = new IntDoubleOrderedMap(); fresh = new IntDoubleOrderedMap(); closed = new IntDoubleOrderedMap(); open = new IntDoubleOrderedMap(); initialize(level); } /** * Constructor meant to take a char[][] returned by DungeonBoneGen.generate(), or any other * char[][] where '#' means a wall and anything else is a walkable tile. If you only have * a map that uses box-drawing characters, use DungeonUtility.linesToHashes() to get a * map that can be used here. * * @param level */ public OldCustomDijkstraMap(final char[][] level) { this(level, new BasicAdjacency(level.length, level[0].length, Measurement.MANHATTAN), new RNG()); } /** * Constructor meant to take a char[][] returned by DungeonBoneGen.generate(), or any other * char[][] where '#' means a wall and anything else is a walkable tile. If you only have * a map that uses box-drawing characters, use DungeonUtility.linesToHashes() to get a * map that can be used here. Also takes an RNG that ensures predictable path choices given * otherwise identical inputs and circumstances. * * @param level * @param rng The RNG to use for certain decisions; only affects find* methods like findPath, not scan. */ public OldCustomDijkstraMap(final char[][] level, RNG rng) { this(level, new BasicAdjacency(level.length, level[0].length, Measurement.MANHATTAN), rng); } /** * Constructor meant to take a char[][] returned by DungeonBoneGen.generate(), or any other * char[][] where one char means a wall and anything else is a walkable tile. If you only have * a map that uses box-drawing characters, use DungeonUtility.linesToHashes() to get a * map that can be used here. You can specify the character used for walls. * * @param level */ public OldCustomDijkstraMap(final char[][] level, char alternateWall) { rng = new RNG(); path = new IntVLA(); adjacency = new BasicAdjacency(level.length, level[0].length, Measurement.MANHATTAN); goals = new IntDoubleOrderedMap(); fresh = new IntDoubleOrderedMap(); closed = new IntDoubleOrderedMap(); open = new IntDoubleOrderedMap(); initialize(level, alternateWall); } /** * Constructor meant to take a char[][] returned by DungeonBoneGen.generate(), or any other * char[][] where '#' means a wall and anything else is a walkable tile. If you only have * a map that uses box-drawing characters, use DungeonUtility.linesToHashes() to get a * map that can be used here. This constructor specifies a distance measurement. * * @param level * @param adjacency */ public OldCustomDijkstraMap(final char[][] level, Adjacency adjacency) { this(level, adjacency, new RNG()); } /** * Constructor meant to take a char[][] returned by DungeonBoneGen.generate(), or any other * char[][] where '#' means a wall and anything else is a walkable tile. If you only have * a map that uses box-drawing characters, use DungeonUtility.linesToHashes() to get a * map that can be used here. Also takes a distance measurement and an RNG that ensures * predictable path choices given otherwise identical inputs and circumstances. * * @param level * @param rng The RNG to use for certain decisions; only affects find* methods like findPath, not scan. */ public OldCustomDijkstraMap(final char[][] level, Adjacency adjacency, RNG rng) { this.rng = rng; path = new IntVLA(); this.adjacency = adjacency; goals = new IntDoubleOrderedMap(); fresh = new IntDoubleOrderedMap(); closed = new IntDoubleOrderedMap(); open = new IntDoubleOrderedMap(); initialize(level); } /** * Used to initialize or re-initialize a OldCustomDijkstraMap that needs a new PhysicalMap because it either wasn't given * one when it was constructed, or because the contents of the terrain have changed permanently (not if a * creature moved; for that you pass the positions of creatures that block paths to scan() or findPath() ). * * @param level * @return */ public OldCustomDijkstraMap initialize(final double[] level) { width = adjacency.width; height = adjacency.height; int len = level.length; gradientMap = new double[len]; physicalMap = new double[len]; costMap = new int[len]; System.arraycopy(level, 0, gradientMap, 0, len); System.arraycopy(level, 0, physicalMap, 0, len); for (int i = 0; i < len; i++) { costMap[i] = (gradientMap[i] > FLOOR) ? '#' : '.'; } adjacency.costRules.putAndMoveToFirst('#', WALL); neighbors = adjacency.neighborMaps(); heuristics = new double[adjacency.directions.length]; for (int i = 0; i < heuristics.length; i++) { heuristics[i] = adjacency.measurement.heuristic(adjacency.directions[i]); } initialized = true; return this; } /** * Used to initialize or re-initialize a OldCustomDijkstraMap that needs a new PhysicalMap because it either wasn't given * one when it was constructed, or because the contents of the terrain have changed permanently (not if a * creature moved; for that you pass the positions of creatures that block paths to scan() or findPath() ). * * @param level * @return */ public OldCustomDijkstraMap initialize(final char[][] level) { return initialize(level, '#'); } /** * Used to initialize or re-initialize a OldCustomDijkstraMap that needs a new PhysicalMap because it either wasn't given * one when it was constructed, or because the contents of the terrain have changed permanently (not if a * creature moved; for that you pass the positions of creatures that block paths to scan() or findPath() ). This * initialize() method allows you to specify an alternate wall char other than the default character, '#' . * * @param level * @param alternateWall * @return */ public OldCustomDijkstraMap initialize(final char[][] level, char alternateWall) { width = level.length; height = level[0].length; int rot = adjacency.rotations, dex; gradientMap = new double[width*height*rot]; physicalMap = new double[width*height*rot]; costMap = new int[width*height*rot]; IntDoubleOrderedMap cst = adjacency.costRules; cst.putAndMoveToFirst(alternateWall, WALL); int c; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { c = level[x][y]; double t = (c == alternateWall) ? WALL : FLOOR; for (int r = 0; r < rot; r++) { dex = adjacency.composite(x, y, r, 0); gradientMap[dex] = t; physicalMap[dex] = t; costMap[dex] = c; } } } neighbors = adjacency.neighborMaps(); heuristics = new double[adjacency.directions.length]; for (int i = 0; i < heuristics.length; i++) { heuristics[i] = adjacency.measurement.heuristic(adjacency.directions[i]); }initialized = true; return this; } /** * Used to initialize the entry cost modifiers for games that require variable costs to enter squares. This expects * a char[][] of the same exact dimensions as the 2D array that was used to previously initialize() this * OldCustomDijkstraMap, treating the '#' char as a wall (impassable) and anything else as having a normal cost to enter. * The costs can be accessed later by using costMap directly (which will have a valid value when this does not * throw an exception), or by calling setCost(). * * @param level a 2D char array that uses '#' for walls * @return this OldCustomDijkstraMap for chaining. */ public OldCustomDijkstraMap initializeCost(final char[][] level) { if (!initialized) throw new IllegalStateException("OldCustomDijkstraMap must be initialized first!"); int rot = adjacency.rotations; int c; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { c = level[x][y]; for (int r = 0; r < rot; r++) { costMap[adjacency.composite(x, y, r, 0)] = c; } } } return this; } /** * Used to initialize the entry cost modifiers for games that require variable costs to enter squares. This expects * an int[] with length equal to the length of any inner array of neighbors (a field that is given a value during * initialize() by this object's Adjacency value), using the int corresponding to a location as the tile type to * look up for that location, as a key in {@link Adjacency#costRules}, even if an int isn't what this class would * assign normally -- although, walls and other impassable values should be given '#' (which can be put in an int * array) or the value of alternateWall, if this was given one, as a value. The tiles can be accessed later by using * costMap directly (which will have a valid value when this does not throw an exception), or by calling setCost(). * <p/> * This method should be slightly more efficient than the other initializeCost methods. * * @param tiles an int array that already has tile types that {@link #adjacency} can find values for * @return this OldCustomDijkstraMap for chaining. */ public OldCustomDijkstraMap initializeCost(final int[] tiles) { if (!initialized) throw new IllegalStateException("OldCustomDijkstraMap must be initialized first!"); if(tiles.length != gradientMap.length) throw new IllegalArgumentException("costs.length must equal gradientMap.length"); costMap = new int[tiles.length]; System.arraycopy(tiles, 0, costMap, 0, tiles.length); return this; } /** * Gets the appropriate DijkstraMap.Measurement to pass to a constructor if you already have a Radius. * Matches SQUARE or CUBE to CHEBYSHEV, DIAMOND or OCTAHEDRON to MANHATTAN, and CIRCLE or SPHERE to EUCLIDEAN. * * @param radius the Radius to find the corresponding Measurement for * @return a DijkstraMap.Measurement that matches radius; SQUARE to CHEBYSHEV, DIAMOND to MANHATTAN, etc. */ public static Measurement findMeasurement(Radius radius) { if (radius.equals2D(Radius.SQUARE)) return Measurement.CHEBYSHEV; else if (radius.equals2D(Radius.DIAMOND)) return Measurement.MANHATTAN; else return Measurement.EUCLIDEAN; } /** * Gets the appropriate Radius corresponding to a DijkstraMap.Measurement. * Matches CHEBYSHEV to SQUARE, MANHATTAN to DIAMOND, and EUCLIDEAN to CIRCLE. * * @param measurement the Measurement to find the corresponding Radius for * @return a DijkstraMap.Measurement that matches radius; CHEBYSHEV to SQUARE, MANHATTAN to DIAMOND, etc. */ public static Radius findRadius(Measurement measurement) { switch (measurement) { case CHEBYSHEV: return Radius.SQUARE; case EUCLIDEAN: return Radius.CIRCLE; default: return Radius.DIAMOND; } } /** * Resets the gradientMap to its original value from physicalMap. */ public void resetMap() { if (!initialized) return; System.arraycopy(physicalMap, 0, gradientMap, 0, physicalMap.length); } /** * Resets this OldCustomDijkstraMap to a state with no goals, no discovered path, and no changes made to gradientMap * relative to physicalMap. */ public void reset() { resetMap(); goals.clear(); path.clear(); closed.clear(); fresh.clear(); open.clear(); frustration = 0; } /** * Marks a cell as a goal for pathfinding, unless the cell is a wall or unreachable area (then it does nothing). * * @param pt */ public void setGoal(int pt) { if (!initialized || !adjacency.validate(pt)) return; if (physicalMap[pt] > FLOOR) { return; } adjacency.putAllVariants(goals, pt, GOAL); } /** * Marks a cell's type for pathfinding cost as tile (it still will look up the tile in the * {@link Adjacency#costRules} field of {@link #adjacency} when it tries to move through one), unless the cell is a * wall or unreachable area (then it always sets the cost to a value that should have the same cost as a wall). * @param pt * @param tile */ public void setCost(int pt, int tile) { if (!initialized || !adjacency.validate(pt)) return; if (physicalMap[pt] > FLOOR) { costMap[pt] = adjacency.costRules.firstIntKey(); return; } costMap[pt] = tile; } /** * Marks a specific cell in gradientMap as completely impossible to enter. * * @param pt */ public void setOccupied(int pt) { if (!initialized || !adjacency.validate(pt)) return; gradientMap[pt] = WALL; } /** * Reverts a cell to the value stored in the original state of the level as known by physicalMap. * * @param pt */ public void resetCell(int pt) { if (!initialized || !adjacency.validate(pt)) return; gradientMap[pt] = physicalMap[pt]; } /** * Used to remove all goals and undo any changes to gradientMap made by having a goal present. */ public void clearGoals() { if (!initialized) return; IntDoubleOrderedMap.KeyIterator ki = goals.keySet().iterator(); while(ki.hasNext()) resetCell(ki.nextInt()); goals.clear(); } protected void setFresh(final int pt, double counter) { if (!initialized || !adjacency.validate(pt)) return; gradientMap[pt] = counter; fresh.put(pt, counter); } /** * Recalculate the CustomDijkstra map and return it. Cells that were marked as goals with setGoal will have * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, * which will have a value defined by the WALL constant in this class, and areas that the scan was * unable to reach, which will have a value defined by the DARK constant in this class (typically, * these areas should not be used to place NPCs or items and should be filled with walls). This uses the * current measurement. * * @param impassable An array of int keys (encoded by an Adjacency, usually) representing the locations of enemies * or other moving obstacles to a path that cannot be moved through; this can be null (meaning no obstacles). * @return A double array using the dimensions of what this knows about the physical map. */ public double[] scan(int[] impassable) { if(impassable == null) return scanInternal(null, -1); return scanInternal(impassable, impassable.length); } /** * Recalculate the CustomDijkstra map and return it. Cells that were marked as goals with setGoal will have * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, * which will have a value defined by the WALL constant in this class, and areas that the scan was * unable to reach, which will have a value defined by the DARK constant in this class (typically, * these areas should not be used to place NPCs or items and should be filled with walls). This uses the * current measurement. * * @param impassable An array of int keys (encoded by an Adjacency, usually) representing the locations of enemies * or other moving obstacles to a path that cannot be moved through; this can be null (meaning no obstacles). * @param usablePortion how much of impassable to use (if non-null); this should usually be {@code impassable.size} * @return A double array using the dimensions of what this knows about the physical map. */ public double[] scan(IntVLA impassable, int usablePortion) { if(impassable == null) return scanInternal(null, -1); return scanInternal(impassable.items, Math.min(usablePortion, impassable.size)); } protected double[] scanInternal(int[] impassable, int usable) { if (!initialized) return null; Adjacency adjacency = this.adjacency; if (impassable != null) { if(usable > impassable.length) usable = impassable.length; for (int i = 0; i < usable; i++) { adjacency.putAllVariants(closed, impassable[i], WALL); } } int[][] fromNeighbors = neighbors[0]; int near, cen, neighborCount = fromNeighbors.length, mid; for (IntDoubleOrderedMap.MapEntry entry : goals.mapEntrySet()) { closed.remove(entry.getIntKey()); gradientMap[entry.getIntKey()] = entry.getDoubleValue(); } double currentLowest = 999000, cs, csm; IntDoubleOrderedMap lowest = new IntDoubleOrderedMap(); int maxLength = gradientMap.length; for (int l = 0; l < maxLength; l++) { if (gradientMap[l] > FLOOR && !goals.containsKey(l)) closed.put(l, physicalMap[l]); else if (gradientMap[l] < currentLowest) { currentLowest = gradientMap[l]; lowest.clear(); lowest.put(l, currentLowest); } else if (gradientMap[l] == currentLowest) { lowest.put(l, currentLowest); } } int numAssigned = lowest.size(); mappedCount = goals.size(); open.putAll(lowest); lowest = adjacency.costRules; while (numAssigned > 0) { numAssigned = 0; for (IntDoubleOrderedMap.MapEntry cell : open.mapEntrySet()) { cen = cell.getIntKey(); for (int d = 0; d < neighborCount; d++) { near = fromNeighbors[d][cen]; if (!adjacency.validate(near)) // Outside the map continue; if(adjacency.isBlocked(cen, d, neighbors, gradientMap, WALL)) continue; if(adjacency.twoStepRule) { near = fromNeighbors[d][mid = near]; // Outside the map if (!adjacency.validate(near)) continue; if(adjacency.isBlocked(mid, d, neighbors, gradientMap, WALL)) continue; csm = lowest.get(costMap[mid]) * heuristics[d]; cs = lowest.get(costMap[near]) * heuristics[d]; if (!closed.containsKey(near) && !open.containsKey(near) && (gradientMap[mid] = cell.getDoubleValue() + csm) + cs < gradientMap[near]) { setFresh(near, cell.getDoubleValue() + cs + csm); ++numAssigned; ++mappedCount; } } else { cs = lowest.get(costMap[near] | (adjacency.extractR(cen) == adjacency.extractR(near) ? 0 : 0x10000)) * heuristics[d]; //double h = adjacency.measurement.heuristic(adjacency.directions[d]); if (!closed.containsKey(near) && !open.containsKey(near) && gradientMap[cen] + cs < gradientMap[near]) { setFresh(near, cell.getDoubleValue() + cs); ++numAssigned; ++mappedCount; } } } } open.clear(); open.putAll(fresh); fresh.clear(); } closed.clear(); open.clear(); double[] gradientClone = new double[maxLength]; for (int l = 0; l < maxLength; l++) { if (gradientMap[l] == FLOOR) { gradientMap[l] = DARK; } } System.arraycopy(gradientMap, 0, gradientClone, 0, maxLength); return gradientClone; } /** * Recalculate the CustomDijkstra map up to a limit and return it. Cells that were marked as goals with setGoal will have * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further * from goals will have a value equal to the distance from the nearest goal. If a cell would take more steps to * reach than the given limit, it will have a value of DARK if it was passable instead of the distance. The * exceptions are walls, which will have a value defined by the WALL constant in this class, and areas that the scan * was unable to reach, which will have a value defined by the DARK constant in this class. This uses the * current measurement. * * @param limit The maximum number of steps to scan outward from a goal. * @param impassable An array or vararg of int keys representing the locations of enemies or other moving obstacles * to a path that cannot be moved through; this can be null if there are no such obstacles. * @return A double array using the dimensions of what this knows about the physical map. */ public double[] partialScan(int limit, int... impassable) { if(impassable == null) return partialScanInternal(limit, null, -1); return partialScanInternal(limit, impassable, impassable.length); } /** * Recalculate the CustomDijkstra map up to a limit and return it. Cells that were marked as goals with setGoal will have * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further * from goals will have a value equal to the distance from the nearest goal. If a cell would take more steps to * reach than the given limit, it will have a value of DARK if it was passable instead of the distance. The * exceptions are walls, which will have a value defined by the WALL constant in this class, and areas that the scan * was unable to reach, which will have a value defined by the DARK constant in this class. This uses the * current measurement. * * @param limit The maximum number of steps to scan outward from a goal. * @param impassable An IntVLA of int keys representing the locations of enemies or other moving obstacles * to a path that cannot be moved through; this can be null if there are no such obstacles. * @param usablePortion how much of impassable to use (if non-null); this should usually be {@code impassable.size} * @return A double array using the dimensions of what this knows about the physical map. */ public double[] partialScan(int limit, IntVLA impassable, int usablePortion) { if(impassable == null) return partialScanInternal(limit, null, -1); return partialScanInternal(limit, impassable.items, Math.min(usablePortion, impassable.size)); } protected double[] partialScanInternal(int limit, int[] impassable, int usable) { if (!initialized) return null; Adjacency adjacency = this.adjacency; if (impassable != null) { if(usable > impassable.length) usable = impassable.length; for (int i = 0; i < usable; i++) { adjacency.putAllVariants(closed, impassable[i], WALL); } } int[][] fromNeighbors = neighbors[0]; int near, invNear, cen, neighborCount = neighbors.length, mid; for (IntDoubleOrderedMap.MapEntry entry : goals.mapEntrySet()) { closed.remove(entry.getIntKey()); gradientMap[entry.getIntKey()] = entry.getDoubleValue(); } double currentLowest = 999000, cs, csm; IntDoubleOrderedMap lowest = new IntDoubleOrderedMap(); int maxLength = gradientMap.length; for (int l = 0; l < maxLength; l++) { if (gradientMap[l] > FLOOR && !goals.containsKey(l)) closed.put(l, physicalMap[l]); else if (gradientMap[l] < currentLowest) { currentLowest = gradientMap[l]; lowest.clear(); lowest.put(l, currentLowest); } else if (gradientMap[l] == currentLowest) { lowest.put(l, currentLowest); } } int numAssigned = lowest.size(); mappedCount = goals.size(); open.putAll(lowest); lowest = adjacency.costRules; int iter = 0; while (numAssigned > 0 && iter++ < limit) { numAssigned = 0; for (IntDoubleOrderedMap.MapEntry cell : open.mapEntrySet()) { cen = cell.getIntKey(); for (int d = 0; d < neighborCount; d++) { near = fromNeighbors[d][cen]; if (!adjacency.validate(near)) // Outside the map continue; if(adjacency.isBlocked(cen, d, neighbors, gradientMap, WALL)) continue; if(adjacency.twoStepRule) { near = fromNeighbors[d][mid = near]; // Outside the map if (!adjacency.validate(near)) continue; if(adjacency.isBlocked(mid, d, neighbors, gradientMap, WALL)) continue; csm = lowest.get(costMap[mid]) * heuristics[d]; cs = lowest.get(costMap[near]) * heuristics[d]; if (!closed.containsKey(near) && !open.containsKey(near) && (gradientMap[mid] = cell.getDoubleValue() + csm) + cs < gradientMap[near]) { setFresh(near, cell.getDoubleValue() + cs + csm); ++numAssigned; ++mappedCount; } } else { cs = lowest.get(costMap[near] | (adjacency.extractR(cen) == adjacency.extractR(near) ? 0 : 0x10000)) * heuristics[d]; //double h = adjacency.measurement.heuristic(adjacency.directions[d]); if (!closed.containsKey(near) && !open.containsKey(near) && gradientMap[cen] + cs < gradientMap[near]) { setFresh(near, cell.getDoubleValue() + cs); ++numAssigned; ++mappedCount; } } } } open.clear(); open.putAll(fresh); fresh.clear(); } closed.clear(); open.clear(); double[] gradientClone = new double[maxLength]; for (int l = 0; l < maxLength; l++) { if (gradientMap[l] == FLOOR) gradientClone[l] = DARK; else gradientClone[l] = gradientMap[l]; } return gradientClone; } /* * Recalculate the CustomDijkstra map until it reaches a cell index in targets, then returns the first target found. * This uses the current measurement. * * @param start the cell to use as the origin for finding the nearest target * @param targets the cell indices that this is trying to find; it will stop once it finds one * @return the cell index that it found first. * / public int findNearest(int start, int... targets) { if (!initialized) return -1; if (targets == null) return -1; for (int i = 0; i < targets.length; i++) { if(targets[i] == start) return start; } resetMap(); int xShift = width / 8, yShift = height / 8; while (physicalMap[start.x][start.y] >= WALL && frustration < 50) { start2 = Coord.get(Math.min(Math.max(1, start.x + rng.nextInt(1 + xShift * 2) - xShift), width - 2), Math.min(Math.max(1, start.y + rng.nextInt(1 + yShift * 2) - yShift), height - 2)); } if (closed.containsKey(start2.encode())) closed.remove(start2.encode()); gradientMap[start2.x][start2.y] = 0.0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (gradientMap[x][y] > FLOOR && !goals.containsKey(Coord.pureEncode(x, y))) closed.put(Coord.pureEncode(x, y), physicalMap[x][y]); } } int numAssigned = 1; mappedCount = 1; open.put(start2.encode(), 0.0); int near, cen; int enc; while (numAssigned > 0) { numAssigned = 0; for (IntDoubleOrderedMap.MapEntry cell : open.mapEntrySet()) { cen = cell.getIntKey(); for (int d = 0; d < neighbors.length; d++) { near = neighbors[d][cen]; if (!adjacency.validate(near)) // Outside the map continue; dir = adjacency.directions[d]; if(adjacency.isBlocked(cen, d, neighbors, gradientMap, WALL)) continue; double h = adjacency.measurement.heuristic(dir); if (!closed.containsKey(near) && !open.containsKey(near) && gradientMap[cen] + h * costMap[near] < gradientMap[near]) { setFresh(near, cell.getDoubleValue() + h * costMap[near]); ++numAssigned; ++mappedCount; } } } open.clear(); open.putAll(fresh); fresh.clear(); numAssigned = 0; for (IntDoubleOrderedMap.MapEntry cell : open.mapEntrySet()) { cen = Coord.decode(cell.getIntKey()); for (int d = 0; d < dirs.length; d++) { adj = cen.translate(dirs[d].deltaX, dirs[d].deltaY); if (adj.x < 0 || adj.y < 0 || width <= adj.x || height <= adj.y) // Outside the map continue; enc = adj.encode(); double h = heuristic(dirs[d]); if (!closed.containsKey(enc) && !open.containsKey(enc) && gradientMap[cen.x][cen.y] + h * costMap[adj.x][adj.y] < gradientMap[adj.x][adj.y]) { setFresh(adj, cell.getDoubleValue() + h * costMap[adj.x][adj.y]); ++numAssigned; ++mappedCount; if (targets.contains(adj)) { fresh.clear(); closed.clear(); open.clear(); return adj; } } } } open.clear(); open.putAll(fresh); fresh.clear(); } closed.clear(); open.clear(); return null; } */ /* * Recalculate the CustomDijkstra map until it reaches a Coord in targets, then returns the first target found. * This uses the current measurement. * * @param start the cell to use as the origin for finding the nearest target * @param targets the Coords that this is trying to find; it will stop once it finds one * @return the Coord that it found first. * public Coord findNearest(Coord start, Coord... targets) { OrderedSet<Coord> tgts = new OrderedSet<>(targets.length); Collections.addAll(tgts, targets); return findNearest(start, tgts); } */ /* * If you have a target or group of targets you want to pathfind to without scanning the full map, this can be good. * It may find sub-optimal paths in the presence of costs to move into cells. It is useful when you want to move in * a straight line to a known nearby goal. * * @param start your starting location * @param targets an array or vararg of Coords to pathfind to the nearest of * @return an ArrayList of Coord that goes from a cell adjacent to start and goes to one of the targets. Copy of path. * public ArrayList<Coord> findShortcutPath(Coord start, Coord... targets) { if (targets.length == 0) { path.clear(); return new IntVLA(path); } Coord currentPos = findNearest(start, targets); while (true) { if (frustration > 500) { path.clear(); break; } double best = gradientMap[currentPos.x][currentPos.y]; final Direction[] dirs = appendDirToShuffle(rng); int choice = rng.nextInt(dirs.length); for (int d = 0; d < dirs.length; d++) { Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); if (gradientMap[pt.x][pt.y] < best) { if (dirs[choice] == Direction.NONE || !path.contains(pt)) { best = gradientMap[pt.x][pt.y]; choice = d; } } } if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { path.clear(); break; } currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); if (gradientMap[currentPos.x][currentPos.y] == 0) break; path.add(currentPos); frustration++; } frustration = 0; Collections.reverse(path); return new ArrayList<>(path); } */ /* * Recalculate the CustomDijkstra map until it reaches a Coord in targets, then returns the first several targets found, * up to limit or less if the map is fully searched without finding enough. * This uses the current measurement. * * @param start the cell to use as the origin for finding the nearest targets * @param limit the maximum number of targets to find before returning * @param targets the Coords that this is trying to find; it will stop once it finds enough (based on limit) * @return the Coords that it found first. * public ArrayList<Coord> findNearestMultiple(Coord start, int limit, Set<Coord> targets) { if (!initialized) return null; if (targets == null) return null; ArrayList<Coord> found = new ArrayList<>(limit); if (targets.contains(start)) return found; Coord start2 = start, adj, cen; int enc; while (physicalMap[start.x][start.y] >= WALL && frustration < 50) { start2 = Coord.get(Math.min(Math.max(1, start.x + rng.nextInt(15) - 7), width - 2), Math.min(Math.max(1, start.y + rng.nextInt(15) - 7), height - 2)); frustration++; } if (closed.containsKey(start2.encode())) closed.remove(start2.encode()); gradientMap[start2.x][start2.y] = 0.0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (gradientMap[x][y] > FLOOR && !goals.containsKey(Coord.pureEncode(x, y))) closed.put(Coord.pureEncode(x, y), physicalMap[x][y]); } } int numAssigned = 1; mappedCount = 1; open.put(start2.encode(), 0.0); Direction[] dirs = (measurement == Measurement.MANHATTAN) ? Direction.CARDINALS : Direction.OUTWARDS; while (numAssigned > 0) { // ++iter; numAssigned = 0; for (IntDoubleOrderedMap.MapEntry cell : open.mapEntrySet()) { cen = Coord.decode(cell.getIntKey()); for (int d = 0; d < dirs.length; d++) { adj = cen.translate(dirs[d].deltaX, dirs[d].deltaY); if (adj.x < 0 || adj.y < 0 || width <= adj.x || height <= adj.y) // Outside the map continue; enc = adj.encode(); double h = heuristic(dirs[d]); if (!closed.containsKey(enc) && !open.containsKey(enc) && gradientMap[cen.x][cen.y] + h * costMap[adj.x][adj.y] < gradientMap[adj.x][adj.y]) { setFresh(adj, cell.getDoubleValue() + h * costMap[adj.x][adj.y]); ++numAssigned; ++mappedCount; if (targets.contains(adj)) { found.add(adj); if (found.size() >= limit) { fresh.clear(); open.clear(); closed.clear(); return found; } } } } } // closed.putAll(open); open = new IntDoubleOrderedMap(fresh); fresh.clear(); } closed.clear(); open.clear(); return found; } */ /** * Recalculate the CustomDijkstra map for a creature that is potentially larger than 1x1 cell and return it. The value of * a cell in the returned CustomDijkstra map assumes that a creature is square, with a side length equal to the passed * size, that its minimum-x, minimum-y cell is the starting cell, and that any cell with a distance number * represents the distance for the creature's minimum-x, minimum-y cell to reach it. Cells that cannot be entered * by the minimum-x, minimum-y cell because of sizing (such as a floor cell next to a maximum-x and/or maximum-y * wall if size is > 1) will be marked as DARK. Cells that were marked as goals with setGoal will have * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, * which will have a value defined by the WALL constant in this class, and areas that the scan was * unable to reach, which will have a value defined by the DARK constant in this class. (typically, * these areas should not be used to place NPCs or items and should be filled with walls). This uses the * current measurement. * <br> * Portals and wrapping are not currently recommended in conjunction with multi-square creatures, since a * 2x2 creature could easily occupy two cells on the east edge and two cells on the west edge of the map, * and that poses all sorts of issues for creatures trying to pathfind to it, not to mention the more * general issues of how to display a bisected, but mobile, creature. * * @param impassable An array of encoded int keys representing the locations of enemies or other moving obstacles to * a path that cannot be moved through; this can be null if there are no such obstacles. * @param size The length of one side of a square creature using this to find a path, i.e. 2 for a 2x2 cell * creature. Non-square creatures are not supported because turning is really hard. * @return A 2D double[width][height] using the width and height of what this knows about the physical map. */ public double[] scanLarge(int size, int[] impassable) { if(impassable == null) return scanLargeInternal(size, null, -1); return scanLargeInternal(size, impassable, impassable.length); } /** * Recalculate the CustomDijkstra map for a creature that is potentially larger than 1x1 cell and return it. The value of * a cell in the returned CustomDijkstra map assumes that a creature is square, with a side length equal to the passed * size, that its minimum-x, minimum-y cell is the starting cell, and that any cell with a distance number * represents the distance for the creature's minimum-x, minimum-y cell to reach it. Cells that cannot be entered * by the minimum-x, minimum-y cell because of sizing (such as a floor cell next to a maximum-x and/or maximum-y * wall if size is > 1) will be marked as DARK. Cells that were marked as goals with setGoal will have * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, * which will have a value defined by the WALL constant in this class, and areas that the scan was * unable to reach, which will have a value defined by the DARK constant in this class. (typically, * these areas should not be used to place NPCs or items and should be filled with walls). This uses the * current measurement. * <br> * Portals and wrapping are not currently recommended in conjunction with multi-square creatures, since a * 2x2 creature could easily occupy two cells on the east edge and two cells on the west edge of the map, * and that poses all sorts of issues for creatures trying to pathfind to it, not to mention the more * general issues of how to display a bisected, but mobile, creature. * * @param impassable An IntVLA where items are ints representing the locations of enemies or other moving obstacles * to a path that cannot be moved through; this can be null if there are no such obstacles. * @param size The length of one side of a square creature using this to find a path, i.e. 2 for a 2x2 cell * creature. Non-square creatures are not supported because turning is really hard. * @return A 2D double[width][height] using the width and height of what this knows about the physical map. */ public double[] scanLarge(int size, IntVLA impassable, int usablePortion) { if(impassable == null) return scanLargeInternal(size, null, -1); return scanLargeInternal(size, impassable.items, Math.min(usablePortion, impassable.size)); } protected double[] scanLargeInternal(int size, int[] impassable, int usable) { if (!initialized) return null; int[][] fromNeighbors = neighbors[0]; int near, cen, neighborCount = neighbors.length, mid, tmp, tmp2, xStore, yStore, rStore, nStore; double valStore; Adjacency adjacency = this.adjacency; if (impassable != null) { if(usable > impassable.length) usable = impassable.length; for (int i = 0; i < usable; i++) { adjacency.putAllVariants(closed, impassable[i], WALL, -size); } } mappedCount = 0; for (IntDoubleOrderedMap.MapEntry entry : goals.mapEntrySet()) { tmp = entry.getIntKey(); xStore = adjacency.extractX(tmp); yStore = adjacency.extractY(tmp); rStore = adjacency.extractR(tmp); nStore = adjacency.extractN(tmp); valStore = entry.getDoubleValue(); for (int a = 0; a < size; a++) { for (int b = 0; b < size; b++) { tmp2 = adjacency.composite(xStore - a, yStore - b, rStore, nStore); if (tmp2 >= 0) { closed.remove(tmp2); gradientMap[tmp2] = valStore; mappedCount++; } } } } double currentLowest = 999000, cs, csm; IntDoubleOrderedMap lowest = new IntDoubleOrderedMap(); int maxLength = gradientMap.length; for (int l = 0; l < maxLength; l++) { if (gradientMap[l] > FLOOR && !goals.containsKey(l)) { xStore = adjacency.extractX(l); yStore = adjacency.extractY(l); rStore = adjacency.extractR(l); nStore = adjacency.extractN(l); for (int a = 0; a < size; a++) { for (int b = 0; b < size; b++) { tmp2 = adjacency.composite(xStore - a, yStore - b, rStore, nStore); if (tmp2 >= 0) closed.put(tmp2, physicalMap[l]); } } } else if (gradientMap[l] < currentLowest) { currentLowest = gradientMap[l]; lowest.clear(); lowest.put(l, currentLowest); } else if (gradientMap[l] == currentLowest) { lowest.put(l, currentLowest); } } int numAssigned = lowest.size(); open.putAll(lowest); lowest = adjacency.costRules; while (numAssigned > 0) { numAssigned = 0; for (IntDoubleOrderedMap.MapEntry cell : open.mapEntrySet()) { cen = cell.getIntKey(); for (int d = 0; d < neighborCount; d++) { near = fromNeighbors[d][cen]; if (!adjacency.validate(near)) // Outside the map continue; //if(adjacency.isBlocked(cen, d, neighbors, gradientMap, WALL)) // continue; if(adjacency.twoStepRule) { near = fromNeighbors[d][mid = near]; // Outside the map if (!adjacency.validate(near)) continue; csm = lowest.get(costMap[mid]) * heuristics[d]; cs = lowest.get(costMap[near]) * heuristics[d]; if (!closed.containsKey(near) && !open.containsKey(near) && (gradientMap[mid] = cell.getDoubleValue() + csm) + cs < gradientMap[near]) { setFresh(near, cell.getDoubleValue() + cs + csm); ++numAssigned; ++mappedCount; } } else { cs = lowest.get(costMap[near] | (adjacency.extractR(cen) == adjacency.extractR(near) ? 0 : 0x10000)) * heuristics[d]; //double h = adjacency.measurement.heuristic(adjacency.directions[d]); if (!closed.containsKey(near) && !open.containsKey(near) && gradientMap[cen] + cs < gradientMap[near]) { setFresh(near, cell.getDoubleValue() + cs); ++numAssigned; ++mappedCount; } } } } open.clear(); open.putAll(fresh); fresh.clear(); } closed.clear(); open.clear(); double[] gradientClone = new double[maxLength]; for (int l = 0; l < maxLength; l++) { if (gradientMap[l] == FLOOR) gradientClone[l] = DARK; else gradientClone[l] = gradientMap[l]; } return gradientClone; } /** * Recalculate the CustomDijkstra map up to a limit and return it. Cells that were marked as goals with setGoal will have * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further * from goals will have a value equal to the distance from the nearest goal. If a cell would take more steps to * reach than the given limit, it will have a value of DARK if it was passable instead of the distance. The * exceptions are walls, which will have a value defined by the WALL constant in this class, and areas that the scan * was unable to reach, which will have a value defined by the DARK constant in this class. This uses the * current measurement. * * @param limit The maximum number of steps to scan outward from a goal. * @param impassable An array or vararg of int keys representing the locations of enemies or other moving obstacles * to a path that cannot be moved through; this can be null if there are no such obstacles. * @return A double array using the dimensions of what this knows about the physical map. */ public double[] partialScanLarge(int size, int limit, int... impassable) { if(impassable == null) return partialScanLargeInternal(size, limit, null, -1); return partialScanLargeInternal(size, limit, impassable, impassable.length); } /** * Recalculate the CustomDijkstra map up to a limit and return it. Cells that were marked as goals with setGoal will have * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further * from goals will have a value equal to the distance from the nearest goal. If a cell would take more steps to * reach than the given limit, it will have a value of DARK if it was passable instead of the distance. The * exceptions are walls, which will have a value defined by the WALL constant in this class, and areas that the scan * was unable to reach, which will have a value defined by the DARK constant in this class. This uses the * current measurement. * * @param limit The maximum number of steps to scan outward from a goal. * @param impassable An IntVLA of int keys representing the locations of enemies or other moving obstacles * to a path that cannot be moved through; this can be null if there are no such obstacles. * @param usablePortion how much of impassable to use (if non-null); this should usually be {@code impassable.size} * @return A double array using the dimensions of what this knows about the physical map. */ public double[] partialScanLarge(int size, int limit, IntVLA impassable, int usablePortion) { if(impassable == null) return partialScanLargeInternal(size, limit, null, -1); return partialScanLargeInternal(size, limit, impassable.items, Math.min(usablePortion, impassable.size)); } protected double[] partialScanLargeInternal(int size, int limit, int[] impassable, int usable) { if (!initialized) return null; Adjacency adjacency = this.adjacency; if (impassable != null) { if(usable > impassable.length) usable = impassable.length; for (int i = 0; i < usable; i++) { adjacency.putAllVariants(closed, impassable[i], WALL, -size); } } int[][] fromNeighbors = neighbors[0]; int near, cen, neighborCount = neighbors.length, mid, tmp, tmp2, xStore, yStore, rStore, nStore; double valStore; mappedCount = 0; for (IntDoubleOrderedMap.MapEntry entry : goals.mapEntrySet()) { tmp = entry.getIntKey(); xStore = adjacency.extractX(tmp); yStore = adjacency.extractY(tmp); rStore = adjacency.extractR(tmp); nStore = adjacency.extractN(tmp); valStore = entry.getDoubleValue(); for (int a = 0; a < size; a++) { for (int b = 0; b < size; b++) { tmp2 = adjacency.composite(xStore - a, yStore - b, rStore, nStore); if (tmp2 >= 0) { closed.remove(tmp2); gradientMap[tmp2] = valStore; mappedCount++; } } } } double currentLowest = 999000, cs, csm; IntDoubleOrderedMap lowest = new IntDoubleOrderedMap(); int maxLength = gradientMap.length; for (int l = 0; l < maxLength; l++) { if (gradientMap[l] > FLOOR && !goals.containsKey(l)) { xStore = adjacency.extractX(l); yStore = adjacency.extractY(l); rStore = adjacency.extractR(l); nStore = adjacency.extractN(l); for (int a = 0; a < size; a++) { for (int b = 0; b < size; b++) { tmp2 = adjacency.composite(xStore - a, yStore - b, rStore, nStore); if (tmp2 >= 0) closed.put(tmp2, physicalMap[l]); } } } else if (gradientMap[l] < currentLowest) { currentLowest = gradientMap[l]; lowest.clear(); lowest.put(l, currentLowest); } else if (gradientMap[l] == currentLowest) { lowest.put(l, currentLowest); } } int numAssigned = lowest.size(); open.putAll(lowest); lowest = adjacency.costRules; int iter = 0; while (numAssigned > 0 && iter++ < limit) { numAssigned = 0; for (IntDoubleOrderedMap.MapEntry cell : open.mapEntrySet()) { cen = cell.getIntKey(); for (int d = 0; d < neighborCount; d++) { near = fromNeighbors[d][cen]; if (!adjacency.validate(near)) // Outside the map continue; //if(adjacency.isBlocked(cen, d, neighbors, gradientMap, WALL)) // continue; if(adjacency.twoStepRule) { near = fromNeighbors[d][mid = near]; // Outside the map if (!adjacency.validate(near)) continue; csm = lowest.get(costMap[mid]) * heuristics[d]; cs = lowest.get(costMap[near]) * heuristics[d]; if (!closed.containsKey(near) && !open.containsKey(near) && (gradientMap[mid] = cell.getDoubleValue() + csm) + cs < gradientMap[near]) { setFresh(near, cell.getDoubleValue() + cs + csm); ++numAssigned; ++mappedCount; } } else { cs = lowest.get(costMap[near] | (adjacency.extractR(cen) == adjacency.extractR(near) ? 0 : 0x10000)) * heuristics[d]; //double h = adjacency.measurement.heuristic(adjacency.directions[d]); if (!closed.containsKey(near) && !open.containsKey(near) && gradientMap[cen] + cs < gradientMap[near]) { setFresh(near, cell.getDoubleValue() + cs); ++numAssigned; ++mappedCount; } } } } open.clear(); open.putAll(fresh); fresh.clear(); } closed.clear(); open.clear(); double[] gradientClone = new double[maxLength]; for (int l = 0; l < maxLength; l++) { if (gradientMap[l] == FLOOR) gradientClone[l] = DARK; else gradientClone[l] = gradientMap[l]; } return gradientClone; } /* public double[][] scan(Set<Coord> impassable, int size) { if (!initialized) return null; if (impassable == null) impassable = new OrderedSet<>(); IntDoubleOrderedMap blocking = new IntDoubleOrderedMap(impassable.size()); for (Coord pt : impassable) { blocking.put(pt.encode(), WALL); for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { if (x + y == 0) continue; if (gradientMap[pt.x - x][pt.y - y] <= FLOOR) blocking.put(Coord.pureEncode(pt.x - x, pt.y - y), DARK); } } } closed.putAll(blocking); Coord dec, cen, adj; int enc; for (IntDoubleOrderedMap.MapEntry entry : goals.mapEntrySet()) { if (closed.containsKey(entry.getIntKey())) closed.remove(entry.getIntKey()); dec = Coord.decode(entry.getIntKey()); gradientMap[dec.x][dec.y] = entry.getDoubleValue(); } mappedCount = goals.size(); double currentLowest = 999000; IntDoubleOrderedMap lowest = new IntDoubleOrderedMap(); int temp, p; for (int y = 0; y < height; y++) { I_AM_BECOME_DEATH_DESTROYER_OF_WORLDS: for (int x = 0; x < width; x++) { p = Coord.pureEncode(x, y); if (gradientMap[x][y] > FLOOR && !goals.containsKey(p)) { closed.put(p, physicalMap[x][y]); if (gradientMap[x][y] == WALL) { for (int i = 0; i < size; i++) { if (x - i < 0) continue; for (int j = 0; j < size; j++) { temp = Coord.pureEncode(x - i, y - j); if (y - j < 0 || closed.containsKey(temp)) continue; if (gradientMap[x - i][y - j] <= FLOOR && !goals.containsKey(temp)) closed.put(temp, DARK); } } } } else if (gradientMap[x][y] < currentLowest && !closed.containsKey(p)) { for (int i = 0; i < size; i++) { if (x + i >= width) continue I_AM_BECOME_DEATH_DESTROYER_OF_WORLDS; for (int j = 0; j < size; j++) { temp = Coord.pureEncode(x + i, y + j); if (y + j >= height || closed.containsKey(temp)) continue I_AM_BECOME_DEATH_DESTROYER_OF_WORLDS; } } currentLowest = gradientMap[x][y]; lowest.clear(); lowest.put(Coord.pureEncode(x, y), currentLowest); } else if (gradientMap[x][y] == currentLowest && !closed.containsKey(p)) { if (!closed.containsKey(p)) { for (int i = 0; i < size; i++) { if (x + i >= width) continue I_AM_BECOME_DEATH_DESTROYER_OF_WORLDS; for (int j = 0; j < size; j++) { temp = Coord.pureEncode(x + i, y + j); if (y + j >= height || closed.containsKey(temp)) continue I_AM_BECOME_DEATH_DESTROYER_OF_WORLDS; } } lowest.put(p, currentLowest); } } } } int numAssigned = lowest.size(); open.putAll(lowest); Direction[] dirs = (measurement == Measurement.MANHATTAN) ? Direction.CARDINALS : Direction.OUTWARDS; while (numAssigned > 0) { numAssigned = 0; for (IntDoubleOrderedMap.MapEntry cell : open.mapEntrySet()) { cen = Coord.decode(cell.getIntKey()); for (int d = 0; d < dirs.length; d++) { adj = cen.translate(dirs[d].deltaX, dirs[d].deltaY); if (adj.x < 0 || adj.y < 0 || width <= adj.x || height <= adj.y) // Outside the map continue; enc = adj.encode(); double h = heuristic(dirs[d]); if (!closed.containsKey(enc) && !open.containsKey(enc) && gradientMap[cen.x][cen.y] + h * costMap[adj.x][adj.y] < gradientMap[adj.x][adj.y]) { setFresh(adj, cell.getDoubleValue() + h * costMap[adj.x][adj.y]); ++numAssigned; ++mappedCount; } } } // closed.putAll(open); open = new IntDoubleOrderedMap(fresh); fresh.clear(); } closed.clear(); open.clear(); double[][] gradientClone = new double[width][height]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if (gradientMap[x][y] == FLOOR) { gradientMap[x][y] = DARK; } } System.arraycopy(gradientMap[x], 0, gradientClone[x], 0, height); } return gradientClone; } */ /** * Scans the dungeon using OldCustomDijkstraMap.scan with the listed goals and start point, and returns a list * of Coord positions (using the current measurement) needed to get closer to the closest reachable * goal. The maximum length of the returned list is given by length; if moving the full length of * the list would place the mover in a position shared by one of the positions in onlyPassable * (which is typically filled with friendly units that can be passed through in multi-tile- * movement scenarios), it will recalculate a move so that it does not pass into that cell. * The keys in impassable should be the positions of enemies and obstacles that cannot be moved * through, and will be ignored if there is a goal overlapping one. * <br> * This caches its result in a member field, path, which can be fetched after finding a path and will change with * each call to a pathfinding method. * @param length the length of the path to calculate * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder * @param targets a vararg or array of Coord that this will try to pathfind toward * @return an ArrayList of Coord that will contain the locations of this creature as it goes toward a target. Copy of path. */ public IntVLA findPath(int length, IntVLA impassable, IntVLA onlyPassable, int start, int... targets) { return findPath(length, -1, impassable, onlyPassable, start, targets); } /** * Scans the dungeon using OldCustomDijkstraMap.scan with the listed goals and start point, and returns a list * of Coord positions (using the current measurement) needed to get closer to the closest reachable * goal. The maximum length of the returned list is given by length; if moving the full length of * the list would place the mover in a position shared by one of the positions in onlyPassable * (which is typically filled with friendly units that can be passed through in multi-tile- * movement scenarios), it will recalculate a move so that it does not pass into that cell. * The keys in impassable should be the positions of enemies and obstacles that cannot be moved * through, and will be ignored if there is a goal overlapping one. * <br> * This caches its result in a member field, path, which can be fetched after finding a path and will change with * each call to a pathfinding method. * @param length the length of the path to calculate * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder * @param targets a vararg or array of Coord that this will try to pathfind toward * @return an ArrayList of Coord that will contain the locations of this creature as it goes toward a target. Copy of path. */ public IntVLA findPath(int length, int scanLimit, IntVLA impassable, IntVLA onlyPassable, int start, int... targets) { if (!initialized) return null; path.clear(); IntVLA impassable2; if (impassable == null) impassable2 = new IntVLA(); else { impassable2 = new IntVLA(impassable); impassable2.removeValue(start); } if (onlyPassable == null) onlyPassable = new IntVLA(); resetMap(); for (int g = 0; g < targets.length; g++) { setGoal(targets[g]); } if (goals.isEmpty()) return new IntVLA(path); Adjacency adjacency = this.adjacency; int[][] toNeighbors = neighbors[1]; if(length < 0) length = 0; if(scanLimit <= 0 || scanLimit < length) scan(impassable2, impassable2.size); else partialScan(scanLimit, impassable2, impassable2.size); int currentPos = start, pt; double paidLength = 0.0; while (true) { if (frustration > 500) { path.clear(); break; } double best = gradientMap[currentPos]; rng.randomOrdering(adjacency.maxAdjacent, reuse); int choice = rng.nextIntHasty(adjacency.maxAdjacent); for (int d = 0; d < adjacency.maxAdjacent; d++) { pt = toNeighbors[reuse[d]][currentPos]; if(adjacency.twoStepRule) pt = toNeighbors[reuse[d]][pt]; if (gradientMap[pt] < best && !path.contains(pt)) { best = gradientMap[pt]; choice = reuse[d];// adjacency.invertAdjacent[reuse[d]]; } } if (best >= gradientMap[currentPos] || physicalMap[toNeighbors[choice][currentPos]] > FLOOR) { break; } currentPos = toNeighbors[choice][pt = currentPos]; if(adjacency.twoStepRule) currentPos = toNeighbors[choice][currentPos]; path.add(currentPos); paidLength += adjacency.costRules.get(costMap[currentPos] | (adjacency.extractR(pt) == adjacency.extractR(currentPos) ? 0 : 0x10000)); frustration++; if (paidLength > length - 1.0) { if (onlyPassable.contains(currentPos)) { closed.put(currentPos, WALL); impassable2.add(currentPos); return findPath(length, scanLimit, impassable2, onlyPassable, start, targets); } break; } if (gradientMap[currentPos] == 0) break; } frustration = 0; goals.clear(); return new IntVLA(path); } // TODO: Tackle these next two once there's a CustomLOS class /* * Scans the dungeon using OldCustomDijkstraMap.scan with the listed goals and start point, and returns a list * of Coord positions (using the current measurement) needed to get closer to a goal, until preferredRange is * reached, or further from a goal if the preferredRange has not been met at the current distance. * The maximum length of the returned list is given by moveLength; if moving the full length of * the list would place the mover in a position shared by one of the positions in onlyPassable * (which is typically filled with friendly units that can be passed through in multi-tile- * movement scenarios), it will recalculate a move so that it does not pass into that cell. * The keys in impassable should be the positions of enemies and obstacles that cannot be moved * through, and will be ignored if there is a goal overlapping one. * <br> * This caches its result in a member field, path, which can be fetched after finding a path and will change with * each call to a pathfinding method. * * @param moveLength the length of the path to calculate * @param preferredRange the distance this unit will try to keep from a target * @param los a squidgrid.LOS object if the preferredRange should try to stay in line of sight, or null if LoS * should be disregarded. * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder * @param targets a vararg or array of Coord that this will try to pathfind toward * @return an ArrayList of Coord that will contain the locations of this creature as it goes toward a target. Copy of path. * / public ArrayList<Coord> findAttackPath(int moveLength, int preferredRange, LOS los, Set<Coord> impassable, Set<Coord> onlyPassable, Coord start, Coord... targets) { return findAttackPath(moveLength, preferredRange, preferredRange, los, impassable, onlyPassable, start, targets); } */ /* * Scans the dungeon using OldCustomDijkstraMap.scan with the listed goals and start point, and returns a list * of Coord positions (using the current measurement) needed to get closer to a goal, until a cell is reached with * a distance from a goal that is at least equal to minPreferredRange and no more than maxPreferredRange, * which may go further from a goal if the minPreferredRange has not been met at the current distance. * The maximum length of the returned list is given by moveLength; if moving the full length of * the list would place the mover in a position shared by one of the positions in onlyPassable * (which is typically filled with friendly units that can be passed through in multi-tile- * movement scenarios), it will recalculate a move so that it does not pass into that cell. * The keys in impassable should be the positions of enemies and obstacles that cannot be moved * through, and will be ignored if there is a goal overlapping one. * <br> * This caches its result in a member field, path, which can be fetched after finding a path and will change with * each call to a pathfinding method. * * @param moveLength the length of the path to calculate * @param minPreferredRange the (inclusive) lower bound of the distance this unit will try to keep from a target * @param maxPreferredRange the (inclusive) upper bound of the distance this unit will try to keep from a target * @param los a squidgrid.LOS object if the preferredRange should try to stay in line of sight, or null if LoS * should be disregarded. * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder * @param targets a vararg or array of Coord that this will try to pathfind toward * @return an ArrayList of Coord that will contain the locations of this creature as it goes toward a target. Copy of path. * / public ArrayList<Coord> findAttackPath(int moveLength, int minPreferredRange, int maxPreferredRange, LOS los, Set<Coord> impassable, Set<Coord> onlyPassable, Coord start, Coord... targets) { if (!initialized) return null; if (minPreferredRange < 0) minPreferredRange = 0; if (maxPreferredRange < minPreferredRange) maxPreferredRange = minPreferredRange; double[][] resMap = new double[width][height]; if (los != null) { for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { resMap[x][y] = (physicalMap[x][y] == WALL) ? 1.0 : 0.0; } } } path.clear(); OrderedSet<Coord> impassable2; if (impassable == null) impassable2 = new OrderedSet<>(); else impassable2 = new OrderedSet<>(impassable); if (onlyPassable == null) onlyPassable = new OrderedSet<>(); resetMap(); for (Coord goal : targets) { setGoal(goal.x, goal.y); } if (goals.isEmpty()) return new ArrayList<>(path); Measurement mess = measurement; if (measurement == Measurement.EUCLIDEAN) { measurement = Measurement.CHEBYSHEV; } scan(impassable2); goals.clear(); for (int x = 0; x < width; x++) { CELL: for (int y = 0; y < height; y++) { if (gradientMap[x][y] == WALL || gradientMap[x][y] == DARK) continue; if (gradientMap[x][y] >= minPreferredRange && gradientMap[x][y] <= maxPreferredRange) { for (Coord goal : targets) { if (los == null || los.isReachable(resMap, x, y, goal.x, goal.y)) { setGoal(x, y); gradientMap[x][y] = 0; continue CELL; } } gradientMap[x][y] = FLOOR; } else gradientMap[x][y] = FLOOR; } } measurement = mess; scan(impassable2); Coord currentPos = start; double paidLength = 0.0; while (true) { if (frustration > 500) { path.clear(); break; } double best = gradientMap[currentPos.x][currentPos.y]; final Direction[] dirs = appendDirToShuffle(rng); int choice = rng.nextInt(dirs.length); for (int d = 0; d < dirs.length; d++) { Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); if (gradientMap[pt.x][pt.y] < best) { if (dirs[choice] == Direction.NONE || !path.contains(pt)) { best = gradientMap[pt.x][pt.y]; choice = d; } } } if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { path.clear(); break; } currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); path.add(Coord.get(currentPos.x, currentPos.y)); paidLength += costMap[currentPos.x][currentPos.y]; frustration++; if (paidLength > moveLength - 1.0) { if (onlyPassable.contains(currentPos)) { closed.put(currentPos.encode(), WALL); impassable2.add(currentPos); return findAttackPath(moveLength, minPreferredRange, maxPreferredRange, los, impassable2, onlyPassable, start, targets); } break; } if (gradientMap[currentPos.x][currentPos.y] == 0) break; } frustration = 0; goals.clear(); return new ArrayList<>(path); } */ private double cachedLongerPaths = 1.2; private long cachedImpassable = 0L, cachedFearSources = 0L; private double[] cachedFleeMap; private int cachedSize = 1; /** * Scans the dungeon using OldCustomDijkstraMap.scan with the listed fearSources and start point, and returns a list * of Coord positions (using Manhattan distance) needed to get further from the closest fearSources, meant * for running away. The maximum length of the returned list is given by length; if moving the full * length of the list would place the mover in a position shared by one of the positions in onlyPassable * (which is typically filled with friendly units that can be passed through in multi-tile- * movement scenarios), it will recalculate a move so that it does not pass into that cell. * The keys in impassable should be the positions of enemies and obstacles that cannot be moved * through, and will be ignored if there is a fearSource overlapping one. The preferLongerPaths parameter * is meant to be tweaked and adjusted; higher values should make creatures prefer to escape out of * doorways instead of hiding in the closest corner, and a value of 1.2 should be typical for many maps. * The parameters preferLongerPaths, impassable, and the varargs used for fearSources will be cached, and * any subsequent calls that use the same values as the last values passed will avoid recalculating * unnecessary scans. * <br> * This caches its result in a member field, path, which can be fetched after finding a path and will change with * each call to a pathfinding method. * * @param length the length of the path to calculate * @param preferLongerPaths Set this to 1.2 if you aren't sure; it will probably need tweaking for different maps. * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder * @param fearSources a vararg or array of Coord positions to run away from * @return an ArrayList of Coord that will contain the locations of this creature as it goes away from fear sources. Copy of path. */ public IntVLA findFleePath(int length, double preferLongerPaths, IntVLA impassable, IntVLA onlyPassable, int start, int... fearSources) { return findFleePath(length, -1, preferLongerPaths, impassable, onlyPassable, start, fearSources); } /** * Scans the dungeon using OldCustomDijkstraMap.scan with the listed fearSources and start point, and returns a list * of Coord positions (using Manhattan distance) needed to get further from the closest fearSources, meant * for running away. The maximum length of the returned list is given by length; if moving the full * length of the list would place the mover in a position shared by one of the positions in onlyPassable * (which is typically filled with friendly units that can be passed through in multi-tile- * movement scenarios), it will recalculate a move so that it does not pass into that cell. * The keys in impassable should be the positions of enemies and obstacles that cannot be moved * through, and will be ignored if there is a fearSource overlapping one. The preferLongerPaths parameter * is meant to be tweaked and adjusted; higher values should make creatures prefer to escape out of * doorways instead of hiding in the closest corner, and a value of 1.2 should be typical for many maps. * The parameters preferLongerPaths, impassable, and the varargs used for fearSources will be cached, and * any subsequent calls that use the same values as the last values passed will avoid recalculating * unnecessary scans. * <br> * This caches its result in a member field, path, which can be fetched after finding a path and will change with * each call to a pathfinding method. * * @param length the length of the path to calculate * @param scanLimit how many steps away from a fear source to calculate; negative scans the whole map * @param preferLongerPaths Set this to 1.2 if you aren't sure; it will probably need tweaking for different maps. * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder * @param fearSources a vararg or array of Coord positions to run away from * @return an ArrayList of Coord that will contain the locations of this creature as it goes away from fear sources. Copy of path. */ public IntVLA findFleePath(int length, int scanLimit, double preferLongerPaths, IntVLA impassable, IntVLA onlyPassable, int start, int... fearSources) { if (!initialized) return null; path.clear(); IntVLA impassable2; if (impassable == null) impassable2 = new IntVLA(); else impassable2 = new IntVLA(impassable); if (onlyPassable == null) onlyPassable = new IntVLA(); if (fearSources == null || fearSources.length < 1) { path.clear(); return new IntVLA(1); } if (cachedSize == 1 && preferLongerPaths == cachedLongerPaths && impassable2.hash64() == cachedImpassable && CrossHash.Lightning.hash64(fearSources) == cachedFearSources) { gradientMap = cachedFleeMap; } else { cachedLongerPaths = preferLongerPaths; cachedImpassable = impassable2.hash64(); cachedFearSources = CrossHash.Lightning.hash64(fearSources); cachedSize = 1; resetMap(); for (int g = 0; g < fearSources.length; g++) { setGoal(fearSources[g]); } if (goals.isEmpty()) return new IntVLA(path); if(length < 0) length = 0; if(scanLimit <= 0 || scanLimit < length) cachedFleeMap = scan(impassable2, impassable2.size); else cachedFleeMap = partialScan(scanLimit, impassable2, impassable2.size); for (int l = 0; l < gradientMap.length; l++) { gradientMap[l] *= (gradientMap[l] >= FLOOR) ? 1.0 : - preferLongerPaths; } if(scanLimit <= 0 || scanLimit < length) cachedFleeMap = scan(impassable2, impassable2.size); else cachedFleeMap = partialScan(scanLimit, impassable2, impassable2.size); } Adjacency adjacency = this.adjacency; int[][] toNeighbors = neighbors[1]; int currentPos = start, pt; double paidLength = 0.0; while (true) { if (frustration > 500) { path.clear(); break; } double best = gradientMap[currentPos]; rng.randomOrdering(adjacency.maxAdjacent, reuse); int choice = rng.nextIntHasty(adjacency.maxAdjacent); for (int d = 0; d < adjacency.maxAdjacent; d++) { pt = toNeighbors[reuse[d]][currentPos]; if (gradientMap[pt] < best && !path.contains(pt)) { best = gradientMap[pt]; choice = reuse[d]; } } if (best >= gradientMap[currentPos] || physicalMap[toNeighbors[choice][currentPos]] > FLOOR) { path.clear(); break; } currentPos = toNeighbors[choice][pt = currentPos]; path.add(currentPos); paidLength += adjacency.costRules.get(costMap[currentPos] | (adjacency.extractR(pt) == adjacency.extractR(currentPos) ? 0 : 0x10000)); frustration++; if (paidLength > length - 1.0) { if (onlyPassable.contains(currentPos)) { closed.put(currentPos, WALL); impassable2.add(currentPos); return findFleePath(length, scanLimit, preferLongerPaths, impassable2, onlyPassable, start, fearSources); } break; } } frustration = 0; goals.clear(); return new IntVLA(path); } /** * Scans the dungeon using OldCustomDijkstraMap.scan with the listed goals and start point, and returns a list * of Coord positions (using the current measurement) needed to get closer to the closest reachable * goal. The maximum length of the returned list is given by length; if moving the full length of * the list would place the mover in a position shared by one of the positions in onlyPassable * (which is typically filled with friendly units that can be passed through in multi-tile- * movement scenarios), it will recalculate a move so that it does not pass into that cell. * The keys in impassable should be the positions of enemies and obstacles that cannot be moved * through, and will be ignored if there is a goal overlapping one. * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. * <br> * This caches its result in a member field, path, which can be fetched after finding a path and will change with * each call to a pathfinding method. * * @param size the side length of the creature trying to find a path * @param length the length of the path to calculate * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder * @param targets a vararg or array of Coord that this will try to pathfind toward * @return an ArrayList of Coord that will contain the min-x, min-y locations of this creature as it goes toward a target. Copy of path. */ public IntVLA findPathLarge (int size, int length, IntVLA impassable, IntVLA onlyPassable, int start, int... targets) { return findPathLarge(size, length, -1, impassable, onlyPassable, start, targets); } /** * Scans the dungeon using OldCustomDijkstraMap.scanLarge with the listed goals and start point, and returns a list * of Coord positions (using the current measurement) needed to get closer to the closest reachable * goal. The maximum length of the returned list is given by length; if moving the full length of * the list would place the mover in a position shared by one of the positions in onlyPassable * (which is typically filled with friendly units that can be passed through in multi-tile- * movement scenarios), it will recalculate a move so that it does not pass into that cell. * The keys in impassable should be the positions of enemies and obstacles that cannot be moved * through, and will be ignored if there is a goal overlapping one. * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. * <br> * This caches its result in a member field, path, which can be fetched after finding a path and will change with * each call to a pathfinding method. * * @param size the side length of the creature trying to find a path * @param length the length of the path to calculate * @param scanLimit how many steps away from a goal to calculate; negative scans the whole map * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder * @param targets a vararg or array of Coord that this will try to pathfind toward * @return an ArrayList of Coord that will contain the min-x, min-y locations of this creature as it goes toward a target. Copy of path. */ public IntVLA findPathLarge (int size, int length, int scanLimit, IntVLA impassable, IntVLA onlyPassable, int start, int... targets) { if (!initialized) return null; path.clear(); IntVLA impassable2; if (impassable == null) impassable2 = new IntVLA(); else impassable2 = new IntVLA(impassable); if (onlyPassable == null) onlyPassable = new IntVLA(); resetMap(); for (int g = 0; g < targets.length; g++) { setGoal(targets[g]); } if (goals.isEmpty()) return new IntVLA(path); Adjacency adjacency = this.adjacency; int[][] toNeighbors = neighbors[1]; if(length < 0) length = 0; if(scanLimit <= 0 || scanLimit < length) scanLarge(size, impassable2, impassable2.size); else partialScanLarge(size, scanLimit, impassable2, impassable2.size); int currentPos = start, pt; double paidLength = 0.0; while (true) { if (frustration > 500) { path.clear(); break; } double best = gradientMap[currentPos]; rng.randomOrdering(adjacency.maxAdjacent, reuse); int choice = rng.nextIntHasty(adjacency.maxAdjacent); for (int d = 0; d < adjacency.maxAdjacent; d++) { pt = toNeighbors[reuse[d]][currentPos]; if (gradientMap[pt] < best && !path.contains(pt)) { best = gradientMap[pt]; choice = reuse[d]; } } if (best >= gradientMap[currentPos] || physicalMap[toNeighbors[choice][currentPos]] > FLOOR) { path.clear(); break; } currentPos = toNeighbors[choice][pt = currentPos]; path.add(currentPos); paidLength += adjacency.costRules.get(costMap[currentPos] | (adjacency.extractR(pt) == adjacency.extractR(currentPos) ? 0 : 0x10000)); frustration++; if (paidLength > length - 1.0) { if (onlyPassable.contains(currentPos)) { closed.put(currentPos, WALL); impassable2.add(currentPos); return findPathLarge(size, scanLimit, length, impassable2, onlyPassable, start, targets); } break; } if (gradientMap[currentPos] == 0) break; } frustration = 0; goals.clear(); return new IntVLA(path); } /* public ArrayList<Coord> findPathLarge(int size, int length, Set<Coord> impassable, Set<Coord> onlyPassable, Coord start, Coord... targets) { if (!initialized) return null; path.clear(); OrderedSet<Coord> impassable2; if (impassable == null) impassable2 = new OrderedSet<>(); else impassable2 = new OrderedSet<>(impassable); if (onlyPassable == null) onlyPassable = new OrderedSet<>(); resetMap(); for (Coord goal : targets) { setGoal(goal.x, goal.y); } if (goals.isEmpty()) return new ArrayList<>(path); scan(impassable2, size); Coord currentPos = start; double paidLength = 0.0; while (true) { if (frustration > 500) { path.clear(); break; } double best = gradientMap[currentPos.x][currentPos.y]; final Direction[] dirs = appendDirToShuffle(rng); int choice = rng.nextInt(dirs.length); for (int d = 0; d < dirs.length; d++) { Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); if (gradientMap[pt.x][pt.y] < best) { if (dirs[choice] == Direction.NONE || !path.contains(pt)) { best = gradientMap[pt.x][pt.y]; choice = d; } } } if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { path.clear(); break; } currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); path.add(currentPos); paidLength += costMap[currentPos.x][currentPos.y]; frustration++; if (paidLength > length - 1.0) { if (onlyPassable.contains(currentPos)) { closed.put(currentPos.encode(), WALL); impassable2.add(currentPos); return findPathLarge(size, length, impassable2, onlyPassable, start, targets); } break; } if (gradientMap[currentPos.x][currentPos.y] == 0) break; } frustration = 0; goals.clear(); return new ArrayList<>(path); } */ // TODO: Again, this needs CustomLOS /* * Scans the dungeon using OldCustomDijkstraMap.scan with the listed goals and start point, and returns a list * of Coord positions (using the current measurement) needed to get closer to a goal, until preferredRange is * reached, or further from a goal if the preferredRange has not been met at the current distance. * The maximum length of the returned list is given by moveLength; if moving the full length of * the list would place the mover in a position shared by one of the positions in onlyPassable * (which is typically filled with friendly units that can be passed through in multi-tile- * movement scenarios), it will recalculate a move so that it does not pass into that cell. * The keys in impassable should be the positions of enemies and obstacles that cannot be moved * through, and will be ignored if there is a goal overlapping one. * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. * <br> * This caches its result in a member field, path, which can be fetched after finding a path and will change with * each call to a pathfinding method. * * @param size the side length of the creature trying to find a path * @param moveLength the length of the path to calculate * @param preferredRange the distance this unit will try to keep from a target * @param los a squidgrid.LOS object if the preferredRange should try to stay in line of sight, or null if LoS * should be disregarded. * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder * @param targets a vararg or array of Coord that this will try to pathfind toward * @return an ArrayList of Coord that will contain the min-x, min-y locations of this creature as it goes toward a target. Copy of path. * / public ArrayList<Coord> findAttackPathLarge(int size, int moveLength, int preferredRange, LOS los, Set<Coord> impassable, Set<Coord> onlyPassable, Coord start, Coord... targets) { if (!initialized) return null; if (preferredRange < 0) preferredRange = 0; double[][] resMap = new double[width][height]; if (los != null) { for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { resMap[x][y] = (physicalMap[x][y] == WALL) ? 1.0 : 0.0; } } } path.clear(); OrderedSet<Coord> impassable2; if (impassable == null) impassable2 = new OrderedSet<>(); else impassable2 = new OrderedSet<>(impassable); if (onlyPassable == null) onlyPassable = new OrderedSet<>(); resetMap(); for (Coord goal : targets) { setGoal(goal.x, goal.y); } if (goals.isEmpty()) return new ArrayList<>(path); Measurement mess = measurement; if (measurement == Measurement.EUCLIDEAN) { measurement = Measurement.CHEBYSHEV; } scan(impassable2, size); goals.clear(); for (int x = 0; x < width; x++) { CELL: for (int y = 0; y < height; y++) { if (gradientMap[x][y] == WALL || gradientMap[x][y] == DARK) continue; if (x + 2 < width && y + 2 < height && gradientMap[x][y] == preferredRange) { for (Coord goal : targets) { if (los == null || los.isReachable(resMap, x, y, goal.x, goal.y) || los.isReachable(resMap, x + 1, y, goal.x, goal.y) || los.isReachable(resMap, x, y + 1, goal.x, goal.y) || los.isReachable(resMap, x + 1, y + 1, goal.x, goal.y)) { setGoal(x, y); gradientMap[x][y] = 0; continue CELL; } } gradientMap[x][y] = FLOOR; } else gradientMap[x][y] = FLOOR; } } measurement = mess; scan(impassable2, size); Coord currentPos = start; double paidLength = 0.0; while (true) { if (frustration > 500) { path.clear(); break; } double best = gradientMap[currentPos.x][currentPos.y]; final Direction[] dirs = appendDirToShuffle(rng); int choice = rng.nextInt(dirs.length); for (int d = 0; d < dirs.length; d++) { Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); if (gradientMap[pt.x][pt.y] < best) { if (dirs[choice] == Direction.NONE || !path.contains(pt)) { best = gradientMap[pt.x][pt.y]; choice = d; } } } if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { path.clear(); break; } currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); path.add(currentPos); frustration++; paidLength += costMap[currentPos.x][currentPos.y]; if (paidLength > moveLength - 1.0) { if (onlyPassable.contains(currentPos)) { closed.put(currentPos.encode(), WALL); impassable2.add(currentPos); return findAttackPathLarge(size, moveLength, preferredRange, los, impassable2, onlyPassable, start, targets); } break; } if (gradientMap[currentPos.x][currentPos.y] == 0) break; } frustration = 0; goals.clear(); return new ArrayList<>(path); } /* * Scans the dungeon using OldCustomDijkstraMap.scan with the listed goals and start point, and returns a list * of Coord positions (using the current measurement) needed to get closer to a goal, until a cell is reached with * a distance from a goal that is at least equal to minPreferredRange and no more than maxPreferredRange, * which may go further from a goal if the minPreferredRange has not been met at the current distance. * The maximum length of the returned list is given by moveLength; if moving the full length of * the list would place the mover in a position shared by one of the positions in onlyPassable * (which is typically filled with friendly units that can be passed through in multi-tile- * movement scenarios), it will recalculate a move so that it does not pass into that cell. * The keys in impassable should be the positions of enemies and obstacles that cannot be moved * through, and will be ignored if there is a goal overlapping one. * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. * <br> * This caches its result in a member field, path, which can be fetched after finding a path and will change with * each call to a pathfinding method. * * @param size the side length of the creature trying to find a path * @param moveLength the length of the path to calculate * @param minPreferredRange the (inclusive) lower bound of the distance this unit will try to keep from a target * @param maxPreferredRange the (inclusive) upper bound of the distance this unit will try to keep from a target * @param los a squidgrid.LOS object if the preferredRange should try to stay in line of sight, or null if LoS * should be disregarded. * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder * @param targets a vararg or array of Coord that this will try to pathfind toward * @return an ArrayList of Coord that will contain the min-x, min-y locations of this creature as it goes toward a target. Copy of path. * / public ArrayList<Coord> findAttackPathLarge(int size, int moveLength, int minPreferredRange, int maxPreferredRange, LOS los, Set<Coord> impassable, Set<Coord> onlyPassable, Coord start, Coord... targets) { if (!initialized) return null; if (minPreferredRange < 0) minPreferredRange = 0; if (maxPreferredRange < minPreferredRange) maxPreferredRange = minPreferredRange; double[][] resMap = new double[width][height]; if (los != null) { for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { resMap[x][y] = (physicalMap[x][y] == WALL) ? 1.0 : 0.0; } } } path.clear(); OrderedSet<Coord> impassable2; if (impassable == null) impassable2 = new OrderedSet<>(); else impassable2 = new OrderedSet<>(impassable); if (onlyPassable == null) onlyPassable = new OrderedSet<>(); resetMap(); for (Coord goal : targets) { setGoal(goal); } if (goals.isEmpty()) return new ArrayList<>(path); Measurement mess = measurement; if (measurement == Measurement.EUCLIDEAN) { measurement = Measurement.CHEBYSHEV; } scan(impassable2, size); goals.clear(); for (int x = 0; x < width; x++) { CELL: for (int y = 0; y < height; y++) { if (gradientMap[x][y] == WALL || gradientMap[x][y] == DARK) continue; if (x + 2 < width && y + 2 < height && gradientMap[x][y] >= minPreferredRange && gradientMap[x][y] <= maxPreferredRange) { for (Coord goal : targets) { if (los == null || los.isReachable(resMap, x, y, goal.x, goal.y) || los.isReachable(resMap, x + 1, y, goal.x, goal.y) || los.isReachable(resMap, x, y + 1, goal.x, goal.y) || los.isReachable(resMap, x + 1, y + 1, goal.x, goal.y)) { setGoal(x, y); gradientMap[x][y] = 0; continue CELL; } } gradientMap[x][y] = FLOOR; } else gradientMap[x][y] = FLOOR; } } measurement = mess; scan(impassable2, size); Coord currentPos = start; double paidLength = 0.0; while (true) { if (frustration > 500) { path.clear(); break; } double best = gradientMap[currentPos.x][currentPos.y]; final Direction[] dirs = appendDirToShuffle(rng); int choice = rng.nextInt(dirs.length); for (int d = 0; d < dirs.length; d++) { Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); if (gradientMap[pt.x][pt.y] < best) { if (dirs[choice] == Direction.NONE || !path.contains(pt)) { best = gradientMap[pt.x][pt.y]; choice = d; } } } if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { path.clear(); break; } currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); path.add(currentPos); frustration++; paidLength += costMap[currentPos.x][currentPos.y]; if (paidLength > moveLength - 1.0) { if (onlyPassable.contains(currentPos)) { closed.put(currentPos.encode(), WALL); impassable2.add(currentPos); return findAttackPathLarge(size, moveLength, minPreferredRange, maxPreferredRange, los, impassable2, onlyPassable, start, targets); } break; } if (gradientMap[currentPos.x][currentPos.y] == 0) break; } frustration = 0; goals.clear(); return new ArrayList<>(path); } */ /** * Scans the dungeon using OldCustomDijkstraMap.scanLarge with the listed fearSources and start point, and returns a list * of Coord positions (using Manhattan distance) needed to get further from the closest fearSources, meant * for running away. The maximum length of the returned list is given by length; if moving the full * length of the list would place the mover in a position shared by one of the positions in onlyPassable * (which is typically filled with friendly units that can be passed through in multi-tile- * movement scenarios), it will recalculate a move so that it does not pass into that cell. * The keys in impassable should be the positions of enemies and obstacles that cannot be moved * through, and will be ignored if there is a fearSource overlapping one. The preferLongerPaths parameter * is meant to be tweaked and adjusted; higher values should make creatures prefer to escape out of * doorways instead of hiding in the closest corner, and a value of 1.2 should be typical for many maps. * The parameters size, preferLongerPaths, impassable, and the varargs used for fearSources will be cached, and * any subsequent calls that use the same values as the last values passed will avoid recalculating * unnecessary scans. Calls to findFleePath will cache as if size is 1, and may share a cache with this function. * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. * <br> * This caches its result in a member field, path, which can be fetched after finding a path and will change with * each call to a pathfinding method. * * @param size the side length of the creature trying the find a path * @param length the length of the path to calculate * @param preferLongerPaths Set this to 1.2 if you aren't sure; it will probably need tweaking for different maps. * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder * @param fearSources a vararg or array of Coord positions to run away from * @return an ArrayList of Coord that will contain the locations of this creature as it goes away from fear sources. Copy of path. */ public IntVLA findFleePathLarge(int size, int length, double preferLongerPaths, IntVLA impassable, IntVLA onlyPassable, int start, int... fearSources) { return findFleePathLarge(size, length, -1, preferLongerPaths, impassable, onlyPassable, start, fearSources); } /** * Scans the dungeon using OldCustomDijkstraMap.scanLarge with the listed fearSources and start point, and returns a list * of Coord positions (using Manhattan distance) needed to get further from the closest fearSources, meant * for running away. The maximum length of the returned list is given by length; if moving the full * length of the list would place the mover in a position shared by one of the positions in onlyPassable * (which is typically filled with friendly units that can be passed through in multi-tile- * movement scenarios), it will recalculate a move so that it does not pass into that cell. * The keys in impassable should be the positions of enemies and obstacles that cannot be moved * through, and will be ignored if there is a fearSource overlapping one. The preferLongerPaths parameter * is meant to be tweaked and adjusted; higher values should make creatures prefer to escape out of * doorways instead of hiding in the closest corner, and a value of 1.2 should be typical for many maps. * The parameters size, preferLongerPaths, impassable, and the varargs used for fearSources will be cached, and * any subsequent calls that use the same values as the last values passed will avoid recalculating * unnecessary scans. Calls to findFleePath will cache as if size is 1, and may share a cache with this function. * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. * <br> * This caches its result in a member field, path, which can be fetched after finding a path and will change with * each call to a pathfinding method. * * @param size the side length of the creature trying the find a path * @param length the length of the path to calculate * @param scanLimit how many steps away from a goal to calculate; negative scans the whole map * @param preferLongerPaths Set this to 1.2 if you aren't sure; it will probably need tweaking for different maps. * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder * @param fearSources a vararg or array of Coord positions to run away from * @return an ArrayList of Coord that will contain the locations of this creature as it goes away from fear sources. Copy of path. */ public IntVLA findFleePathLarge(int size, int length, int scanLimit, double preferLongerPaths, IntVLA impassable, IntVLA onlyPassable, int start, int... fearSources) { if (!initialized) return null; path.clear(); IntVLA impassable2; if (impassable == null) impassable2 = new IntVLA(); else impassable2 = new IntVLA(impassable); if (onlyPassable == null) onlyPassable = new IntVLA(); if (fearSources == null || fearSources.length < 1) { path.clear(); return new IntVLA(1); } if (cachedSize == size && preferLongerPaths == cachedLongerPaths && impassable2.hash64() == cachedImpassable && CrossHash.Lightning.hash64(fearSources) == cachedFearSources) { gradientMap = cachedFleeMap; } else { cachedLongerPaths = preferLongerPaths; cachedImpassable = impassable2.hash64(); cachedFearSources = CrossHash.Lightning.hash64(fearSources); cachedSize = size; resetMap(); for (int g = 0; g < fearSources.length; g++) { setGoal(fearSources[g]); } if (goals.isEmpty()) return new IntVLA(path); if(scanLimit <= 0 || scanLimit < length) cachedFleeMap = scanLarge(size, impassable2, impassable2.size); else cachedFleeMap = partialScanLarge(size, scanLimit, impassable2, impassable2.size); for (int l = 0; l < gradientMap.length; l++) { gradientMap[l] *= (gradientMap[l] >= FLOOR) ? 1.0 : -preferLongerPaths; } if(scanLimit <= 0 || scanLimit < length) cachedFleeMap = scanLarge(size, impassable2, impassable2.size); else cachedFleeMap = partialScanLarge(size, scanLimit, impassable2, impassable2.size); } Adjacency adjacency = this.adjacency; int[][] toNeighbors = neighbors[1]; int currentPos = start, pt; double paidLength = 0.0; while (true) { if (frustration > 500) { path.clear(); break; } double best = gradientMap[currentPos]; rng.randomOrdering(adjacency.maxAdjacent, reuse); int choice = rng.nextIntHasty(adjacency.maxAdjacent); for (int d = 0; d < adjacency.maxAdjacent; d++) { pt = toNeighbors[reuse[d]][currentPos]; if (gradientMap[pt] < best && !path.contains(pt)) { best = gradientMap[pt]; choice = reuse[d]; } } if (best >= gradientMap[currentPos] || physicalMap[toNeighbors[choice][currentPos]] > FLOOR) { path.clear(); break; } currentPos = toNeighbors[choice][pt = currentPos]; path.add(currentPos); paidLength += adjacency.costRules.get(costMap[currentPos] | (adjacency.extractR(pt) == adjacency.extractR(currentPos) ? 0 : 0x10000)); frustration++; if (paidLength > length - 1.0) { if (onlyPassable.contains(currentPos)) { closed.put(currentPos, WALL); impassable2.add(currentPos); return findFleePathLarge(size, scanLimit, length, preferLongerPaths, impassable2, onlyPassable, start, fearSources); } break; } } frustration = 0; goals.clear(); return new IntVLA(path); } /* if (!initialized) return null; path.clear(); OrderedSet<Coord> impassable2; if (impassable == null) impassable2 = new OrderedSet<>(); else impassable2 = new OrderedSet<>(impassable); if (onlyPassable == null) onlyPassable = new OrderedSet<>(); if (fearSources == null || fearSources.length < 1) { path.clear(); return new ArrayList<>(path); } if (size == cachedSize && preferLongerPaths == cachedLongerPaths && impassable2.equals(cachedImpassable) && Arrays.equals(fearSources, cachedFearSources)) { gradientMap = cachedFleeMap; } else { cachedLongerPaths = preferLongerPaths; cachedImpassable = new OrderedSet<>(impassable2); cachedFearSources = GwtCompatibility.cloneCoords(fearSources); cachedSize = size; resetMap(); for (Coord goal : fearSources) { setGoal(goal.x, goal.y); } if (goals.isEmpty()) return new ArrayList<>(path); scan(impassable2, size); for (int x = 0; x < gradientMap.length; x++) { for (int y = 0; y < gradientMap[x].length; y++) { gradientMap[x][y] *= (gradientMap[x][y] >= FLOOR) ? 1.0 : (0.0 - preferLongerPaths); } } cachedFleeMap = scan(impassable2, size); } Coord currentPos = start; double paidLength = 0.0; while (true) { if (frustration > 500) { path.clear(); break; } double best = gradientMap[currentPos.x][currentPos.y]; final Direction[] dirs = appendDirToShuffle(rng); int choice = rng.nextInt(dirs.length); for (int d = 0; d < dirs.length; d++) { Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); if (gradientMap[pt.x][pt.y] < best) { if (dirs[choice] == Direction.NONE || !path.contains(pt)) { best = gradientMap[pt.x][pt.y]; choice = d; } } } if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { path.clear(); break; } currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); if (path.size() > 0) { Coord last = path.get(path.size() - 1); if (gradientMap[last.x][last.y] <= gradientMap[currentPos.x][currentPos.y]) break; } path.add(currentPos); frustration++; paidLength += costMap[currentPos.x][currentPos.y]; if (paidLength > length - 1.0) { if (onlyPassable.contains(currentPos)) { closed.put(currentPos.encode(), WALL); impassable2.add(currentPos); return findFleePathLarge(size, length, preferLongerPaths, impassable2, onlyPassable, start, fearSources); } break; } } frustration = 0; goals.clear(); return new ArrayList<>(path); } */ /* * Intended primarily for internal use. Needs scan() to already be called and at least one goal to already be set, * and does not restrict the length of the path or behave as if the pathfinder has allies or enemies. * <br> * This caches its result in a member field, path, which can be fetched after finding a path and will change with * each call to a pathfinding method. * * @param target the target cell * @return an ArrayList of Coord that make up the best path. Copy of path. * / public ArrayList<Coord> findPathPreScanned(Coord target) { if (!initialized || goals == null || goals.isEmpty()) return null; RNG rng2 = new StatefulRNG(new LightRNG(0xf00d)); path.clear(); Coord currentPos = target; while (true) { if (frustration > 2000) { path.clear(); break; } double best = gradientMap[currentPos.x][currentPos.y]; final Direction[] dirs = appendDirToShuffle(rng2); int choice = rng2.nextInt(dirs.length); for (int d = 0; d < dirs.length; d++) { Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); if (gradientMap[pt.x][pt.y] < best) { if (dirs[choice] == Direction.NONE || !path.contains(pt)) { best = gradientMap[pt.x][pt.y]; choice = d; } } } if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { path.clear(); break; } currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); path.add(0, currentPos); frustration++; if (gradientMap[currentPos.x][currentPos.y] == 0) break; } frustration = 0; return new ArrayList<>(path); } */ /** * A simple limited flood-fill that returns a OrderedMap of Coord keys to the Double values in the OldCustomDijkstraMap, only * calculating out to a number of steps determined by limit. This can be useful if you need many flood-fills and * don't need a large area for each, or if you want to have an effect spread to a certain number of cells away. * * @param radius the number of steps to take outward from each starting position. * @param starts a vararg group of Points to step outward from; this often will only need to be one Coord. * @return A OrderedMap of Coord keys to Double values; the starts are included in this with the value 0.0. */ public IntDoubleOrderedMap floodFill(int radius, int... starts) { if (!initialized || starts == null) return null; IntDoubleOrderedMap fill = new IntDoubleOrderedMap(); resetMap(); for (int g = 0; g < starts.length; g++) { setGoal(starts[g]); } if (goals.isEmpty()) return fill; partialScan(radius); double temp; for (int l = 0; l < gradientMap.length; l++) { temp = gradientMap[l]; if (temp < FLOOR) { fill.put(l, temp); } } goals.clear(); return fill; } public int getMappedCount() { return mappedCount; } }