001package squidpony.squidai; 002 003import squidpony.ArrayTools; 004import squidpony.Maker; 005import squidpony.squidgrid.Direction; 006import squidpony.squidgrid.LOS; 007import squidpony.squidgrid.Measurement; 008import squidpony.squidgrid.Radius; 009import squidpony.squidmath.*; 010 011import java.io.Serializable; 012import java.util.*; 013 014/** 015 * An alternative to AStarSearch when you want to fully explore a search space, or when you want a gradient floodfill. 016 * It's currently significantly faster that AStarSearch, and also supports pathfinding to the nearest of multiple 017 * goals, which is not possible with AStarSearch. This last feature enables a whole host of others, like pathfinding 018 * for creatures that can attack targets between a specified minimum and maximum distance, and there's also the 019 * standard uses of Dijkstra Maps such as finding ideal paths to run away. One unique optimization made possible by 020 * Dijkstra Maps is for when only one endpoint of a path can change in some section of a game, such as when you want to 021 * draw a path from the player's current cell to the cell the mouse is over, and while the mouse can move quickly, the 022 * player doesn't move until instructed to. This can be done very efficiently by setting the player as a goal with 023 * {@link #setGoal(Coord)}, scanning the map to find distances with {@link #scan(Collection)}, and then as long as the 024 * player's position is unchanged (and no obstacles are added/moved), you can get the path by calling 025 * {@link #findPathPreScanned(Coord)} and giving it the mouse position as a Coord. If various parts of the path can 026 * change instead of just one (such as other NPCs moving around), then you should set a goal or goals and call 027 * {@link #findPath(int, Collection, Collection, Coord, Coord...)}. The parameters for this are used in various methods 028 * in this class with only slight differences: length is the length of path that can be moved "in one go," so 1 for most 029 * roguelikes and more for most strategy games, impassable used for enemies and solid moving obstacles, onlyPassable can 030 * be null in most roguelikes but in strategy games should contain ally positions that can be moved through as long as 031 * no one stops in them, start is the NPC's starting position, and targets is an array or vararg of Coord that the NPC 032 * should pathfind toward (it could be just one Coord, with or without explicitly putting it in an array, or it could be 033 * more and the NPC will pick the closest). 034 * <br> 035 * As a bit of introduction, the article http://www.roguebasin.com/index.php?title=Dijkstra_Maps_Visualized can 036 * provide some useful information on how these work and how to visualize the information they can produce, while 037 * http://www.roguebasin.com/index.php?title=The_Incredible_Power_of_Dijkstra_Maps is an inspiring list of the 038 * various features Dijkstra Maps can enable. 039 * <br> 040 * If you can't remember how to spell this, just remember: Does It Just Know Stuff? That's Really Awesome! 041 * Created by Tommy Ettinger on 4/4/2015. 042 */ 043public class DijkstraMap implements Serializable { 044 private static final long serialVersionUID = -2456306898212944441L; 045 046 /** 047 * This affects how distance is measured on diagonal directions vs. orthogonal directions. MANHATTAN should form a 048 * diamond shape on a featureless map, while CHEBYSHEV and EUCLIDEAN will form a square. EUCLIDEAN does not affect 049 * the length of paths, though it will change the DijkstraMap's gradientMap to have many non-integer values, and 050 * that in turn will make paths this finds much more realistic and smooth (favoring orthogonal directions unless a 051 * diagonal one is a better option). 052 */ 053 public Measurement measurement = Measurement.MANHATTAN; 054 055 056 /** 057 * Stores which parts of the map are accessible and which are not. Should not be changed unless the actual physical 058 * terrain has changed. You should call initialize() with a new map instead of changing this directly. 059 */ 060 public double[][] physicalMap; 061 /** 062 * The frequently-changing values that are often the point of using this class; goals will have a value of 0, and 063 * any cells that can have a character reach a goal in n steps will have a value of n. Cells that cannot be 064 * entered because they are solid will have a very high value equal to the WALL constant in this class, and cells 065 * that cannot be entered because they cannot reach a goal will have a different very high value equal to the 066 * DARK constant in this class. 067 */ 068 public double[][] gradientMap; 069 /** 070 * This stores the entry cost multipliers for each cell; that is, a value of 1.0 is a normal, unmodified cell, but 071 * 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 072 * 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 073 * cells). Unlike the measurement field, this does affect the length of paths, as well as the numbers assigned 074 * to gradientMap during a scan. The values for walls are identical to the value used by gradientMap, that is, this 075 * class' WALL static final field. Floors, however, are never given FLOOR as a value, and default to 1.0 . 076 */ 077 public double[][] costMap; 078 079 public boolean standardCosts = true; 080 /** 081 * Height of the map. Exciting stuff. Don't change this, instead call initialize(). 082 */ 083 public int height; 084 /** 085 * Width of the map. Exciting stuff. Don't change this, instead call initialize(). 086 */ 087 public int width; 088 /** 089 * The latest path that was obtained by calling findPath(). It will not contain the value passed as a starting 090 * cell; only steps that require movement will be included, and so if the path has not been found or a valid 091 * path toward a goal is impossible, this ArrayList will be empty. 092 */ 093 public ArrayList<Coord> path; 094 095 private HashSet<Coord> impassable2, friends, tempSet; 096 097 public boolean cutShort; 098 099 /** 100 * Goals are always marked with 0. 101 */ 102 public static final double GOAL = 0.0; 103 /** 104 * Floor cells, which include any walkable cell, are marked with a high number equal to 999200.0 . 105 */ 106 public static final double FLOOR = 999200.0; 107 /** 108 * Walls, which are solid no-entry cells, are marked with a high number equal to 999500.0 . 109 */ 110 public static final double WALL = 999500.0; 111 /** 112 * This is used to mark cells that the scan couldn't reach, and these dark cells are marked with a high number 113 * equal to 999800.0 . 114 */ 115 public static final double DARK = 999800.0; 116 /** 117 * Goals that pathfinding will seek out. The Double value should almost always be 0.0 , the same as the static GOAL 118 * constant in this class. 119 */ 120 protected IntVLA goals = new IntVLA(256), fresh = new IntVLA(256); 121 122 /** 123 * The IRNG used to decide which one of multiple equally-short paths to take. You may want to give this a 124 * {@link GWTRNG} if you may target web browsers with GWT, or a {@link RNG} or {@link StatefulRNG} otherwise. 125 */ 126 public IRNG rng; 127 private int frustration; 128 public Coord[][] targetMap; 129 130 private Direction[] dirs = new Direction[9]; 131 132 private boolean initialized; 133 134 private int mappedCount; 135 136 private int blockingRequirement = 2; 137 138 /** 139 * Construct a DijkstraMap without a level to actually scan. If you use this constructor, you must call an 140 * initialize() method before using this class. 141 */ 142 public DijkstraMap() { 143 rng = new GWTRNG(); 144 path = new ArrayList<>(); 145 } 146 147 /** 148 * Construct a DijkstraMap without a level to actually scan. This constructor allows you to specify an IRNG, such as 149 * an {@link RNG}, before it is ever used in this class. If you use this constructor, you must call an initialize() 150 * method before using any other methods in the class. 151 */ 152 public DijkstraMap(IRNG random) { 153 rng = random; 154 path = new ArrayList<>(); 155 } 156 157 /** 158 * Used to construct a DijkstraMap from the output of another. 159 * 160 * @param level 161 */ 162 public DijkstraMap(final double[][] level) { 163 this(level, Measurement.MANHATTAN); 164 } 165 166 /** 167 * Used to construct a DijkstraMap from the output of another, specifying a distance calculation. 168 * 169 * @param level 170 * @param measurement 171 */ 172 public DijkstraMap(final double[][] level, Measurement measurement) { 173 rng = new GWTRNG(); 174 this.measurement = measurement; 175 path = new ArrayList<>(); 176 initialize(level); 177 } 178 179 /** 180 * Constructor meant to take a char[][] returned by DungeonBoneGen.generate(), or any other 181 * char[][] where '#' means a wall and anything else is a walkable tile. If you only have 182 * a map that uses box-drawing characters, use DungeonUtility.linesToHashes() to get a 183 * map that can be used here. 184 * 185 * @param level 186 */ 187 public DijkstraMap(final char[][] level) { 188 this(level, Measurement.MANHATTAN, new GWTRNG()); 189 } 190 191 /** 192 * Constructor meant to take a char[][] returned by DungeonBoneGen.generate(), or any other 193 * char[][] where '#' means a wall and anything else is a walkable tile. If you only have 194 * a map that uses box-drawing characters, use DungeonUtility.linesToHashes() to get a 195 * map that can be used here. Also takes an IRNG, such as an {@link RNG}, that ensures 196 * predictable path choices given otherwise identical inputs and circumstances. 197 * 198 * @param level 199 * @param rng The RNG to use for certain decisions; only affects find* methods like findPath, not scan. 200 */ 201 public DijkstraMap(final char[][] level, IRNG rng) { 202 this(level, Measurement.MANHATTAN, rng); 203 } 204 205 /** 206 * Constructor meant to take a char[][] returned by DungeonBoneGen.generate(), or any other 207 * char[][] where one char means a wall and anything else is a walkable tile. If you only have 208 * a map that uses box-drawing characters, use DungeonUtility.linesToHashes() to get a 209 * map that can be used here. You can specify the character used for walls. 210 * 211 * @param level 212 */ 213 public DijkstraMap(final char[][] level, char alternateWall) { 214 rng = new GWTRNG(); 215 path = new ArrayList<>(); 216 217 initialize(level, alternateWall); 218 } 219 220 /** 221 * Constructor meant to take a char[][] returned by DungeonBoneGen.generate(), or any other 222 * char[][] where '#' means a wall and anything else is a walkable tile. If you only have 223 * a map that uses box-drawing characters, use DungeonUtility.linesToHashes() to get a 224 * map that can be used here. This constructor specifies a distance measurement. 225 * 226 * @param level 227 * @param measurement 228 */ 229 public DijkstraMap(final char[][] level, Measurement measurement) { 230 this(level, measurement, new GWTRNG()); 231 } 232 233 /** 234 * Constructor meant to take a char[][] returned by DungeonBoneGen.generate(), or any other 235 * char[][] where '#' means a wall and anything else is a walkable tile. If you only have 236 * a map that uses box-drawing characters, use DungeonUtility.linesToHashes() to get a 237 * map that can be used here. Also takes a distance measurement and an RNG that ensures 238 * predictable path choices given otherwise identical inputs and circumstances. 239 * 240 * @param level 241 * @param rng The RNG to use for certain decisions; only affects find* methods like findPath, not scan. 242 */ 243 public DijkstraMap(final char[][] level, Measurement measurement, IRNG rng) { 244 this.rng = rng; 245 path = new ArrayList<>(); 246 this.measurement = measurement; 247 248 initialize(level); 249 } 250 251 /** 252 * Used to initialize or re-initialize a DijkstraMap that needs a new physicalMap because it either wasn't given 253 * one when it was constructed, or because the contents of the terrain have changed permanently (not if a 254 * creature moved; for that you pass the positions of creatures that block paths to scan() or findPath() ). 255 * 256 * @param level a 2D double array that should be used as the physicalMap for this DijkstraMap 257 * @return this for chaining 258 */ 259 public DijkstraMap initialize(final double[][] level) { 260 width = level.length; 261 height = level[0].length; 262 gradientMap = new double[width][height]; 263 physicalMap = new double[width][height]; 264 costMap = new double[width][height]; 265 targetMap = new Coord[width][height]; 266 for (int x = 0; x < width; x++) { 267 System.arraycopy(level[x], 0, gradientMap[x], 0, height); 268 System.arraycopy(level[x], 0, physicalMap[x], 0, height); 269 Arrays.fill(costMap[x], 1.0); 270 } 271 standardCosts = true; 272 impassable2 = new HashSet<>(32, 0.375f); 273 friends = new HashSet<>(32, 0.375f); 274 tempSet = new HashSet<>(32, 0.375f); 275 initialized = true; 276 return this; 277 } 278 279 /** 280 * Used to initialize or re-initialize a DijkstraMap that needs a new physicalMap because it either wasn't given 281 * one when it was constructed, or because the contents of the terrain have changed permanently (not if a 282 * creature moved; for that you pass the positions of creatures that block paths to scan() or findPath() ). 283 * 284 * @param level a 2D char array that this will use to establish which cells are walls ('#' as wall, others as floor) 285 * @return this for chaining 286 */ 287 public DijkstraMap initialize(final char[][] level) { 288 width = level.length; 289 height = level[0].length; 290 gradientMap = new double[width][height]; 291 physicalMap = new double[width][height]; 292 costMap = new double[width][height]; 293 targetMap = new Coord[width][height]; 294 for (int x = 0; x < width; x++) { 295 Arrays.fill(costMap[x], 1.0); 296 for (int y = 0; y < height; y++) { 297 double t = (level[x][y] == '#') ? WALL : FLOOR; 298 gradientMap[x][y] = t; 299 physicalMap[x][y] = t; 300 } 301 } 302 standardCosts = true; 303 impassable2 = new HashSet<>(32, 0.375f); 304 friends = new HashSet<>(32, 0.375f); 305 tempSet = new HashSet<>(32, 0.375f); 306 initialized = true; 307 return this; 308 } 309 310 /** 311 * Used to initialize or re-initialize a DijkstraMap that needs a new PhysicalMap because it either wasn't given 312 * one when it was constructed, or because the contents of the terrain have changed permanently (not if a 313 * creature moved; for that you pass the positions of creatures that block paths to scan() or findPath() ). This 314 * initialize() method allows you to specify an alternate wall char other than the default character, '#' . 315 * 316 * @param level a 2D char array that this will use to establish which cells are walls (alternateWall defines the wall char, everything else is floor) 317 * @param alternateWall the char to consider a wall when it appears in level 318 * @return this for chaining 319 */ 320 public DijkstraMap initialize(final char[][] level, char alternateWall) { 321 width = level.length; 322 height = level[0].length; 323 gradientMap = new double[width][height]; 324 physicalMap = new double[width][height]; 325 costMap = new double[width][height]; 326 targetMap = new Coord[width][height]; 327 for (int x = 0; x < width; x++) { 328 Arrays.fill(costMap[x], 1.0); 329 for (int y = 0; y < height; y++) { 330 double t = (level[x][y] == alternateWall) ? WALL : FLOOR; 331 gradientMap[x][y] = t; 332 physicalMap[x][y] = t; 333 } 334 } 335 standardCosts = true; 336 impassable2 = new HashSet<>(32, 0.375f); 337 friends = new HashSet<>(32, 0.375f); 338 tempSet = new HashSet<>(32, 0.375f); 339 initialized = true; 340 return this; 341 } 342 343 /** 344 * Used to initialize the entry cost modifiers for games that require variable costs to enter squares. This expects 345 * a char[][] of the same exact dimensions as the 2D array that was used to previously initialize() this 346 * DijkstraMap, treating the '#' char as a wall (impassable) and anything else as having a normal cost to enter. 347 * The costs can be accessed later by using costMap directly (which will have a valid value when this does not 348 * throw an exception), or by calling setCost(). 349 * 350 * @param level a 2D char array that uses '#' for walls 351 * @return this DijkstraMap for chaining. 352 */ 353 public DijkstraMap initializeCost(final char[][] level) { 354 if (!initialized) throw new IllegalStateException("DijkstraMap must be initialized first!"); 355 ArrayTools.fill(costMap, 1.0); 356 standardCosts = true; 357 return this; 358 } 359 360 /** 361 * Used to initialize the entry cost modifiers for games that require variable costs to enter squares. This expects 362 * a char[][] of the same exact dimensions as the 2D array that was used to previously initialize() this 363 * DijkstraMap, treating the '#' char as a wall (impassable) and anything else as having a normal cost to enter. 364 * The costs can be accessed later by using costMap directly (which will have a valid value when this does not 365 * throw an exception), or by calling setCost(). 366 * <p/> 367 * This method allows you to specify an alternate wall char other than the default character, '#' . 368 * 369 * @param level a 2D char array that uses alternateChar for walls. 370 * @param alternateWall a char to use to represent walls. 371 * @return this DijkstraMap for chaining. 372 */ 373 public DijkstraMap initializeCost(final char[][] level, char alternateWall) { 374 if (!initialized) throw new IllegalStateException("DijkstraMap must be initialized first!"); 375 ArrayTools.fill(costMap, 1.0); 376 standardCosts = true; 377 return this; 378 } 379 380 /** 381 * Used to initialize the entry cost modifiers for games that require variable costs to enter squares. This expects 382 * a double[][] of the same exact dimensions as the 2D array that was used to previously initialize() this 383 * DijkstraMap, using the exact values given in costs as the values to enter cells, even if they aren't what this 384 * class would assign normally -- walls and other impassable values should be given WALL as a value, however. 385 * The costs can be accessed later by using costMap directly (which will have a valid value when this does not 386 * throw an exception), or by calling setCost(). Causes findPath() to always explore the full map instead of 387 * stopping as soon as it finds any path, since unequal costs could make some paths cost less but be discovered 388 * later in the pathfinding process. 389 * <p/> 390 * This method should be slightly more efficient than the other initializeCost methods. 391 * 392 * @param costs a 2D double array that already has the desired cost values 393 * @return this DijkstraMap for chaining. 394 */ 395 public DijkstraMap initializeCost(final double[][] costs) { 396 if (!initialized) throw new IllegalStateException("DijkstraMap must be initialized first!"); 397 costMap = new double[width][height]; 398 for (int x = 0; x < width; x++) { 399 System.arraycopy(costs[x], 0, costMap[x], 0, height); 400 } 401 standardCosts = false; 402 return this; 403 } 404 405 /** 406 * Internally, DijkstraMap uses int primitives instead of Coord objects, but the specific encoding depends on 407 * this DijkstraMap's width and height. This method converts from a Coord to an encoded int that stores the same 408 * information, but is specific to this width and height and is somewhat more efficient to work with. 409 * @param point a Coord to find an encoded int for 410 * @return an int that encodes the given Coord for this DijkstraMap's width and height 411 */ 412 public int encode(final Coord point) 413 { 414 return width * point.y + point.x; 415 } 416 /** 417 * Internally, DijkstraMap uses int primitives instead of Coord objects, but the specific encoding depends on 418 * this DijkstraMap's width and height. This method converts from an x,y point to an encoded int that stores the 419 * same information, but is specific to this width and height and is somewhat more efficient to work with. 420 * @param x the x component of the point to find an encoded int for 421 * @param y the y component of the point to find an encoded int for 422 * @return an int that encodes the given x,y point for this DijkstraMap's width and height 423 */ 424 public int encode(final int x, final int y) 425 { 426 return width * y + x; 427 } 428 429 /** 430 * If you for some reason have one of the internally-used ints produced by {@link #encode(Coord)}, this will convert 431 * it back to a Coord if you need it as such. You may prefer using {@link #decodeX(int)} and {@link #decodeY(int)} 432 * to get the x and y components independently and without involving objects. 433 * @param encoded an encoded int specific to this DijkstraMap's height and width; see {@link #encode(Coord)} 434 * @return the Coord that represents the same x,y position that the given encoded int stores 435 */ 436 public Coord decode(final int encoded) 437 { 438 return Coord.get(encoded % width, encoded / width); 439 } 440 441 /** 442 * If you for some reason have one of the internally-used ints produced by {@link #encode(Coord)}, this will decode 443 * the x component of the point encoded in that int. This is an extremely simple method that is equivalent to the 444 * code {@code encoded % width}, where width is a public field in this class. You probably would use this method in 445 * conjunction with {@link #decodeY(int)}, or would instead use {@link #decode(int)} to get a Coord. 446 * @param encoded an encoded int specific to this DijkstraMap's height and width; see {@link #encode(Coord)} 447 * @return the x component of the position that the given encoded int stores 448 */ 449 public int decodeX(final int encoded) 450 { 451 return encoded % width; 452 } 453 454 /** 455 * If you for some reason have one of the internally-used ints produced by {@link #encode(Coord)}, this will decode 456 * the y component of the point encoded in that int. This is an extremely simple method that is equivalent to the 457 * code {@code encoded / width}, where width is a public field in this class. You probably would use this method in 458 * conjunction with {@link #decodeX(int)}, or would instead use {@link #decode(int)} to get a Coord. 459 * @param encoded an encoded int specific to this DijkstraMap's height and width; see {@link #encode(Coord)} 460 * @return the y component of the position that the given encoded int stores 461 */ 462 public int decodeY(final int encoded) 463 { 464 return encoded / width; 465 } 466 467 /** 468 * Gets the appropriate Measurement to pass to a constructor if you already have a Radius. 469 * Matches SQUARE or CUBE to CHEBYSHEV, DIAMOND or OCTAHEDRON to MANHATTAN, and CIRCLE or SPHERE to EUCLIDEAN. 470 * 471 * @param radius the Radius to find the corresponding Measurement for 472 * @return a Measurement that matches radius; SQUARE to CHEBYSHEV, DIAMOND to MANHATTAN, etc. 473 */ 474 public static Measurement findMeasurement(Radius radius) { 475 switch (radius) 476 { 477 case CUBE: 478 case SQUARE: 479 return Measurement.CHEBYSHEV; 480 case DIAMOND: 481 case OCTAHEDRON: 482 return Measurement.MANHATTAN; 483 default: 484 return Measurement.EUCLIDEAN; 485 } 486 } 487 488 /** 489 * Gets the appropriate Radius corresponding to a Measurement. 490 * Matches CHEBYSHEV to SQUARE, MANHATTAN to DIAMOND, and EUCLIDEAN to CIRCLE. 491 * <br> 492 * See also {@link Measurement#matchingRadius()} as a method on Measurement. 493 * @param measurement the Measurement to find the corresponding Radius for 494 * @return a Radius that matches measurement; CHEBYSHEV to SQUARE, MANHATTAN to DIAMOND, etc. 495 */ 496 public static Radius findRadius(Measurement measurement) { 497 switch (measurement) { 498 case CHEBYSHEV: 499 return Radius.SQUARE; 500 case EUCLIDEAN: 501 return Radius.CIRCLE; 502 default: 503 return Radius.DIAMOND; 504 } 505 } 506 507 /** 508 * Resets the gradientMap to its original value from physicalMap. 509 */ 510 public void resetMap() { 511 if (!initialized) return; 512 for (int x = 0; x < width; x++) { 513 System.arraycopy(physicalMap[x], 0, gradientMap[x], 0, height); 514 } 515 } 516 517 /** 518 * Resets the targetMap (which is only assigned in the first place if you use findTechniquePath() ). 519 */ 520 public void resetTargetMap() { 521 if (!initialized) return; 522 for (int x = 0; x < width; x++) { 523 for (int y = 0; y < height; y++) { 524 targetMap[x][y] = null; 525 } 526 } 527 } 528 529 530 /** 531 * Resets this DijkstraMap to a state with no goals, no discovered path, and no changes made to gradientMap 532 * relative to physicalMap. 533 */ 534 public void reset() { 535 resetMap(); 536 resetTargetMap(); 537 goals.clear(); 538 path.clear(); 539 fresh.clear(); 540 frustration = 0; 541 } 542 543 /** 544 * Marks a cell as a goal for pathfinding, unless the cell is a wall or unreachable area (then it does nothing). 545 * 546 * @param x 547 * @param y 548 */ 549 public void setGoal(int x, int y) { 550 if (!initialized || x < 0 || x >= width || y < 0 || y >= height) return; 551 if (physicalMap[x][y] > FLOOR) { 552 return; 553 } 554 555 goals.add(encode(x, y)); 556 gradientMap[x][y] = 0.0; 557 } 558 559 /** 560 * Marks a cell as a goal for pathfinding, unless the cell is a wall or unreachable area (then it does nothing). 561 * 562 * @param pt 563 */ 564 public void setGoal(Coord pt) { 565 if (!initialized || !pt.isWithin(width, height)) return; 566 if (physicalMap[pt.x][pt.y] > FLOOR) { 567 return; 568 } 569 570 goals.add(encode(pt)); 571 gradientMap[pt.x][pt.y] = 0.0; 572 573 } 574 /** 575 * Marks many cells as goals for pathfinding, ignoring cells in walls or unreachable areas. Possibly more efficient 576 * than a loop that calls {@link #setGoal(Coord)} over and over, since this doesn't need to do a bounds check. The 577 * GreasedRegion passed to this should have the same (or smaller) width and height as this DijkstraMap. 578 * 579 * @param pts a GreasedRegion containing "on" cells to treat as goals; should have the same width and height as this 580 */ 581 public void setGoals(GreasedRegion pts) { 582 if (!initialized || pts.width > width || pts.height > height) return; 583 for(Coord c : pts) 584 { 585 if(physicalMap[c.x][c.y] <= FLOOR) { 586 goals.add(encode(c)); 587 gradientMap[c.x][c.y] = 0.0; 588 } 589 } 590 } 591 592 /** 593 * Marks many cells as goals for pathfinding, ignoring cells in walls or unreachable areas. Simply loops through 594 * pts and calls {@link #setGoal(Coord)} on each Coord in pts. 595 * If you have a GreasedRegion, you should use it with {@link #setGoals(GreasedRegion)}, which is faster. 596 * @param pts any Iterable of Coord, which can be a List, Set, Queue, etc. of Coords to mark as goals 597 */ 598 public void setGoals(Iterable<Coord> pts) { 599 if (!initialized) return; 600 for(Coord c : pts) 601 { 602 setGoal(c); 603 } 604 } 605 /** 606 * Marks many cells as goals for pathfinding, ignoring cells in walls or unreachable areas. Simply loops through 607 * pts and calls {@link #setGoal(Coord)} on each Coord in pts. 608 * @param pts an array of Coord to mark as goals 609 */ 610 public void setGoals(Coord[] pts) { 611 if (!initialized) return; 612 for(int i = 0; i < pts.length; i++) 613 { 614 setGoal(pts[i]); 615 } 616 } 617 618 /** 619 * Marks a cell's cost for pathfinding as cost, unless the cell is a wall or unreachable area (then it always sets 620 * the cost to the value of the WALL field). 621 * 622 * @param pt 623 * @param cost 624 */ 625 public void setCost(Coord pt, double cost) { 626 if (!initialized || !pt.isWithin(width, height)) return; 627 if (physicalMap[pt.x][pt.y] > FLOOR) { 628 costMap[pt.x][pt.y] = 1.0; 629 return; 630 } 631 if(cost != 1.0) 632 standardCosts = false; 633 costMap[pt.x][pt.y] = cost; 634 } 635 636 /** 637 * Marks a cell's cost for pathfinding as cost, unless the cell is a wall or unreachable area (then it always sets 638 * the cost to the value of the WALL field). 639 * 640 * @param x 641 * @param y 642 * @param cost 643 */ 644 public void setCost(int x, int y, double cost) { 645 if (!initialized || x < 0 || x >= width || y < 0 || y >= height) return; 646 if (physicalMap[x][y] > FLOOR) { 647 costMap[x][y] = 1.0; 648 return; 649 } 650 if(cost != 1.0) 651 standardCosts = false; 652 costMap[x][y] = cost; 653 } 654 655 /** 656 * Marks a specific cell in gradientMap as completely impossible to enter. 657 * 658 * @param x 659 * @param y 660 */ 661 public void setOccupied(int x, int y) { 662 if (!initialized || x < 0 || x >= width || y < 0 || y >= height) return; 663 gradientMap[x][y] = WALL; 664 } 665 666 /** 667 * Reverts a cell to the value stored in the original state of the level as known by physicalMap. 668 * 669 * @param x 670 * @param y 671 */ 672 public void resetCell(int x, int y) { 673 if (!initialized || x < 0 || x >= width || y < 0 || y >= height) return; 674 gradientMap[x][y] = physicalMap[x][y]; 675 } 676 677 /** 678 * Reverts a cell to the value stored in the original state of the level as known by physicalMap. 679 * 680 * @param pt 681 */ 682 public void resetCell(Coord pt) { 683 if (!initialized || !pt.isWithin(width, height)) return; 684 gradientMap[pt.x][pt.y] = physicalMap[pt.x][pt.y]; 685 } 686 687 /** 688 * Used to remove all goals and undo any changes to gradientMap made by having a goal present. 689 */ 690 public void clearGoals() { 691 if (!initialized) 692 return; 693 int sz = goals.size, t; 694 for (int i = 0; i < sz; i++) { 695 resetCell(decodeX(t = goals.pop()), decodeY(t)); 696 } 697 } 698 699 private void setFresh(final int x, final int y, double counter) { 700 if (x < 0 || x >= width || y < 0 || y >= height || gradientMap[x][y] < counter) 701 return; 702 gradientMap[x][y] = counter; 703 fresh.add(encode(x, y)); 704 } 705 706 private void setFresh(final Coord pt, double counter) { 707 if (!pt.isWithin(width, height) || gradientMap[pt.x][pt.y] < counter) 708 return; 709 gradientMap[pt.x][pt.y] = counter; 710 fresh.add(encode(pt)); 711 } 712 713 /** 714 * Recalculate the Dijkstra map and return it. Cells that were marked as goals with setGoal will have 715 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 716 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 717 * which will have a value defined by the WALL constant in this class, and areas that the scan was 718 * unable to reach, which will have a value defined by the DARK constant in this class (typically, 719 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 720 * current measurement. The result is stored in the {@link #gradientMap} field and a copy is returned. 721 * 722 * @return A 2D double[width][height] using the width and height of what this knows about the physical map. 723 */ 724 public double[][] scan() { 725 return scan(null); 726 } 727 728 /** 729 * Recalculate the Dijkstra map and return it. Cells that were marked as goals with setGoal will have 730 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 731 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 732 * which will have a value defined by the WALL constant in this class, and areas that the scan was 733 * unable to reach, which will have a value defined by the DARK constant in this class (typically, 734 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 735 * current measurement. The result is stored in the {@link #gradientMap} field and a copy is returned. 736 * 737 * @param impassable A Collection of Coord keys representing the locations of enemies or other moving obstacles to a 738 * path that cannot be moved through; this can be null if there are no such obstacles. 739 * @return A 2D double[width][height] using the width and height of what this knows about the physical map. 740 */ 741 public double[][] scan(final Collection<Coord> impassable) { 742 scan(null, impassable); 743 double[][] gradientClone = new double[width][height]; 744 for (int x = 0; x < width; x++) { 745 for (int y = 0; y < height; y++) { 746 if (gradientMap[x][y] == FLOOR) { 747 gradientMap[x][y] = DARK; 748 } 749 } 750 System.arraycopy(gradientMap[x], 0, gradientClone[x], 0, height); 751 } 752 753 return gradientClone; 754 } 755 756 /** 757 * Recalculate the Dijkstra map and return it. Cells that were marked as goals with setGoal will have 758 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 759 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 760 * which will have a value defined by the WALL constant in this class, and areas that the scan was 761 * unable to reach, which will have a value defined by the DARK constant in this class (typically, 762 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 763 * current measurement. The result is stored in the {@link #gradientMap} field, and nothing is returned. 764 * If you want the data returned, you can use {@link #scan(Collection)} (which calls this method with 765 * null for the start parameter, then modifies the gradientMap field and returns a copy), or you can 766 * just retrieve the gradientMap (maybe copying it; {@link squidpony.ArrayTools#copy(double[][])} is a 767 * convenient option for copying a 2D double array). If start is non-null, which is usually used when 768 * finding a single path, then cells that didn't need to be explored (because they were further than the 769 * path needed to go from start to goal) will have the value {@link #FLOOR}. You may wish to assign a 770 * different value to these cells in some cases (especially if start is null, which means any cells that 771 * are still FLOOR could not be reached from any goal), and the overloads of scan that return 2D double 772 * arrays do change FLOOR to {@link #DARK}, which is usually treated similarly to {@link #WALL}. 773 * 774 * @param start a Coord representing the location of the pathfinder; may be null, which has this scan the whole map 775 * @param impassable A Collection of Coord keys representing the locations of enemies or other moving obstacles to a 776 * path that cannot be moved through; this can be null if there are no such obstacles. 777 */ 778 public void scan(final Coord start, final Collection<Coord> impassable) { 779 scan(start, impassable, false); 780 } 781 782 /** 783 * Recalculate the Dijkstra map and return it. Cells in {@link #gradientMap} that had the lowest value 784 * will be treated as goals if {@code nonZeroOptimum} is true; otherwise, only cells marked as goals with 785 * {@link #setGoal(Coord)} will be considered goals and some overhead will be saved. The cells adjacent 786 * to goals will have a value of 1, and cells progressively further from goals will have a value equal to 787 * the distance from the nearest goal. The exceptions are walls, which will have a value defined by the 788 * {@link #WALL} constant in this class, and areas that the scan was unable to reach, which will have a 789 * value defined by the {@link #DARK} constant in this class (typically, these areas should not be used to 790 * place NPCs or items and should be filled with walls). This uses the current {@link #measurement}. The 791 * result is stored in the {@link #gradientMap} field, and nothing is returned. If you want the data 792 * returned, you can use {@link #scan(Collection)} (which calls this method with null for the start 793 * parameter, then modifies the gradientMap field and returns a copy), or you can 794 * just retrieve the gradientMap (maybe copying it; {@link squidpony.ArrayTools#copy(double[][])} is a 795 * convenient option for copying a 2D double array). If start is non-null, which is usually used when 796 * finding a single path, then cells that didn't need to be explored (because they were further than the 797 * path needed to go from start to goal) will have the value {@link #FLOOR}. You may wish to assign a 798 * different value to these cells in some cases (especially if start is null, which means any cells that 799 * are still FLOOR could not be reached from any goal), and the overloads of scan that return 2D double 800 * arrays do change FLOOR to {@link #DARK}, which is usually treated similarly to {@link #WALL}. 801 * 802 * @param start a Coord representing the location of the pathfinder; may be null, which has this scan the whole map 803 * @param impassable A Collection of Coord keys representing the locations of enemies or other moving obstacles to a 804 * path that cannot be moved through; this can be null if there are no such obstacles. 805 * @param nonZeroOptimum if the cell to pathfind toward should have a value of {@link #GOAL} (0.0), this should be 806 * false; if it should have a different value or if you don't know, it should be true 807 */ 808 public void scan(final Coord start, final Collection<Coord> impassable, final boolean nonZeroOptimum) { 809 810 if (!initialized) return; 811 if (impassable != null && !impassable.isEmpty()) { 812 for (Coord pt : impassable) { 813 if(pt != null && pt.isWithin(width, height)) 814 gradientMap[pt.x][pt.y] = WALL; 815 } 816 } 817 int dec, adjX, adjY, cen, cenX, cenY; 818 fresh.clear(); 819 fresh.addAll(goals); 820 for (int i = 0; i < goals.size; i++) { 821 dec = goals.get(i); 822 gradientMap[decodeX(dec)][decodeY(dec)] = GOAL; 823 } 824 double cs, dist; 825 if(nonZeroOptimum) { 826 double currentLowest = 999000.0; 827 for (int x = 0; x < width; x++) { 828 for (int y = 0; y < height; y++) { 829 if (gradientMap[x][y] <= FLOOR) { 830 if (gradientMap[x][y] < currentLowest) { 831 currentLowest = gradientMap[x][y]; 832 fresh.clear(); 833 fresh.add(encode(x, y)); 834 } else if (gradientMap[x][y] == currentLowest) { 835 fresh.add(encode(x, y)); 836 } 837 } 838 } 839 } 840 } 841 int fsz, numAssigned = fresh.size; 842 mappedCount = goals.size; 843 Direction[] moveDirs = (measurement == Measurement.MANHATTAN) ? Direction.CARDINALS : Direction.OUTWARDS; 844 845 while (numAssigned > 0) { 846 numAssigned = 0; 847 fsz = fresh.size; 848 for (int ci = fsz-1; ci >= 0; ci--) { 849 cen = fresh.removeIndex(ci); 850 cenX = decodeX(cen); 851 cenY = decodeY(cen); 852 dist = gradientMap[cenX][cenY]; 853 854 for (int d = 0; d < moveDirs.length; d++) { 855 adjX = cenX + moveDirs[d].deltaX; 856 adjY = cenY + moveDirs[d].deltaY; 857 if (adjX < 0 || adjY < 0 || width <= adjX || height <= adjY) 858 /* Outside the map */ 859 continue; 860 if(d >= 4 && blockingRequirement > 0) // diagonal 861 { 862 if((gradientMap[adjX][cenY] > FLOOR ? 1 : 0) 863 + (gradientMap[cenX][adjY] > FLOOR ? 1 : 0) 864 >= blockingRequirement) 865 { 866 continue; 867 } 868 } 869 double h = measurement.heuristic(moveDirs[d]); 870 cs = dist + h * costMap[adjX][adjY]; 871 if (gradientMap[adjX][adjY] <= FLOOR && cs < gradientMap[adjX][adjY]) { 872 setFresh(adjX, adjY, cs); 873 ++numAssigned; 874 ++mappedCount; 875 if(start != null && start.x == adjX && start.y == adjY && standardCosts) 876 { 877 if (impassable != null && !impassable.isEmpty()) { 878 for (Coord pt : impassable) { 879 if(pt != null && pt.isWithin(width, height)) 880 gradientMap[pt.x][pt.y] = physicalMap[pt.x][pt.y]; 881 } 882 } 883 return; 884 } 885 } 886 } 887 } 888 } 889 if (impassable != null && !impassable.isEmpty()) { 890 for (Coord pt : impassable) { 891 if(pt != null && pt.isWithin(width, height)) 892 gradientMap[pt.x][pt.y] = physicalMap[pt.x][pt.y]; 893 } 894 } 895 } 896 897 /** 898 * Recalculate the Dijkstra map up to a limit and return it. Cells that were marked as goals with setGoal will have 899 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 900 * from goals will have a value equal to the distance from the nearest goal. If a cell would take more steps to 901 * reach than the given limit, it will have a value of DARK if it was passable instead of the distance. The 902 * exceptions are walls, which will have a value defined by the WALL constant in this class, and areas that the scan 903 * was unable to reach, which will have a value defined by the DARK constant in this class. This uses the 904 * current measurement. The result is stored in the {@link #gradientMap} field and a copy is returned. 905 * 906 * @param limit The maximum number of steps to scan outward from a goal. 907 * @return A 2D double[width][height] using the width and height of what this knows about the physical map. 908 */ 909 public double[][] partialScan(final int limit) { 910 return partialScan(limit, null); 911 } 912 913 /** 914 * Recalculate the Dijkstra map up to a limit and return it. Cells that were marked as goals with setGoal will have 915 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 916 * from goals will have a value equal to the distance from the nearest goal. If a cell would take more steps to 917 * reach than the given limit, it will have a value of DARK if it was passable instead of the distance. The 918 * exceptions are walls, which will have a value defined by the WALL constant in this class, and areas that the scan 919 * was unable to reach, which will have a value defined by the DARK constant in this class. This uses the 920 * current measurement. The result is stored in the {@link #gradientMap} field and a copy is returned. 921 * 922 * @param limit The maximum number of steps to scan outward from a goal. 923 * @param impassable A Collection of Coord keys representing the locations of enemies or other moving obstacles to a 924 * path that cannot be moved through; this can be null if there are no such obstacles. 925 * @return A 2D double[width][height] using the width and height of what this knows about the physical map. 926 */ 927 public double[][] partialScan(final int limit, final Collection<Coord> impassable) { 928 partialScan(null, limit, impassable); 929 double[][] gradientClone = new double[width][height]; 930 for (int x = 0; x < width; x++) { 931 for (int y = 0; y < height; y++) { 932 if (gradientMap[x][y] == FLOOR) { 933 gradientMap[x][y] = DARK; 934 } 935 } 936 System.arraycopy(gradientMap[x], 0, gradientClone[x], 0, height); 937 } 938 939 return gradientClone; 940 } 941 942 /** 943 * Recalculate the Dijkstra map up to a limit and return it. Cells that were marked as goals with setGoal will have 944 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 945 * from goals will have a value equal to the distance from the nearest goal. If a cell would take more steps to 946 * reach than the given limit, or if it was otherwise unreachable, it will have a value of {@link #FLOOR} or greater 947 * if it was passable instead of the distance. The exceptions are walls, which will have a value defined by the 948 * {@link #WALL} constant in this class. This uses the current {@link #measurement}. The result is stored in the 949 * {@link #gradientMap} field, and nothing is returned.If you want the data returned, you can use 950 * {@link #partialScan(int, Collection)} (which calls this method with null for the start parameter, then modifies 951 * the gradientMap field and returns a copy), or you can just retrieve the gradientMap (maybe copying it; 952 * {@link squidpony.ArrayTools#copy(double[][])} is a convenient option for copying a 2D double array). 953 * <br> 954 * If start is non-null, which is usually used when finding a single path, then cells that didn't need to be 955 * explored (because they were further than the path needed to go from start to goal) will have the value 956 * {@link #FLOOR}. You may wish to assign a different value to these cells in some cases (especially if start is 957 * null, which means any cells that are still FLOOR could not be reached from any goal), and the overloads of 958 * partialScan that return 2D double arrays do change FLOOR to {@link #DARK}, which is usually treated similarly to 959 * {@link #WALL}. 960 * 961 * @param start a Coord representing the location of the pathfinder; may be null to have this scan more of the map 962 * @param limit The maximum number of steps to scan outward from a goal. 963 * @param impassable A Collection of Coord keys representing the locations of enemies or other moving obstacles to a 964 * path that cannot be moved through; this can be null if there are no such obstacles. 965 */ 966 public void partialScan(final Coord start, final int limit, final Collection<Coord> impassable) { 967 partialScan(start, limit, impassable, false); 968 } 969 970 /** 971 * Recalculate the Dijkstra map up to a limit and return it. Cells in {@link #gradientMap} that had the lowest value 972 * will be treated as goals if {@code nonZeroOptimum} is true; otherwise, only cells marked as goals with 973 * {@link #setGoal(Coord)} will be considered goals and some overhead will be saved. If a cell would take more steps 974 * to reach than the given limit, or if it was otherwise unreachable, it will have a value of {@link #FLOOR} or 975 * greater if it was passable instead of the distance. The exceptions are walls, which will have a value defined by 976 * the {@link #WALL} constant in this class. This uses the current {@link #measurement}. The result is stored in the 977 * {@link #gradientMap} field, and nothing is returned.If you want the data returned, you can use 978 * {@link #partialScan(int, Collection)} (which calls this method with null for the start parameter, then modifies 979 * the gradientMap field and returns a copy), or you can just retrieve the gradientMap (maybe copying it; 980 * {@link squidpony.ArrayTools#copy(double[][])} is a convenient option for copying a 2D double array). 981 * <br> 982 * If start is non-null, which is usually used when finding a single path, then cells that didn't need to be 983 * explored (because they were further than the path needed to go from start to goal) will have the value 984 * {@link #FLOOR}. You may wish to assign a different value to these cells in some cases (especially if start is 985 * null, which means any cells that are still FLOOR could not be reached from any goal), and the overloads of 986 * partialScan that return 2D double arrays do change FLOOR to {@link #DARK}, which is usually treated similarly to 987 * {@link #WALL}. 988 * 989 * @param start a Coord representing the location of the pathfinder; may be null to have this scan more of the map 990 * @param limit The maximum number of steps to scan outward from a goal. 991 * @param impassable A Collection of Coord keys representing the locations of enemies or other moving obstacles to a 992 * path that cannot be moved through; this can be null if there are no such obstacles. 993 * @param nonZeroOptimum if the cell to pathfind toward should have a value of {@link #GOAL} (0.0), this should be 994 * false; if it should have a different value or if you don't know, it should be true 995 */ 996 public void partialScan(final Coord start, final int limit, final Collection<Coord> impassable, final boolean nonZeroOptimum) { 997 998 if (!initialized || limit <= 0) return; 999 if (impassable != null && !impassable.isEmpty()) { 1000 for (Coord pt : impassable) { 1001 if(pt != null && pt.isWithin(width, height)) 1002 gradientMap[pt.x][pt.y] = WALL; 1003 } 1004 } 1005 int dec, adjX, adjY, cen, cenX, cenY; 1006 fresh.clear(); 1007 fresh.addAll(goals); 1008 for (int i = 0; i < goals.size; i++) { 1009 //if (closed.containsKey(entry.getIntKey())) 1010 // continue; 1011 // closed.remove(entry.getIntKey()); 1012 dec = goals.get(i); 1013 gradientMap[decodeX(dec)][decodeY(dec)] = GOAL; 1014 } 1015 double cs, dist; 1016 if(nonZeroOptimum) { 1017 double currentLowest = 999000; 1018 if (start == null) { 1019 for (int x = 0; x < width; x++) { 1020 for (int y = 0; y < height; y++) { 1021 if (gradientMap[x][y] <= FLOOR) { 1022 if (gradientMap[x][y] < currentLowest) { 1023 currentLowest = gradientMap[x][y]; 1024 fresh.clear(); 1025 fresh.add(encode(x, y)); 1026 } else if (gradientMap[x][y] == currentLowest) { 1027 fresh.add(encode(x, y)); 1028 } 1029 } 1030 } 1031 } 1032 } else { 1033 final int x0 = Math.max(0, start.x - limit), x1 = Math.min(start.x + limit + 1, width), 1034 y0 = Math.max(0, start.y - limit), y1 = Math.min(start.y + limit + 1, height); 1035 for (int x = x0; x < x1; x++) { 1036 for (int y = y0; y < y1; y++) { 1037 if (gradientMap[x][y] <= FLOOR) { 1038 if (gradientMap[x][y] < currentLowest) { 1039 currentLowest = gradientMap[x][y]; 1040 fresh.clear(); 1041 fresh.add(encode(x, y)); 1042 } else if (gradientMap[x][y] == currentLowest) { 1043 fresh.add(encode(x, y)); 1044 } 1045 } 1046 } 1047 } 1048 } 1049 } 1050 int fsz, numAssigned = fresh.size; 1051 mappedCount = goals.size; 1052 Direction[] moveDirs = (measurement == Measurement.MANHATTAN) ? Direction.CARDINALS : Direction.OUTWARDS; 1053 1054 int iter = 0; 1055 while (numAssigned > 0 && iter++ < limit) { 1056 numAssigned = 0; 1057 fsz = fresh.size; 1058 for (int ci = fsz-1; ci >= 0; ci--) { 1059 cen = fresh.removeIndex(ci); 1060 cenX = decodeX(cen); 1061 cenY = decodeY(cen); 1062 dist = gradientMap[cenX][cenY]; 1063 1064 for (int d = 0; d < moveDirs.length; d++) { 1065 adjX = cenX + moveDirs[d].deltaX; 1066 adjY = cenY + moveDirs[d].deltaY; 1067 if (adjX < 0 || adjY < 0 || width <= adjX || height <= adjY) 1068 /* Outside the map */ 1069 continue; 1070 if(d >= 4 && blockingRequirement > 0) // diagonal 1071 { 1072 if((gradientMap[adjX][cenY] > FLOOR ? 1 : 0) 1073 + (gradientMap[cenX][adjY] > FLOOR ? 1 : 0) 1074 >= blockingRequirement) 1075 { 1076 continue; 1077 } 1078 } 1079 double h = measurement.heuristic(moveDirs[d]); 1080 cs = dist + h * costMap[adjX][adjY]; 1081 if (gradientMap[adjX][adjY] <= FLOOR && cs < gradientMap[adjX][adjY]) { 1082 setFresh(adjX, adjY, cs); 1083 ++numAssigned; 1084 ++mappedCount; 1085 if(start != null && start.x == adjX && start.y == adjY && standardCosts) 1086 { 1087 if (impassable != null && !impassable.isEmpty()) { 1088 for (Coord pt : impassable) { 1089 if(pt != null && pt.isWithin(width, height)) 1090 gradientMap[pt.x][pt.y] = physicalMap[pt.x][pt.y]; 1091 } 1092 } 1093 return; 1094 } 1095 } 1096 } 1097 } 1098 } 1099 if (impassable != null && !impassable.isEmpty()) { 1100 for (Coord pt : impassable) { 1101 if(pt != null && pt.isWithin(width, height)) 1102 gradientMap[pt.x][pt.y] = physicalMap[pt.x][pt.y]; 1103 } 1104 } 1105 } 1106 1107 /** 1108 * Recalculate the Dijkstra map until it reaches a Coord in targets, then returns the first target found. 1109 * This uses the current measurement. 1110 * 1111 * @param start the cell to use as the origin for finding the nearest target 1112 * @param targets the Coords that this is trying to find; it will stop once it finds one 1113 * @return the Coord that it found first. 1114 */ 1115 public Coord findNearest(Coord start, Collection<Coord> targets) { 1116 if (!initialized) return null; 1117 if (targets == null) 1118 return null; 1119 if (targets.contains(start)) 1120 return start; 1121 resetMap(); 1122 Coord start2 = start; 1123 int xShift = width / 6, yShift = height / 6; 1124 while (physicalMap[start.x][start.y] >= WALL && frustration < 50) { 1125 start2 = Coord.get(Math.min(Math.max(1, start.x + rng.nextInt(1 + xShift * 2) - xShift), width - 2), 1126 Math.min(Math.max(1, start.y + rng.nextInt(1 + yShift * 2) - yShift), height - 2)); 1127 } 1128 gradientMap[start2.x][start2.y] = 0.0; 1129 int adjX, adjY, cen, cenX, cenY; 1130 double cs, dist; 1131 Coord adj; 1132 fresh.clear(); 1133 fresh.add(encode(start2)); 1134 int fsz, numAssigned = 1; 1135 mappedCount = 1; 1136 Direction[] moveDirs = (measurement == Measurement.MANHATTAN) ? Direction.CARDINALS : Direction.OUTWARDS; 1137 1138 while (numAssigned > 0) { 1139 numAssigned = 0; 1140 fsz = fresh.size; 1141 for (int ci = fsz-1; ci >= 0; ci--) { 1142 cen = fresh.removeIndex(ci); 1143 cenX = decodeX(cen); 1144 cenY = decodeY(cen); 1145 dist = gradientMap[cenX][cenY]; 1146 1147 for (int d = 0; d < moveDirs.length; d++) { 1148 adjX = cenX + moveDirs[d].deltaX; 1149 adjY = cenY + moveDirs[d].deltaY; 1150 if (adjX < 0 || adjY < 0 || width <= adjX || height <= adjY) 1151 /* Outside the map */ 1152 continue; 1153 if(d >= 4 && blockingRequirement > 0) // diagonal 1154 { 1155 if((gradientMap[adjX][cenY] > FLOOR ? 1 : 0) 1156 + (gradientMap[cenX][adjY] > FLOOR ? 1 : 0) 1157 >= blockingRequirement) 1158 { 1159 continue; 1160 } 1161 } 1162 double h = measurement.heuristic(moveDirs[d]); 1163 cs = dist + h * costMap[adjX][adjY]; 1164 if (gradientMap[adjX][adjY] <= FLOOR && cs < gradientMap[adjX][adjY]) { 1165 ++mappedCount; 1166 if (targets.contains(adj = Coord.get(adjX, adjY))) { 1167 fresh.clear(); 1168 return adj; 1169 } 1170 setFresh(adjX, adjY, cs); 1171 ++numAssigned; 1172 } 1173 } 1174 } 1175 } 1176 1177 return null; 1178 } 1179 1180 /** 1181 * Recalculate the Dijkstra map until it reaches a Coord in targets, then returns the first target found. 1182 * This uses the current measurement. 1183 * 1184 * @param start the cell to use as the origin for finding the nearest target 1185 * @param targets the Coords that this is trying to find; it will stop once it finds one 1186 * @return the Coord that it found first. 1187 */ 1188 public Coord findNearest(Coord start, Coord... targets) { 1189 return findNearest(start, Maker.makeHS(targets)); 1190 } 1191 1192 /** 1193 * If you have a target or group of targets you want to pathfind to without scanning the full map, this can be good. 1194 * It may find sub-optimal paths in the presence of costs to move into cells. It is useful when you want to move in 1195 * a straight line to a known nearby goal. 1196 * 1197 * @param start your starting location 1198 * @param targets an array or vararg of Coords to pathfind to the nearest of 1199 * @return an ArrayList of Coord that goes from a cell adjacent to start and goes to one of the targets. Copy of path. 1200 */ 1201 public ArrayList<Coord> findShortcutPath(Coord start, Coord... targets) { 1202 if (targets.length == 0) { 1203 cutShort = true; 1204 path.clear(); 1205 return new ArrayList<>(path); 1206 } 1207 Coord currentPos = findNearest(start, targets); 1208 while (true) { 1209 if (frustration > 500) { 1210 path.clear(); 1211 break; 1212 } 1213 double best = gradientMap[currentPos.x][currentPos.y]; 1214 appendDirToShuffle(rng); 1215 int choice = 0; 1216 1217 for (int d = 0; d < measurement.directionCount() + 1; d++) { 1218 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 1219 if(!pt.isWithin(width, height)) 1220 continue; 1221 if (gradientMap[pt.x][pt.y] < best) { 1222 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 1223 best = gradientMap[pt.x][pt.y]; 1224 choice = d; 1225 } 1226 } 1227 } 1228 1229 if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 1230 cutShort = true; 1231 frustration = 0; 1232 return new ArrayList<>(path); 1233 } 1234 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 1235 if (gradientMap[currentPos.x][currentPos.y] == 0) 1236 break; 1237 path.add(currentPos); 1238 frustration++; 1239 } 1240 frustration = 0; 1241 cutShort = false; 1242 Collections.reverse(path); 1243 return new ArrayList<>(path); 1244 1245 } 1246 1247 /** 1248 * Recalculate the Dijkstra map until it reaches a Coord in targets, then returns the first several targets found, 1249 * up to limit or less if the map is fully searched without finding enough. 1250 * This uses the current measurement. 1251 * 1252 * @param start the cell to use as the origin for finding the nearest targets 1253 * @param limit the maximum number of targets to find before returning 1254 * @param targets the Coords that this is trying to find; it will stop once it finds enough (based on limit) 1255 * @return the Coords that it found first. 1256 */ 1257 public ArrayList<Coord> findNearestMultiple(Coord start, int limit, Collection<Coord> targets) { 1258 if (!initialized) return null; 1259 ArrayList<Coord> found = new ArrayList<>(limit); 1260 if (targets == null) 1261 return found; 1262 if (targets.contains(start)) 1263 return found; 1264 resetMap(); 1265 Coord start2 = start; 1266 int xShift = width / 6, yShift = height / 6; 1267 while (physicalMap[start.x][start.y] >= WALL && frustration < 50) { 1268 start2 = Coord.get(Math.min(Math.max(1, start.x + rng.nextInt(1 + xShift * 2) - xShift), width - 2), 1269 Math.min(Math.max(1, start.y + rng.nextInt(1 + yShift * 2) - yShift), height - 2)); 1270 } 1271 gradientMap[start2.x][start2.y] = 0.0; 1272 int adjX, adjY, cen, cenX, cenY; 1273 double cs, dist; 1274 Coord adj; 1275 fresh.clear(); 1276 fresh.add(encode(start2)); 1277 int fsz, numAssigned = 1; 1278 mappedCount = 1; 1279 Direction[] moveDirs = (measurement == Measurement.MANHATTAN) ? Direction.CARDINALS : Direction.OUTWARDS; 1280 1281 while (numAssigned > 0) { 1282 numAssigned = 0; 1283 fsz = fresh.size; 1284 for (int ci = fsz-1; ci >= 0; ci--) { 1285 cen = fresh.removeIndex(ci); 1286 cenX = decodeX(cen); 1287 cenY = decodeY(cen); 1288 dist = gradientMap[cenX][cenY]; 1289 1290 for (int d = 0; d < moveDirs.length; d++) { 1291 adjX = cenX + moveDirs[d].deltaX; 1292 adjY = cenY + moveDirs[d].deltaY; 1293 if (adjX < 0 || adjY < 0 || width <= adjX || height <= adjY) 1294 /* Outside the map */ 1295 continue; 1296 if(d >= 4 && blockingRequirement > 0) // diagonal 1297 { 1298 if((gradientMap[adjX][cenY] > FLOOR ? 1 : 0) 1299 + (gradientMap[cenX][adjY] > FLOOR ? 1 : 0) 1300 >= blockingRequirement) 1301 { 1302 continue; 1303 } 1304 } 1305 double h = measurement.heuristic(moveDirs[d]); 1306 cs = dist + h * costMap[adjX][adjY]; 1307 if (gradientMap[adjX][adjY] <= FLOOR && cs < gradientMap[adjX][adjY]) { 1308 ++mappedCount; 1309 if (targets.contains(adj = Coord.get(adjX, adjY))) { 1310 found.add(adj); 1311 if (found.size() >= limit) { 1312 fresh.clear(); 1313 return found; 1314 } 1315 } 1316 setFresh(adjX, adjY, cs); 1317 ++numAssigned; 1318 } 1319 } 1320 } 1321 } 1322 return found; 1323 } 1324 1325 /** 1326 * Recalculate the Dijkstra map for a creature that is potentially larger than 1x1 cell and return it. The value of 1327 * a cell in the returned Dijkstra map assumes that a creature is square, with a side length equal to the passed 1328 * size, that its minimum-x, minimum-y cell is the starting cell, and that any cell with a distance number 1329 * represents the distance for the creature's minimum-x, minimum-y cell to reach it. Cells that cannot be entered 1330 * by the minimum-x, minimum-y cell because of sizing (such as a floor cell next to a maximum-x and/or maximum-y 1331 * wall if size is > 1) will be marked as DARK. Cells that were marked as goals with setGoal will have 1332 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 1333 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 1334 * which will have a value defined by the WALL constant in this class, and areas that the scan was 1335 * unable to reach, which will have a value defined by the DARK constant in this class. (typically, 1336 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 1337 * current measurement. The result is stored in the {@link #gradientMap} field and a copy is returned. 1338 * 1339 * @param impassable A Collection of Coord keys representing the locations of enemies or other moving obstacles to a 1340 * path that cannot be moved through; this can be null if there are no such obstacles. 1341 * @param size The length of one side of a square creature using this to find a path, i.e. 2 for a 2x2 cell 1342 * creature. Non-square creatures are not supported because turning is really hard. 1343 * @return A 2D double[width][height] using the width and height of what this knows about the physical map. 1344 */ 1345 public double[][] scan(final Collection<Coord> impassable, final int size) { 1346 scan(null, impassable, size); 1347 double[][] gradientClone = new double[width][height]; 1348 for (int x = 0; x < width; x++) { 1349 for (int y = 0; y < height; y++) { 1350 if (gradientMap[x][y] == FLOOR) { 1351 gradientMap[x][y] = DARK; 1352 } 1353 } 1354 System.arraycopy(gradientMap[x], 0, gradientClone[x], 0, height); 1355 } 1356 return gradientClone; 1357 1358 } 1359 1360 /** 1361 * Recalculate the Dijkstra map for a creature that is potentially larger than 1x1 cell and return it. The value of 1362 * a cell in the returned Dijkstra map assumes that a creature is square, with a side length equal to the passed 1363 * size, that its minimum-x, minimum-y cell is the starting cell, and that any cell with a distance number 1364 * represents the distance for the creature's minimum-x, minimum-y cell to reach it. Cells that cannot be entered 1365 * by the minimum-x, minimum-y cell because of sizing (such as a floor cell next to a maximum-x and/or maximum-y 1366 * wall if size is > 1) will be marked as DARK. Cells that were marked as goals with setGoal will have 1367 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 1368 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 1369 * which will have a value defined by the WALL constant in this class, and areas that the scan was 1370 * unable to reach, which will have a value defined by the DARK constant in this class. (typically, 1371 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 1372 * current measurement. The result is stored in the {@link #gradientMap} field, and nothing is returned. 1373 * If you want the data returned, you can use {@link #scan(Collection, int)} (which calls this method with 1374 * null for the start parameter, then modifies the gradientMap field and returns a copy), or you can 1375 * just retrieve the gradientMap (maybe copying it; {@link squidpony.ArrayTools#copy(double[][])} is a 1376 * convenient option for copying a 2D double array). If start is non-null, which is usually used when 1377 * finding a single path, then cells that didn't need to be explored (because they were further than the 1378 * path needed to go from start to goal) will have the value {@link #FLOOR}. You may wish to assign a 1379 * different value to these cells in some cases (especially if start is null, which means any cells that 1380 * are still FLOOR could not be reached from any goal), and the overloads of scan that return 2D double 1381 * arrays do change FLOOR to {@link #DARK}, which is usually treated similarly to {@link #WALL}. 1382 * 1383 * @param impassable A Collection of Coord keys representing the locations of enemies or other moving obstacles to a 1384 * path that cannot be moved through; this can be null if there are no such obstacles. 1385 * @param size The length of one side of a square creature using this to find a path, i.e. 2 for a 2x2 cell 1386 * creature. Non-square creatures are not supported because turning is really hard. 1387 */ 1388 public void scan(final Coord start, final Collection<Coord> impassable, final int size) { 1389 1390 if (!initialized) return; 1391 double[][] gradientClone = ArrayTools.copy(gradientMap); 1392 if (impassable != null && !impassable.isEmpty()) { 1393 for (Coord pt : impassable) { 1394 if(pt != null && pt.isWithin(width, height)) 1395 gradientMap[pt.x][pt.y] = WALL; 1396 } 1397 } 1398 for (int xx = size; xx < width; xx++) { 1399 for (int yy = size; yy < height; yy++) { 1400 if(gradientMap[xx][yy] > FLOOR) { 1401 for (int xs = xx, xi = 0; xi < size && xs >= 0; xs--, xi++) { 1402 for (int ys = yy, yi = 0; yi < size && ys >= 0; ys--, yi++) { 1403 gradientClone[xs][ys] = WALL; 1404 } 1405 } 1406 } 1407 } 1408 } 1409 int dec, adjX, adjY, cen, cenX, cenY; 1410 1411 PER_GOAL: 1412 for (int i = 0; i < goals.size; i++) { 1413 dec = goals.get(i); 1414 for (int xs = decodeX(dec), xi = 0; xi < size && xs >= 0; xs--, xi++) { 1415 for (int ys = decodeY(dec), yi = 0; yi < size && ys >= 0; ys--, yi++) { 1416 if(physicalMap[xs][ys] > FLOOR) 1417 continue PER_GOAL; 1418 gradientClone[xs][ys] = GOAL; 1419 } 1420 } 1421 } 1422 double currentLowest = 999000, cs, dist; 1423 fresh.clear(); 1424 for (int x = 0; x < width; x++) { 1425 for (int y = 0; y < height; y++) { 1426 if (gradientClone[x][y] <= FLOOR) { 1427 if (gradientClone[x][y] < currentLowest) { 1428 currentLowest = gradientClone[x][y]; 1429 fresh.clear(); 1430 fresh.add(encode(x, y)); 1431 } else if (gradientClone[x][y] == currentLowest) { 1432 fresh.add(encode(x, y)); 1433 } 1434 } 1435 } 1436 } 1437 int fsz, numAssigned = fresh.size; 1438 mappedCount = goals.size; 1439 Direction[] moveDirs = (measurement == Measurement.MANHATTAN) ? Direction.CARDINALS : Direction.OUTWARDS; 1440 1441 while (numAssigned > 0) { 1442 numAssigned = 0; 1443 fsz = fresh.size; 1444 for (int ci = fsz-1; ci >= 0; ci--) { 1445 cen = fresh.removeIndex(ci); 1446 cenX = decodeX(cen); 1447 cenY = decodeY(cen); 1448 dist = gradientClone[cenX][cenY]; 1449 1450 for (int d = 0; d < moveDirs.length; d++) { 1451 adjX = cenX + moveDirs[d].deltaX; 1452 adjY = cenY + moveDirs[d].deltaY; 1453 if (adjX < 0 || adjY < 0 || width <= adjX || height <= adjY) 1454 /* Outside the map */ 1455 continue; 1456 if(d >= 4 && blockingRequirement > 0) // diagonal 1457 { 1458 if((gradientClone[adjX][cenY] > FLOOR ? 1 : 0) 1459 + (gradientClone[cenX][adjY] > FLOOR ? 1 : 0) 1460 >= blockingRequirement) 1461 { 1462 continue; 1463 } 1464 } 1465 double h = measurement.heuristic(moveDirs[d]); 1466 cs = dist + h * costMap[adjX][adjY]; 1467 if (gradientClone[adjX][adjY] <= FLOOR && cs < gradientClone[adjX][adjY]) { 1468 setFresh(adjX, adjY, cs); 1469 ++numAssigned; 1470 ++mappedCount; 1471 if(start != null && start.x == adjX && start.y == adjY && standardCosts) 1472 { 1473 if (impassable != null && !impassable.isEmpty()) { 1474 for (Coord pt : impassable) { 1475 if(pt != null && pt.isWithin(width, height)) { 1476 for (int xs = pt.x, xi = 0; xi < size && xs >= 0; xs--, xi++) { 1477 for (int ys = pt.y, yi = 0; yi < size && ys >= 0; ys--, yi++) { 1478 gradientClone[xs][ys] = physicalMap[xs][ys]; 1479 } 1480 } 1481 } 1482 } 1483 } 1484 gradientMap = gradientClone; 1485 return; 1486 } 1487 } 1488 } 1489 } 1490 } 1491 if (impassable != null && !impassable.isEmpty()) { 1492 for (Coord pt : impassable) { 1493 if(pt != null && pt.isWithin(width, height)) { 1494 for (int xs = pt.x, xi = 0; xi < size && xs >= 0; xs--, xi++) { 1495 for (int ys = pt.y, yi = 0; yi < size && ys >= 0; ys--, yi++) { 1496 gradientClone[xs][ys] = physicalMap[xs][ys]; 1497 } 1498 } 1499 } 1500 } 1501 } 1502 gradientMap = gradientClone; 1503 } 1504 1505 1506 /** 1507 * Recalculate the Dijkstra map for a creature that is potentially larger than 1x1 cell and return it. The value of 1508 * a cell in the returned Dijkstra map assumes that a creature is square, with a side length equal to the passed 1509 * size, that its minimum-x, minimum-y cell is the starting cell, and that any cell with a distance number 1510 * represents the distance for the creature's minimum-x, minimum-y cell to reach it. Cells that cannot be entered 1511 * by the minimum-x, minimum-y cell because of sizing (such as a floor cell next to a maximum-x and/or maximum-y 1512 * wall if size is > 1) will be marked as DARK. Cells that were marked as goals with setGoal will have 1513 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 1514 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 1515 * which will have a value defined by the WALL constant in this class, and areas that the scan was 1516 * unable to reach, which will have a value defined by the DARK constant in this class. (typically, 1517 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 1518 * current measurement. The result is stored in the {@link #gradientMap} field and a copy is returned. 1519 * 1520 * @param impassable A Collection of Coord keys representing the locations of enemies or other moving obstacles to a 1521 * path that cannot be moved through; this can be null if there are no such obstacles. 1522 * @param size The length of one side of a square creature using this to find a path, i.e. 2 for a 2x2 cell 1523 * creature. Non-square creatures are not supported because turning is really hard. 1524 * @return A 2D double[width][height] using the width and height of what this knows about the physical map. 1525 */ 1526 public double[][] partialScan(final int limit, final Collection<Coord> impassable, final int size) { 1527 partialScan(limit,null, impassable, size); 1528 double[][] gradientClone = new double[width][height]; 1529 for (int x = 0; x < width; x++) { 1530 for (int y = 0; y < height; y++) { 1531 if (gradientMap[x][y] == FLOOR) { 1532 gradientMap[x][y] = DARK; 1533 } 1534 } 1535 System.arraycopy(gradientMap[x], 0, gradientClone[x], 0, height); 1536 } 1537 return gradientClone; 1538 1539 } 1540 1541 /** 1542 * Recalculate the Dijkstra map for a creature that is potentially larger than 1x1 cell and return it. The value of 1543 * a cell in the returned Dijkstra map assumes that a creature is square, with a side length equal to the passed 1544 * size, that its minimum-x, minimum-y cell is the starting cell, and that any cell with a distance number 1545 * represents the distance for the creature's minimum-x, minimum-y cell to reach it. Cells that cannot be entered 1546 * by the minimum-x, minimum-y cell because of sizing (such as a floor cell next to a maximum-x and/or maximum-y 1547 * wall if size is > 1) will be marked as DARK. Cells that were marked as goals with setGoal will have 1548 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 1549 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 1550 * which will have a value defined by the WALL constant in this class, and areas that the scan was 1551 * unable to reach, which will have a value defined by the DARK constant in this class. (typically, 1552 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 1553 * current measurement. The result is stored in the {@link #gradientMap} field, and nothing is returned. 1554 * If you want the data returned, you can use {@link #partialScan(int, Collection, int)} (which calls this method 1555 * with null for the start parameter, then modifies the gradientMap field and returns a copy), or you can 1556 * just retrieve the gradientMap (maybe copying it; {@link squidpony.ArrayTools#copy(double[][])} is a 1557 * convenient option for copying a 2D double array). If start is non-null, which is usually used when 1558 * finding a single path, then cells that didn't need to be explored (because they were further than the 1559 * path needed to go from start to goal) will have the value {@link #FLOOR}. You may wish to assign a 1560 * different value to these cells in some cases (especially if start is null, which means any cells that 1561 * are still FLOOR could not be reached from any goal), and the overloads of partialScan that return 2D double 1562 * arrays do change FLOOR to {@link #DARK}, which is usually treated similarly to {@link #WALL}. 1563 * 1564 * @param impassable A Collection of Coord keys representing the locations of enemies or other moving obstacles to a 1565 * path that cannot be moved through; this can be null if there are no such obstacles. 1566 * @param size The length of one side of a square creature using this to find a path, i.e. 2 for a 2x2 cell 1567 * creature. Non-square creatures are not supported because turning is really hard. 1568 */ 1569 public void partialScan(final int limit, final Coord start, final Collection<Coord> impassable, final int size) { 1570 1571 if (!initialized || limit <= 0) return; 1572 double[][] gradientClone = ArrayTools.copy(gradientMap); 1573 if (impassable != null && !impassable.isEmpty()) { 1574 for (Coord pt : impassable) { 1575 if(pt != null && pt.isWithin(width, height)) 1576 gradientMap[pt.x][pt.y] = WALL; 1577 } 1578 } 1579 for (int xx = size; xx < width; xx++) { 1580 for (int yy = size; yy < height; yy++) { 1581 if(gradientMap[xx][yy] > FLOOR) { 1582 for (int xs = xx, xi = 0; xi < size && xs >= 0; xs--, xi++) { 1583 for (int ys = yy, yi = 0; yi < size && ys >= 0; ys--, yi++) { 1584 gradientClone[xs][ys] = WALL; 1585 } 1586 } 1587 } 1588 } 1589 } 1590 int dec, adjX, adjY, cen, cenX, cenY; 1591 1592 PER_GOAL: 1593 for (int i = 0; i < goals.size; i++) { 1594 dec = goals.get(i); 1595 for (int xs = decodeX(dec), xi = 0; xi < size && xs >= 0; xs--, xi++) { 1596 for (int ys = decodeY(dec), yi = 0; yi < size && ys >= 0; ys--, yi++) { 1597 if(physicalMap[xs][ys] > FLOOR) 1598 continue PER_GOAL; 1599 gradientClone[xs][ys] = GOAL; 1600 } 1601 } 1602 } 1603 double currentLowest = 999000, cs, dist; 1604 fresh.clear(); 1605 for (int x = 0; x < width; x++) { 1606 for (int y = 0; y < height; y++) { 1607 if (gradientClone[x][y] <= FLOOR) { 1608 if (gradientClone[x][y] < currentLowest) { 1609 currentLowest = gradientClone[x][y]; 1610 fresh.clear(); 1611 fresh.add(encode(x, y)); 1612 } else if (gradientClone[x][y] == currentLowest) { 1613 fresh.add(encode(x, y)); 1614 } 1615 } 1616 } 1617 } 1618 int fsz, numAssigned = fresh.size; 1619 mappedCount = goals.size; 1620 Direction[] moveDirs = (measurement == Measurement.MANHATTAN) ? Direction.CARDINALS : Direction.OUTWARDS; 1621 1622 int iter = 0; 1623 while (numAssigned > 0 && iter++ < limit) { 1624 numAssigned = 0; 1625 fsz = fresh.size; 1626 for (int ci = fsz-1; ci >= 0; ci--) { 1627 cen = fresh.removeIndex(ci); 1628 cenX = decodeX(cen); 1629 cenY = decodeY(cen); 1630 dist = gradientClone[cenX][cenY]; 1631 1632 for (int d = 0; d < moveDirs.length; d++) { 1633 adjX = cenX + moveDirs[d].deltaX; 1634 adjY = cenY + moveDirs[d].deltaY; 1635 if (adjX < 0 || adjY < 0 || width <= adjX || height <= adjY) 1636 /* Outside the map */ 1637 continue; 1638 if(d >= 4 && blockingRequirement > 0) // diagonal 1639 { 1640 if((gradientClone[adjX][cenY] > FLOOR ? 1 : 0) 1641 + (gradientClone[cenX][adjY] > FLOOR ? 1 : 0) 1642 >= blockingRequirement) 1643 { 1644 continue; 1645 } 1646 } 1647 double h = measurement.heuristic(moveDirs[d]); 1648 cs = dist + h * costMap[adjX][adjY]; 1649 if (gradientClone[adjX][adjY] <= FLOOR && cs < gradientClone[adjX][adjY]) { 1650 setFresh(adjX, adjY, cs); 1651 ++numAssigned; 1652 ++mappedCount; 1653 if(start != null && start.x == adjX && start.y == adjY && standardCosts) 1654 { 1655 if (impassable != null && !impassable.isEmpty()) { 1656 for (Coord pt : impassable) { 1657 if(pt != null && pt.isWithin(width, height)) { 1658 for (int xs = pt.x, xi = 0; xi < size && xs >= 0; xs--, xi++) { 1659 for (int ys = pt.y, yi = 0; yi < size && ys >= 0; ys--, yi++) { 1660 gradientClone[xs][ys] = physicalMap[xs][ys]; 1661 } 1662 } 1663 } 1664 } 1665 } 1666 gradientMap = gradientClone; 1667 return; 1668 } 1669 } 1670 } 1671 } 1672 } 1673 if (impassable != null && !impassable.isEmpty()) { 1674 for (Coord pt : impassable) { 1675 if(pt != null && pt.isWithin(width, height)) { 1676 for (int xs = pt.x, xi = 0; xi < size && xs >= 0; xs--, xi++) { 1677 for (int ys = pt.y, yi = 0; yi < size && ys >= 0; ys--, yi++) { 1678 gradientClone[xs][ys] = physicalMap[xs][ys]; 1679 } 1680 } 1681 } 1682 } 1683 } 1684 gradientMap = gradientClone; 1685 } 1686 1687 /** 1688 * Scans the dungeon using DijkstraMap.scan with the listed goals and start point, and returns a list 1689 * of Coord positions (using the current measurement) needed to get closer to the closest reachable 1690 * goal. The maximum length of the returned list is given by length, which represents movement in a system where 1691 * a single move can be multiple cells if length is greater than 1 and should usually be 1 in standard roguelikes; 1692 * if moving the full length of the list would place the mover in a position shared by one of the positions in 1693 * onlyPassable (which is typically filled with friendly units that can be passed through in multi-cell-movement 1694 * scenarios), it will recalculate a move so that it does not pass into that cell. The keys in impassable should 1695 * be the positions of enemies and obstacles that cannot be moved through, and will be ignored if there is a goal 1696 * overlapping one. This overload always scans the whole map; use 1697 * {@link #findPath(int, int, Collection, Collection, Coord, Coord...)} to scan a smaller area for performance reasons. 1698 * <br> 1699 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 1700 * each call to a pathfinding method. 1701 * @param length the length of the path to calculate 1702 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 1703 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 1704 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 1705 * @param targets a vararg or array of Coord that this will try to pathfind toward 1706 * @return an ArrayList of Coord that will contain the locations of this creature as it goes toward a target. Copy of path. 1707 */ 1708 public ArrayList<Coord> findPath(int length, Collection<Coord> impassable, 1709 Collection<Coord> onlyPassable, Coord start, Coord... targets) { 1710 return findPath(length, -1, impassable, onlyPassable, start, targets); 1711 } 1712 1713 /** 1714 * Scans the dungeon using DijkstraMap.scan or DijkstraMap.partialScan with the listed goals and start 1715 * point, and returns a list of Coord positions (using the current measurement) needed to get closer 1716 * to the closest reachable goal. The maximum length of the returned list is given by length, which represents 1717 * movement in a system where a single move can be multiple cells if length is greater than 1 and should usually 1718 * be 1 in standard roguelikes; if moving the full length of the list would place the mover in a position shared 1719 * by one of the positions in onlyPassable (which is typically filled with friendly units that can be passed 1720 * through in multi-cell-movement scenarios), it will recalculate a move so that it does not pass into that cell. 1721 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 1722 * through, and will be ignored if there is a goal overlapping one. 1723 * The full map will only be scanned if scanLimit is 0 or less; for positive scanLimit values this will scan only 1724 * that distance out from each goal, which can save processing time on maps where only a small part matters. 1725 * Generally, scanLimit should be significantly greater than length. 1726 * <br> 1727 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 1728 * each call to a pathfinding method. 1729 * 1730 * @param length the length of the path to calculate 1731 * @param scanLimit how many cells away from a goal to actually process; negative to process whole map 1732 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 1733 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 1734 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 1735 * @param targets a vararg or array of Coord that this will try to pathfind toward 1736 * @return an ArrayList of Coord that will contain the locations of this creature as it goes toward a target. Copy of path. 1737 */ 1738 public ArrayList<Coord> findPath(int length, int scanLimit, Collection<Coord> impassable, 1739 Collection<Coord> onlyPassable, Coord start, Coord... targets) { 1740 return findPath(null, length, scanLimit, impassable, onlyPassable, start, targets); 1741 } 1742 /** 1743 * Scans the dungeon using DijkstraMap.scan or DijkstraMap.partialScan with the listed goals and start 1744 * point, and returns a list of Coord positions (using the current measurement) needed to get closer 1745 * to the closest reachable goal. The maximum length of the returned list is given by length, which represents 1746 * movement in a system where a single move can be multiple cells if length is greater than 1 and should usually 1747 * be 1 in standard roguelikes; if moving the full length of the list would place the mover in a position shared 1748 * by one of the positions in onlyPassable (which is typically filled with friendly units that can be passed 1749 * through in multi-cell-movement scenarios), it will recalculate a move so that it does not pass into that cell. 1750 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 1751 * through, and will be ignored if there is a goal overlapping one. 1752 * The full map will only be scanned if scanLimit is 0 or less; for positive scanLimit values this will scan only 1753 * that distance out from each goal, which can save processing time on maps where only a small part matters. 1754 * Generally, scanLimit should be significantly greater than length. 1755 * <br> 1756 * This overload takes a buffer parameter, an ArrayList of Coord, that the results will be appended to. If the 1757 * buffer is null, a new ArrayList will be made and appended to. This caches its result in a member field, path, 1758 * which can be fetched after finding a path and will change with each call to a pathfinding method. Any existing 1759 * contents of buffer will not affect the path field of this DijkstraMap. 1760 * 1761 * @param buffer an existing ArrayList of Coord that will have the result appended to it (in-place); if null, this will make a new ArrayList 1762 * @param length the length of the path to calculate 1763 * @param scanLimit how many cells away from a goal to actually process; negative to process whole map 1764 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 1765 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 1766 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 1767 * @param targets a vararg or array of Coord that this will try to pathfind toward 1768 * @return an ArrayList of Coord that will contain the locations of this creature as it goes toward a target. Copy of path. 1769 */ 1770 public ArrayList<Coord> findPath(ArrayList<Coord> buffer, int length, int scanLimit, Collection<Coord> impassable, 1771 Collection<Coord> onlyPassable, Coord start, Coord... targets) { 1772 path.clear(); 1773 if (!initialized || length <= 0) 1774 { 1775 cutShort = true; 1776 if(buffer == null) 1777 return new ArrayList<>(); 1778 else 1779 { 1780 return buffer; 1781 } 1782 } 1783 if (impassable == null) 1784 impassable2.clear(); 1785 else 1786 { 1787 impassable2.clear(); 1788 impassable2.addAll(impassable); 1789 } 1790 if (onlyPassable != null && length == 1) 1791 impassable2.addAll(onlyPassable); 1792 1793 resetMap(); 1794 setGoals(targets); 1795 if (goals.isEmpty()) 1796 { 1797 cutShort = true; 1798 if(buffer == null) 1799 return new ArrayList<>(); 1800 else 1801 { 1802 return buffer; 1803 } 1804 } 1805 if(scanLimit <= 0 || scanLimit < length) 1806 scan(start, impassable2); 1807 else 1808 partialScan(start, scanLimit, impassable2); 1809 Coord currentPos = start; 1810 double paidLength = 0.0; 1811 while (true) { 1812 if (frustration > 500) { 1813 path.clear(); 1814 break; 1815 } 1816 double best = gradientMap[currentPos.x][currentPos.y]; 1817 appendDirToShuffle(rng); 1818 int choice = 0; 1819 1820 for (int d = 0; d <= measurement.directionCount(); d++) { 1821 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 1822 if(!pt.isWithin(width, height)) 1823 continue; 1824 if (gradientMap[pt.x][pt.y] < best && !impassable2.contains(pt)) { 1825 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 1826 best = gradientMap[pt.x][pt.y]; 1827 choice = d; 1828 } 1829 } 1830 } 1831 1832 if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 1833 cutShort = true; 1834 frustration = 0; 1835 if(buffer == null) 1836 return new ArrayList<>(path); 1837 else 1838 { 1839 buffer.addAll(path); 1840 return buffer; 1841 } 1842 } 1843 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 1844 path.add(currentPos); 1845 paidLength += costMap[currentPos.x][currentPos.y]; 1846 frustration++; 1847 if (paidLength > length - 1.0) { 1848 if (onlyPassable != null && onlyPassable.contains(currentPos)) { 1849 tempSet.clear(); 1850 tempSet.addAll(impassable2); 1851 tempSet.add(currentPos); 1852 return findPath(buffer, length, scanLimit, tempSet, onlyPassable, start, targets); 1853 } 1854 break; 1855 } 1856 if (gradientMap[currentPos.x][currentPos.y] == 0) 1857 break; 1858 } 1859 cutShort = false; 1860 frustration = 0; 1861 goals.clear(); 1862 if(buffer == null) 1863 return new ArrayList<>(path); 1864 else 1865 { 1866 buffer.addAll(path); 1867 return buffer; 1868 } 1869 } 1870 1871 /** 1872 * Scans the dungeon using DijkstraMap.scan with the listed goals and start point, and returns a list 1873 * of Coord positions (using the current measurement) needed to get closer to a goal, until preferredRange is 1874 * reached, or further from a goal if the preferredRange has not been met at the current distance. 1875 * The maximum length of the returned list is given by moveLength; if moving the full length of 1876 * the list would place the mover in a position shared by one of the positions in onlyPassable 1877 * (which is typically filled with friendly units that can be passed through in multi-tile- 1878 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 1879 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 1880 * through, and will be ignored if there is a goal overlapping one. 1881 * <br> 1882 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 1883 * each call to a pathfinding method. 1884 * 1885 * @param moveLength the length of the path to calculate 1886 * @param preferredRange the distance this unit will try to keep from a target 1887 * @param los a squidgrid.LOS object if the preferredRange should try to stay in line of sight, or null if LoS 1888 * should be disregarded. 1889 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 1890 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 1891 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 1892 * @param targets a vararg or array of Coord that this will try to pathfind toward 1893 * @return an ArrayList of Coord that will contain the locations of this creature as it goes toward a target. Copy of path. 1894 */ 1895 public ArrayList<Coord> findAttackPath(int moveLength, int preferredRange, LOS los, Collection<Coord> impassable, 1896 Collection<Coord> onlyPassable, Coord start, Coord... targets) { 1897 return findAttackPath(moveLength, preferredRange, preferredRange, los, impassable, onlyPassable, start, targets); 1898 } 1899 1900 /** 1901 * Scans the dungeon using DijkstraMap.scan with the listed goals and start point, and returns a list 1902 * of Coord positions (using the current measurement) needed to get closer to a goal, until a cell is reached with 1903 * a distance from a goal that is at least equal to minPreferredRange and no more than maxPreferredRange, 1904 * which may go further from a goal if the minPreferredRange has not been met at the current distance. 1905 * The maximum length of the returned list is given by moveLength; if moving the full length of 1906 * the list would place the mover in a position shared by one of the positions in onlyPassable 1907 * (which is typically filled with friendly units that can be passed through in multi-tile- 1908 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 1909 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 1910 * through, and will be ignored if there is a goal overlapping one. 1911 * <br> 1912 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 1913 * each call to a pathfinding method. 1914 * 1915 * @param moveLength the length of the path to calculate 1916 * @param minPreferredRange the (inclusive) lower bound of the distance this unit will try to keep from a target 1917 * @param maxPreferredRange the (inclusive) upper bound of the distance this unit will try to keep from a target 1918 * @param los a squidgrid.LOS object if the preferredRange should try to stay in line of sight, or null if LoS 1919 * should be disregarded. 1920 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 1921 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 1922 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 1923 * @param targets a vararg or array of Coord that this will try to pathfind toward 1924 * @return an ArrayList of Coord that will contain the locations of this creature as it goes toward a target. Copy of path. 1925 */ 1926 public ArrayList<Coord> findAttackPath(int moveLength, int minPreferredRange, int maxPreferredRange, LOS los, 1927 Collection<Coord> impassable, Collection<Coord> onlyPassable, Coord start, Coord... targets) { 1928 return findAttackPath(null, moveLength, minPreferredRange, maxPreferredRange, los, impassable, onlyPassable, start, targets); 1929 } 1930 /** 1931 * Scans the dungeon using DijkstraMap.scan with the listed goals and start point, and returns a list 1932 * of Coord positions (using the current measurement) needed to get closer to a goal, until a cell is reached with 1933 * a distance from a goal that is at least equal to minPreferredRange and no more than maxPreferredRange, 1934 * which may go further from a goal if the minPreferredRange has not been met at the current distance. 1935 * The maximum length of the returned list is given by moveLength; if moving the full length of 1936 * the list would place the mover in a position shared by one of the positions in onlyPassable 1937 * (which is typically filled with friendly units that can be passed through in multi-tile- 1938 * movement scenarios), it will recalculate a move so that it does not pass into that cell. In most roguelikes where 1939 * movement happens one cell at a time, moveLength should be 1; if it is higher then the path will prefer getting 1940 * further away from the target (using up most or all of moveLength) while minPreferredRange and maxPreferredRange 1941 * can be satisfied. This does ensure a pathfinder with a ranged weapon stays far from melee range, but it may not 1942 * be the expected behavior because it will try to find the best path rather than the shortest it can attack from. 1943 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 1944 * through, and will be ignored if there is a goal overlapping one. 1945 * <br> 1946 * This overload takes a buffer parameter, an ArrayList of Coord, that the results will be appended to. If the 1947 * buffer is null, a new ArrayList will be made and appended to. This caches its result in a member field, path, 1948 * which can be fetched after finding a path and will change with each call to a pathfinding method. Any existing 1949 * contents of buffer will not affect the path field of this DijkstraMap. 1950 * 1951 * @param buffer an existing ArrayList of Coord that will have the result appended to it (in-place); if null, this will make a new ArrayList 1952 * @param moveLength the length of the path to calculate; almost always, the pathfinder will try to use this length in full to obtain the best range 1953 * @param minPreferredRange the (inclusive) lower bound of the distance this unit will try to keep from a target 1954 * @param maxPreferredRange the (inclusive) upper bound of the distance this unit will try to keep from a target 1955 * @param los a squidgrid.LOS object if the preferredRange should try to stay in line of sight, or null if LoS 1956 * should be disregarded. 1957 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 1958 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 1959 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 1960 * @param targets a vararg or array of Coord that this will try to pathfind toward 1961 * @return an ArrayList of Coord that will contain the locations of this creature as it goes toward a target. Copy of path. 1962 */ 1963 public ArrayList<Coord> findAttackPath(ArrayList<Coord> buffer, int moveLength, int minPreferredRange, int maxPreferredRange, LOS los, 1964 Collection<Coord> impassable, Collection<Coord> onlyPassable, Coord start, Coord... targets) { 1965 if (!initialized || moveLength <= 0) 1966 { 1967 cutShort = true; 1968 if(buffer == null) 1969 return new ArrayList<>(); 1970 else 1971 { 1972 return buffer; 1973 } 1974 } 1975 if (minPreferredRange < 0) minPreferredRange = 0; 1976 if (maxPreferredRange < minPreferredRange) maxPreferredRange = minPreferredRange; 1977 double[][] resMap = new double[width][height]; 1978 if (los != null) { 1979 for (int x = 0; x < width; x++) { 1980 for (int y = 0; y < height; y++) { 1981 resMap[x][y] = (physicalMap[x][y] == WALL) ? 1.0 : 0.0; 1982 } 1983 } 1984 } 1985 path.clear(); 1986 if (impassable == null) 1987 impassable2.clear(); 1988 else 1989 { 1990 impassable2.clear(); 1991 impassable2.addAll(impassable); 1992 } 1993 if (onlyPassable != null && moveLength == 1) 1994 impassable2.addAll(onlyPassable); 1995 1996 resetMap(); 1997 for (Coord goal : targets) { 1998 setGoal(goal.x, goal.y); 1999 } 2000 if (goals.isEmpty()) 2001 { 2002 cutShort = true; 2003 if(buffer == null) 2004 return new ArrayList<>(); 2005 else 2006 { 2007 return buffer; 2008 } 2009 } 2010 2011 Measurement mess = measurement; 2012 if (measurement == Measurement.EUCLIDEAN) { 2013 measurement = Measurement.CHEBYSHEV; 2014 } 2015 scan(null, impassable2); 2016 for (int x = 0; x < width; x++) { 2017 for (int y = 0; y < height; y++) { 2018 if (gradientMap[x][y] == FLOOR) { 2019 gradientMap[x][y] = DARK; 2020 } 2021 } 2022 } 2023 goals.clear(); 2024 2025 for (int x = 0; x < width; x++) { 2026 CELL: 2027 for (int y = 0; y < height; y++) { 2028 if (gradientMap[x][y] == WALL || gradientMap[x][y] == DARK) 2029 continue; 2030 if (gradientMap[x][y] >= minPreferredRange && gradientMap[x][y] <= maxPreferredRange) { 2031 2032 for (Coord goal : targets) { 2033 if (los == null || los.isReachable(resMap, x, y, goal.x, goal.y)) { 2034 setGoal(x, y); 2035 gradientMap[x][y] = 0; 2036 continue CELL; 2037 } 2038 } 2039 gradientMap[x][y] = FLOOR; 2040 } else 2041 gradientMap[x][y] = FLOOR; 2042 } 2043 } 2044 measurement = mess; 2045 scan(null, impassable2); 2046 for (int x = 0; x < width; x++) { 2047 for (int y = 0; y < height; y++) { 2048 if (gradientMap[x][y] == FLOOR) { 2049 gradientMap[x][y] = DARK; 2050 } 2051 } 2052 } 2053 if(gradientMap[start.x][start.y] <= 0.0) 2054 { 2055 cutShort = false; 2056 frustration = 0; 2057 goals.clear(); 2058 if(buffer == null) 2059 return new ArrayList<>(path); 2060 else 2061 { 2062 buffer.addAll(path); 2063 return buffer; 2064 } 2065 2066 } 2067 Coord currentPos = start; 2068 double paidLength = 0.0; 2069 while (true) { 2070 if (frustration > 500) { 2071 path.clear(); 2072 break; 2073 } 2074 double best = gradientMap[currentPos.x][currentPos.y]; 2075 appendDirToShuffle(rng); 2076 int choice = 0; 2077 2078 for (int d = 0; d <= measurement.directionCount(); d++) { 2079 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 2080 if(!pt.isWithin(width, height)) 2081 continue; 2082 if (gradientMap[pt.x][pt.y] < best && !impassable2.contains(pt)) { 2083 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 2084 best = gradientMap[pt.x][pt.y]; 2085 choice = d; 2086 } 2087 } 2088 } 2089 2090 if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 2091 cutShort = true; 2092 frustration = 0; 2093 if(buffer == null) 2094 return new ArrayList<>(path); 2095 else 2096 { 2097 buffer.addAll(path); 2098 return buffer; 2099 } 2100 } 2101 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 2102 path.add(Coord.get(currentPos.x, currentPos.y)); 2103 paidLength += costMap[currentPos.x][currentPos.y]; 2104 frustration++; 2105 if (paidLength > moveLength - 1.0) { 2106 2107 if (onlyPassable != null && onlyPassable.contains(currentPos)) { 2108 tempSet.clear(); 2109 tempSet.addAll(impassable2); 2110 tempSet.add(currentPos); 2111 return findAttackPath(buffer, moveLength, minPreferredRange, maxPreferredRange, los, tempSet, 2112 onlyPassable, start, targets); 2113 } 2114 break; 2115 } 2116 if (gradientMap[currentPos.x][currentPos.y] == 0) 2117 break; 2118 } 2119 cutShort = false; 2120 frustration = 0; 2121 goals.clear(); 2122 if(buffer == null) 2123 return new ArrayList<>(path); 2124 else 2125 { 2126 buffer.addAll(path); 2127 return buffer; 2128 } 2129 } 2130 2131 /** 2132 * Scans the dungeon using DijkstraMap.scan with the listed goals and start point, and returns a list 2133 * of Coord positions (using the current measurement) needed to get closer to a goal, where goals are 2134 * considered valid if they are at a valid range for the given Technique to hit at least one target 2135 * and ideal if that Technique can affect as many targets as possible from a cell that can be moved 2136 * to with at most movelength steps. 2137 * <br> 2138 * The return value of this method is the path to get to a location to attack, but on its own it 2139 * does not tell the user how to perform the attack. It does set the targetMap 2D Coord array field 2140 * so that if your position at the end of the returned path is non-null in targetMap, it will be 2141 * a Coord that can be used as a target position for Technique.apply() . If your position at the end 2142 * of the returned path is null, then an ideal attack position was not reachable by the path. 2143 * <br> 2144 * This needs a char[][] dungeon as an argument because DijkstraMap does not always have a char[][] 2145 * version of the map available to it, and certain AOE implementations that a Technique uses may 2146 * need a char[][] specifically to determine what they affect. 2147 * <br> 2148 * The maximum length of the returned list is given by moveLength; if moving the full length of 2149 * the list would place the mover in a position shared by one of the positions in allies 2150 * (which is typically filled with friendly units that can be passed through in multi-tile- 2151 * movement scenarios, and is also used considered an undesirable thing to affect for the Technique), 2152 * it will recalculate a move so that it does not pass into that cell. 2153 * <br> 2154 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2155 * through, and will be ignored if there is a target overlapping one. 2156 * <br> 2157 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 2158 * each call to a pathfinding method. 2159 * 2160 * @param moveLength the maximum distance to try to pathfind out to; if a spot to use a Technique can be found 2161 * while moving no more than this distance, then the targetMap field in this object will have a 2162 * target Coord that is ideal for the given Technique at the x, y indices corresponding to the 2163 * last Coord in the returned path. 2164 * @param tech a Technique that we will try to find an ideal place to use, and/or a path toward that place. 2165 * @param dungeon a char 2D array with '#' for walls. 2166 * @param los a squidgrid.LOS object if the preferred range should try to stay in line of sight, or null if LoS 2167 * should be disregarded. 2168 * @param impassable locations of enemies or mobile hazards/obstacles that aren't in the map as walls 2169 * @param allies called onlyPassable in other methods, here it also represents allies for Technique things 2170 * @param start the Coord the pathfinder starts at. 2171 * @param targets a Set of Coord, not an array of Coord or variable argument list as in other methods. 2172 * @return an ArrayList of Coord that represents a path to travel to get to an ideal place to use tech. Copy of path. 2173 */ 2174 public ArrayList<Coord> findTechniquePath(int moveLength, Technique tech, char[][] dungeon, LOS los, 2175 Collection<Coord> impassable, Collection<Coord> allies, Coord start, Collection<Coord> targets) { 2176 return findTechniquePath(null, moveLength, tech, dungeon, los, impassable, allies, start, targets); 2177 } 2178 /** 2179 * Scans the dungeon using DijkstraMap.scan with the listed goals and start point, and returns a list 2180 * of Coord positions (using the current measurement) needed to get closer to a goal, where goals are 2181 * considered valid if they are at a valid range for the given Technique to hit at least one target 2182 * and ideal if that Technique can affect as many targets as possible from a cell that can be moved 2183 * to with at most movelength steps. 2184 * <br> 2185 * The return value of this method is the path to get to a location to attack, but on its own it 2186 * does not tell the user how to perform the attack. It does set the targetMap 2D Coord array field 2187 * so that if your position at the end of the returned path is non-null in targetMap, it will be 2188 * a Coord that can be used as a target position for Technique.apply() . If your position at the end 2189 * of the returned path is null, then an ideal attack position was not reachable by the path. 2190 * <br> 2191 * This needs a char[][] dungeon as an argument because DijkstraMap does not always have a char[][] 2192 * version of the map available to it, and certain AOE implementations that a Technique uses may 2193 * need a char[][] specifically to determine what they affect. 2194 * <br> 2195 * The maximum length of the returned list is given by moveLength; if moving the full length of 2196 * the list would place the mover in a position shared by one of the positions in allies 2197 * (which is typically filled with friendly units that can be passed through in multi-tile- 2198 * movement scenarios, and is also used considered an undesirable thing to affect for the Technique), 2199 * it will recalculate a move so that it does not pass into that cell. 2200 * <br> 2201 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2202 * through, and will be ignored if there is a target overlapping one. 2203 * <br> 2204 * This overload takes a buffer parameter, an ArrayList of Coord, that the results will be appended to. If the 2205 * buffer is null, a new ArrayList will be made and appended to. This caches its result in a member field, path, 2206 * which can be fetched after finding a path and will change with each call to a pathfinding method. Any existing 2207 * contents of buffer will not affect the path field of this DijkstraMap. 2208 * 2209 * @param buffer an existing ArrayList of Coord that will have the result appended to it (in-place); if null, this will make a new ArrayList 2210 * @param moveLength the maximum distance to try to pathfind out to; if a spot to use a Technique can be found 2211 * while moving no more than this distance, then the targetMap field in this object will have a 2212 * target Coord that is ideal for the given Technique at the x, y indices corresponding to the 2213 * last Coord in the returned path. 2214 * @param tech a Technique that we will try to find an ideal place to use, and/or a path toward that place. 2215 * @param dungeon a char 2D array with '#' for walls. 2216 * @param los a squidgrid.LOS object if the preferred range should try to stay in line of sight, or null if LoS 2217 * should be disregarded. 2218 * @param impassable locations of enemies or mobile hazards/obstacles that aren't in the map as walls 2219 * @param allies called onlyPassable in other methods, here it also represents allies for Technique things 2220 * @param start the Coord the pathfinder starts at. 2221 * @param targets a Set of Coord, not an array of Coord or variable argument list as in other methods. 2222 * @return an ArrayList of Coord that represents a path to travel to get to an ideal place to use tech. Copy of path. 2223 */ 2224 public ArrayList<Coord> findTechniquePath(ArrayList<Coord> buffer, int moveLength, Technique tech, char[][] dungeon, LOS los, 2225 Collection<Coord> impassable, Collection<Coord> allies, Coord start, Collection<Coord> targets) { 2226 if (!initialized || moveLength <= 0) 2227 { 2228 cutShort = true; 2229 if(buffer == null) 2230 return new ArrayList<>(); 2231 else 2232 { 2233 return buffer; 2234 } 2235 } 2236 tech.setMap(dungeon); 2237 double[][] resMap = new double[width][height]; 2238 double[][] worthMap = new double[width][height]; 2239 double[][] userDistanceMap; 2240 double paidLength = 0.0; 2241 2242 for (int x = 0; x < width; x++) { 2243 for (int y = 0; y < height; y++) { 2244 resMap[x][y] = (physicalMap[x][y] == WALL) ? 1.0 : 0.0; 2245 targetMap[x][y] = null; 2246 } 2247 } 2248 2249 path.clear(); 2250 if (targets == null || targets.size() == 0) 2251 { 2252 cutShort = true; 2253 if(buffer == null) 2254 return new ArrayList<>(); 2255 else 2256 { 2257 return buffer; 2258 } 2259 } 2260 if (impassable == null) 2261 impassable2.clear(); 2262 else { 2263 impassable2.clear(); 2264 impassable2.addAll(impassable); 2265 } 2266 if (allies == null) 2267 friends.clear(); 2268 else { 2269 friends.clear(); 2270 friends.addAll(allies); 2271 friends.remove(start); 2272 } 2273 2274 resetMap(); 2275 setGoal(start); 2276 userDistanceMap = scan(impassable2); 2277 clearGoals(); 2278 resetMap(); 2279 for (Coord goal : targets) { 2280 setGoal(goal.x, goal.y); 2281 } 2282 if (goals.isEmpty()) 2283 { 2284 cutShort = true; 2285 if(buffer == null) 2286 return new ArrayList<>(); 2287 else 2288 { 2289 return buffer; 2290 } 2291 } 2292 2293 Measurement mess = measurement; 2294 /* 2295 if(measurement == Measurement.EUCLIDEAN) 2296 { 2297 measurement = Measurement.CHEBYSHEV; 2298 } 2299 */ 2300 scan(null, impassable2); 2301 for (int x = 0; x < width; x++) { 2302 for (int y = 0; y < height; y++) { 2303 if (gradientMap[x][y] == FLOOR) { 2304 gradientMap[x][y] = DARK; 2305 } 2306 } 2307 } 2308 2309 clearGoals(); 2310 2311 Coord tempPt; 2312 OrderedMap<Coord, ArrayList<Coord>> ideal; 2313 // generate an array of the single best location to attack when you are in a given cell. 2314 for (int x = 0; x < width; x++) { 2315 CELL: 2316 for (int y = 0; y < height; y++) { 2317 tempPt = Coord.get(x, y); 2318 if (gradientMap[x][y] == WALL || gradientMap[x][y] == DARK || userDistanceMap[x][y] > moveLength * 2.0) 2319 continue; 2320 if (gradientMap[x][y] >= tech.aoe.getMinRange() && gradientMap[x][y] <= tech.aoe.getMaxRange()) { 2321 for (Coord tgt : targets) { 2322 if (los == null || los.isReachable(resMap, x, y, tgt.x, tgt.y)) { 2323 ideal = tech.idealLocations(tempPt, targets, friends); 2324 if (!ideal.isEmpty()) { 2325 targetMap[x][y] = ideal.keyAt(0); 2326 worthMap[x][y] = ideal.getAt(0).size(); 2327 setGoal(x, y); 2328 } 2329 continue CELL; 2330 } 2331 } 2332 gradientMap[x][y] = FLOOR; 2333 } else 2334 gradientMap[x][y] = FLOOR; 2335 } 2336 } 2337 scan(null,impassable2); 2338 2339 double currentDistance = gradientMap[start.x][start.y]; 2340 if (currentDistance <= moveLength) { 2341 int[] g_arr = goals.toArray(); 2342 2343 goals.clear(); 2344 setGoal(start); 2345 scan(null, impassable2, false); 2346 gradientMap[start.x][start.y] = moveLength; 2347 int decX, decY; 2348 double bestWorth = 0.0; 2349 for (int g, ig = 0; ig < g_arr.length; ig++) { 2350 g = g_arr[ig]; 2351 decX = decodeX(g); 2352 decY = decodeY(g); 2353 if (gradientMap[decX][decY] <= moveLength && worthMap[decX][decY] > bestWorth) { 2354 goals.clear(); 2355 goals.add(g); 2356 bestWorth = worthMap[decX][decY]; 2357 } 2358 else if (gradientMap[decX][decY] <= moveLength && bestWorth > 0 && worthMap[decX][decY] == bestWorth) 2359 { 2360 goals.add(g); 2361 } 2362 } 2363 resetMap(); 2364 /* for(Coord g : goals.keySet()) 2365 { 2366 gradientMap[g.x][g.y] = 0.0 - worthMap[g.x][g.y]; 2367 }*/ 2368 scan(impassable2); 2369 2370 } 2371 2372 measurement = mess; 2373 2374 Coord currentPos = Coord.get(start.x, start.y); 2375 while (true) { 2376 if (frustration > 500) { 2377 path.clear(); 2378 break; 2379 } 2380 double best = gradientMap[currentPos.x][currentPos.y]; 2381 appendDirToShuffle(rng); 2382 int choice = 0; 2383 2384 for (int d = 0; d <= measurement.directionCount(); d++) { 2385 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 2386 if(!pt.isWithin(width, height)) 2387 continue; 2388 if (gradientMap[pt.x][pt.y] < best && !impassable2.contains(pt)) { 2389 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 2390 best = gradientMap[pt.x][pt.y]; 2391 choice = d; 2392 } 2393 } 2394 } 2395 if (best >= gradientMap[currentPos.x][currentPos.y]) { 2396 if (friends.contains(currentPos)) { 2397 tempSet.clear(); 2398 tempSet.addAll(impassable2); 2399 tempSet.add(currentPos); 2400 return findTechniquePath(buffer, moveLength, tech, dungeon, los, tempSet, 2401 friends, start, targets); 2402 } 2403 break; 2404 } 2405 if (best > gradientMap[start.x][start.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 2406 cutShort = true; 2407 frustration = 0; 2408 if(buffer == null) 2409 return new ArrayList<>(path); 2410 else 2411 { 2412 buffer.addAll(path); 2413 return buffer; 2414 } 2415 } 2416 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 2417 path.add(currentPos); 2418 paidLength += costMap[currentPos.x][currentPos.y]; 2419 frustration++; 2420 if (paidLength > moveLength - 1.0) { 2421 if (friends.contains(currentPos)) { 2422 tempSet.clear(); 2423 tempSet.addAll(impassable2); 2424 tempSet.add(currentPos); 2425 return findTechniquePath(buffer, moveLength, tech, dungeon, los, tempSet, 2426 friends, start, targets); 2427 } 2428 break; 2429 } 2430// if(gradientMap[currentPos.x][currentPos.y] == 0) 2431// break; 2432 } 2433 cutShort = false; 2434 frustration = 0; 2435 goals.clear(); 2436 if(buffer == null) 2437 return new ArrayList<>(path); 2438 else 2439 { 2440 buffer.addAll(path); 2441 return buffer; 2442 } 2443 } 2444 2445 2446 private double cachedLongerPaths = 1.2; 2447 private HashSet<Coord> cachedImpassable = new HashSet<>(32, 0.25f); 2448 private Coord[] cachedFearSources; 2449 private double[][] cachedFleeMap; 2450 private int cachedSize = 1; 2451 2452 /** 2453 * Scans the dungeon using DijkstraMap.scan with the listed fearSources and start point, and returns a list 2454 * of Coord positions (using Manhattan distance) needed to get further from the closest fearSources, meant 2455 * for running away. The maximum length of the returned list is given by length; if moving the full 2456 * length of the list would place the mover in a position shared by one of the positions in onlyPassable 2457 * (which is typically filled with friendly units that can be passed through in multi-tile- 2458 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 2459 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2460 * through, and will be ignored if there is a fearSource overlapping one. The preferLongerPaths parameter 2461 * is meant to be tweaked and adjusted; higher values should make creatures prefer to escape out of 2462 * doorways instead of hiding in the closest corner, and a value of 1.2 should be typical for many maps. 2463 * The parameters preferLongerPaths, impassable, and the varargs used for fearSources will be cached, and 2464 * any subsequent calls that use the same values as the last values passed will avoid recalculating 2465 * unnecessary scans. 2466 * <br> 2467 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 2468 * each call to a pathfinding method. 2469 * 2470 * @param length the length of the path to calculate 2471 * @param preferLongerPaths Set this to 1.2 if you aren't sure; it will probably need tweaking for different maps. 2472 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 2473 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 2474 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 2475 * @param fearSources a vararg or array of Coord positions to run away from 2476 * @return an ArrayList of Coord that will contain the locations of this creature as it goes away from fear sources. Copy of path. 2477 */ 2478 public ArrayList<Coord> findFleePath(int length, double preferLongerPaths, Collection<Coord> impassable, 2479 Collection<Coord> onlyPassable, Coord start, Coord... fearSources) { 2480 return findFleePath(null, length, -1, preferLongerPaths, impassable, onlyPassable, start, fearSources); 2481 } 2482 2483 /** 2484 * Scans the dungeon using DijkstraMap.scan or DijkstraMap.partialScan with the listed fearSources and start 2485 * point, and returns a list of Coord positions (using this DijkstraMap's metric) needed to get further from 2486 * the closest fearSources, meant for running away. The maximum length of the returned list is given by length, 2487 * which represents movement in a system where a single move can be multiple cells if length is greater than 1 and 2488 * should usually be 1 in standard roguelikes; if moving the full length of the list would place the mover in a 2489 * position shared by one of the positions in onlyPassable (which is typically filled with friendly units that can 2490 * be passed through in multi-cell-movement scenarios), it will recalculate a move so that it does not pass into 2491 * that cell. The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2492 * through, and will be ignored if there is a fearSource overlapping one. The preferLongerPaths parameter 2493 * is meant to be tweaked and adjusted; higher values should make creatures prefer to escape out of 2494 * doorways instead of hiding in the closest corner, and a value of 1.2 should be typical for many maps. 2495 * The parameters preferLongerPaths, impassable, and the varargs used for fearSources will be cached, and 2496 * any subsequent calls that use the same values as the last values passed will avoid recalculating 2497 * unnecessary scans. However, scanLimit is not cached; if you use scanLimit then it is assumed you are using some 2498 * value for it that shouldn't change relative to the other parameters (like twice the length). 2499 * The full map will only be scanned if scanLimit is 0 or less; for positive scanLimit values this will scan only 2500 * that distance out from each goal, which can save processing time on maps where only a small part matters. 2501 * Generally, scanLimit should be significantly greater than length. 2502 * <br> 2503 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 2504 * each call to a pathfinding method. 2505 * 2506 * @param length the length of the path to calculate 2507 * @param scanLimit how many steps away from a fear source to calculate; negative scans the whole map 2508 * @param preferLongerPaths Set this to 1.2 if you aren't sure; it will probably need tweaking for different maps. 2509 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 2510 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 2511 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 2512 * @param fearSources a vararg or array of Coord positions to run away from 2513 * @return an ArrayList of Coord that will contain the locations of this creature as it goes away from fear sources. Copy of path. 2514 */ 2515 public ArrayList<Coord> findFleePath(int length, int scanLimit, double preferLongerPaths, Collection<Coord> impassable, 2516 Collection<Coord> onlyPassable, Coord start, Coord... fearSources) { 2517 return findFleePath(null, length, scanLimit, preferLongerPaths, impassable, onlyPassable, start, fearSources); 2518 } 2519 /** 2520 * Scans the dungeon using DijkstraMap.scan or DijkstraMap.partialScan with the listed fearSources and start 2521 * point, and returns a list of Coord positions (using this DijkstraMap's metric) needed to get further from 2522 * the closest fearSources, meant for running away. The maximum length of the returned list is given by length, 2523 * which represents movement in a system where a single move can be multiple cells if length is greater than 1 and 2524 * should usually be 1 in standard roguelikes; if moving the full length of the list would place the mover in a 2525 * position shared by one of the positions in onlyPassable (which is typically filled with friendly units that can 2526 * be passed through in multi-cell-movement scenarios), it will recalculate a move so that it does not pass into 2527 * that cell. The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2528 * through, and will be ignored if there is a fearSource overlapping one. The preferLongerPaths parameter 2529 * is meant to be tweaked and adjusted; higher values should make creatures prefer to escape out of 2530 * doorways instead of hiding in the closest corner, and a value of 1.2 should be typical for many maps. 2531 * The parameters preferLongerPaths, impassable, and the varargs used for fearSources will be cached, and 2532 * any subsequent calls that use the same values as the last values passed will avoid recalculating 2533 * unnecessary scans. However, scanLimit is not cached; if you use scanLimit then it is assumed you are using some 2534 * value for it that shouldn't change relative to the other parameters (like twice the length). 2535 * The full map will only be scanned if scanLimit is 0 or less; for positive scanLimit values this will scan only 2536 * that distance out from each goal, which can save processing time on maps where only a small part matters. 2537 * Generally, scanLimit should be significantly greater than length. 2538 * <br> 2539 * This overload takes a buffer parameter, an ArrayList of Coord, that the results will be appended to. If the 2540 * buffer is null, a new ArrayList will be made and appended to. This caches its result in a member field, path, 2541 * which can be fetched after finding a path and will change with each call to a pathfinding method. Any existing 2542 * contents of buffer will not affect the path field of this DijkstraMap. 2543 * 2544 * @param buffer an existing ArrayList of Coord that will have the result appended to it (in-place); if null, this will make a new ArrayList 2545 * @param length the length of the path to calculate 2546 * @param scanLimit how many steps away from a fear source to calculate; negative scans the whole map 2547 * @param preferLongerPaths Set this to 1.2 if you aren't sure; it will probably need tweaking for different maps. 2548 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 2549 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 2550 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 2551 * @param fearSources a vararg or array of Coord positions to run away from 2552 * @return an ArrayList of Coord that will contain the locations of this creature as it goes away from fear sources. Copy of path. 2553 */ 2554 public ArrayList<Coord> findFleePath(ArrayList<Coord> buffer, int length, int scanLimit, double preferLongerPaths, Collection<Coord> impassable, 2555 Collection<Coord> onlyPassable, Coord start, Coord... fearSources) { 2556 if (!initialized || length <= 0) 2557 { 2558 cutShort = true; 2559 if(buffer == null) 2560 return new ArrayList<>(); 2561 else 2562 { 2563 return buffer; 2564 } 2565 } 2566 path.clear(); 2567 if (fearSources == null || fearSources.length < 1) { 2568 cutShort = true; 2569 if(buffer == null) 2570 return new ArrayList<>(); 2571 else 2572 { 2573 return buffer; 2574 } 2575 } 2576 2577 if (impassable == null) 2578 impassable2.clear(); 2579 else 2580 { 2581 impassable2.clear(); 2582 impassable2.addAll(impassable); 2583 } 2584 if(onlyPassable != null && length == 1) 2585 impassable2.addAll(onlyPassable); 2586 if (cachedSize == 1 && preferLongerPaths == cachedLongerPaths && impassable2.equals(cachedImpassable) && 2587 Arrays.equals(fearSources, cachedFearSources)) { 2588 gradientMap = cachedFleeMap; 2589 } else { 2590 cachedLongerPaths = preferLongerPaths; 2591 cachedImpassable.clear(); 2592 cachedImpassable.addAll(impassable2); 2593 cachedFearSources = new Coord[fearSources.length]; 2594 System.arraycopy(fearSources, 0, cachedFearSources, 0, fearSources.length); 2595 cachedSize = 1; 2596 resetMap(); 2597 setGoals(fearSources); 2598 if (goals.isEmpty()) 2599 { 2600 cutShort = true; 2601 if(buffer == null) 2602 return new ArrayList<>(); 2603 else 2604 { 2605 return buffer; 2606 } 2607 } 2608 2609 if(scanLimit <= 0 || scanLimit < length) 2610 cachedFleeMap = scan(impassable2); 2611 else 2612 cachedFleeMap = partialScan(scanLimit, impassable2); 2613 2614 2615 for (int x = 0; x < gradientMap.length; x++) { 2616 for (int y = 0; y < gradientMap[x].length; y++) { 2617 gradientMap[x][y] *= (gradientMap[x][y] >= FLOOR) ? 1.0 : -preferLongerPaths; 2618 } 2619 } 2620 2621 if(scanLimit <= 0 || scanLimit < length) 2622 { 2623 scan(null, impassable2, true); 2624 for (int x = 0; x < width; x++) { 2625 for (int y = 0; y < height; y++) { 2626 if (gradientMap[x][y] == FLOOR) { 2627 gradientMap[x][y] = DARK; 2628 } 2629 } 2630 System.arraycopy(gradientMap[x], 0, cachedFleeMap[x], 0, height); 2631 } 2632 } 2633 else 2634 cachedFleeMap = partialScan(scanLimit, impassable2); 2635 } 2636 Coord currentPos = start; 2637 double paidLength = 0.0; 2638 while (true) { 2639 if (frustration > 500) { 2640 path.clear(); 2641 break; 2642 } 2643 double best = gradientMap[currentPos.x][currentPos.y]; 2644 appendDirToShuffle(rng); 2645 int choice = 0; 2646 2647 for (int d = 0; d <= measurement.directionCount(); d++) { 2648 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 2649 if(!pt.isWithin(width, height)) 2650 continue; 2651 if (gradientMap[pt.x][pt.y] < best && !impassable2.contains(pt)) { 2652 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 2653 best = gradientMap[pt.x][pt.y]; 2654 choice = d; 2655 } 2656 } 2657 } 2658 if (best >= gradientMap[start.x][start.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 2659 cutShort = true; 2660 frustration = 0; 2661 if(buffer == null) 2662 return new ArrayList<>(path); 2663 else 2664 { 2665 buffer.addAll(path); 2666 return buffer; 2667 } 2668 } 2669 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 2670 if (path.size() > 0) { 2671 Coord last = path.get(path.size() - 1); 2672 if (gradientMap[last.x][last.y] <= gradientMap[currentPos.x][currentPos.y]) 2673 break; 2674 } 2675 path.add(currentPos); 2676 frustration++; 2677 paidLength += costMap[currentPos.x][currentPos.y]; 2678 if (paidLength > length - 1.0) { 2679 if (onlyPassable != null && onlyPassable.contains(currentPos)) { 2680 tempSet.clear(); 2681 tempSet.addAll(impassable2); 2682 tempSet.add(currentPos); 2683 return findFleePath(buffer, length, scanLimit, preferLongerPaths, tempSet, onlyPassable, start, fearSources); 2684 } 2685 break; 2686 } 2687 } 2688 cutShort = false; 2689 frustration = 0; 2690 goals.clear(); 2691 if(buffer == null) 2692 return new ArrayList<>(path); 2693 else 2694 { 2695 buffer.addAll(path); 2696 return buffer; 2697 } 2698 } 2699 2700 /** 2701 * For pathfinding creatures larger than 1x1 cell; scans the dungeon using DijkstraMap.scan with the listed goals 2702 * and start point, and returns a list 2703 * of Coord positions (using the current measurement) needed to get closer to the closest reachable 2704 * goal. The maximum length of the returned list is given by length; if moving the full length of 2705 * the list would place the mover in a position shared by one of the positions in onlyPassable 2706 * (which is typically filled with friendly units that can be passed through in multi-tile- 2707 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 2708 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2709 * through, and will be ignored if there is a goal overlapping one. 2710 * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The 2711 * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and 2712 * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. 2713 * <br> 2714 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 2715 * each call to a pathfinding method. 2716 * 2717 * @param size the side length of the creature trying to find a path 2718 * @param length the length of the path to calculate 2719 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 2720 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 2721 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 2722 * @param targets a vararg or array of Coord that this will try to pathfind toward 2723 * @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. 2724 */ 2725 2726 public ArrayList<Coord> findPathLarge(final int size, int length, Collection<Coord> impassable, 2727 Collection<Coord> onlyPassable, Coord start, Coord... targets) { 2728 return findPathLarge(size, length, -1, impassable, onlyPassable, start, targets); 2729 } 2730 public ArrayList<Coord> findPathLarge(final int size, int length, final int scanLimit, Collection<Coord> impassable, 2731 Collection<Coord> onlyPassable, Coord start, Coord... targets) { 2732 2733 if (!initialized) return null; 2734 path.clear(); 2735 if (impassable == null) 2736 impassable2.clear(); 2737 else 2738 { 2739 impassable2.clear(); 2740 impassable2.addAll(impassable); 2741 } 2742 if(onlyPassable != null && length == 1) 2743 impassable2.addAll(onlyPassable); 2744 2745 resetMap(); 2746 for (Coord goal : targets) { 2747 setGoal(goal.x, goal.y); 2748 } 2749 if (goals.isEmpty()) 2750 { 2751 cutShort = true; 2752 return new ArrayList<>(path); 2753 } 2754 2755 if(length < 0) 2756 length = 0; 2757 if(scanLimit <= 0 || scanLimit < length) 2758 scan(start, impassable2, size); 2759 else 2760 partialScan(scanLimit, start, impassable2, size); 2761 2762 Coord currentPos = start; 2763 double paidLength = 0.0; 2764 while (true) { 2765 if (frustration > 500) { 2766 path.clear(); 2767 break; 2768 } 2769 double best = gradientMap[currentPos.x][currentPos.y]; 2770 appendDirToShuffle(rng); 2771 int choice = 0; 2772 2773 for (int d = 0; d <= measurement.directionCount(); d++) { 2774 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 2775 if(!pt.isWithin(width, height)) 2776 continue; 2777 if (gradientMap[pt.x][pt.y] < best && !impassable2.contains(pt)) { 2778 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 2779 best = gradientMap[pt.x][pt.y]; 2780 choice = d; 2781 } 2782 } 2783 } 2784 2785 if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 2786 cutShort = true; 2787 frustration = 0; 2788 return new ArrayList<>(path); 2789 } 2790 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 2791 2792 path.add(currentPos); 2793 paidLength += costMap[currentPos.x][currentPos.y]; 2794 frustration++; 2795 if (paidLength > length - 1.0) { 2796 if (onlyPassable != null && onlyPassable.contains(currentPos)) { 2797 tempSet.clear(); 2798 tempSet.addAll(impassable2); 2799 tempSet.add(currentPos); 2800 return findPathLarge(size, length, tempSet, onlyPassable, start, targets); 2801 } 2802 break; 2803 } 2804 if (gradientMap[currentPos.x][currentPos.y] == 0) 2805 break; 2806 } 2807 cutShort = false; 2808 frustration = 0; 2809 goals.clear(); 2810 return new ArrayList<>(path); 2811 } 2812 2813 /** 2814 * For pathfinding creatures larger than 1x1 cell; scans the dungeon using DijkstraMap.scan with the listed goals 2815 * and start point, and returns a list 2816 * of Coord positions (using the current measurement) needed to get closer to a goal, until preferredRange is 2817 * reached, or further from a goal if the preferredRange has not been met at the current distance. 2818 * The maximum length of the returned list is given by moveLength; if moving the full length of 2819 * the list would place the mover in a position shared by one of the positions in onlyPassable 2820 * (which is typically filled with friendly units that can be passed through in multi-tile- 2821 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 2822 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2823 * through, and will be ignored if there is a goal overlapping one. 2824 * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The 2825 * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and 2826 * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. 2827 * <br> 2828 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 2829 * each call to a pathfinding method. 2830 * 2831 * @param size the side length of the creature trying to find a path 2832 * @param moveLength the length of the path to calculate 2833 * @param preferredRange the distance this unit will try to keep from a target 2834 * @param los a squidgrid.LOS object if the preferredRange should try to stay in line of sight, or null if LoS 2835 * should be disregarded. 2836 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 2837 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 2838 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 2839 * @param targets a vararg or array of Coord that this will try to pathfind toward 2840 * @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. 2841 */ 2842 public ArrayList<Coord> findAttackPathLarge(int size, int moveLength, int preferredRange, LOS los, Collection<Coord> impassable, 2843 Collection<Coord> onlyPassable, Coord start, Coord... targets) { 2844 if (!initialized) return null; 2845 if (preferredRange < 0) preferredRange = 0; 2846 double[][] resMap = new double[width][height]; 2847 if (los != null) { 2848 for (int x = 0; x < width; x++) { 2849 for (int y = 0; y < height; y++) { 2850 resMap[x][y] = (physicalMap[x][y] == WALL) ? 1.0 : 0.0; 2851 } 2852 } 2853 } 2854 path.clear(); 2855 if (impassable == null) 2856 impassable2.clear(); 2857 else 2858 { 2859 impassable2.clear(); 2860 impassable2.addAll(impassable); 2861 } 2862 if(onlyPassable != null && moveLength == 1) 2863 impassable2.addAll(onlyPassable); 2864 2865 resetMap(); 2866 for (Coord goal : targets) { 2867 setGoal(goal.x, goal.y); 2868 } 2869 if (goals.isEmpty()) 2870 { 2871 cutShort = true; 2872 return new ArrayList<>(path); 2873 } 2874 2875 Measurement mess = measurement; 2876 if (measurement == Measurement.EUCLIDEAN) { 2877 measurement = Measurement.CHEBYSHEV; 2878 } 2879 scan(impassable2, size); 2880 goals.clear(); 2881 2882 for (int x = 0; x < width; x++) { 2883 CELL: 2884 for (int y = 0; y < height; y++) { 2885 if (gradientMap[x][y] == WALL || gradientMap[x][y] == DARK) 2886 continue; 2887 if (x + 2 < width && y + 2 < height && gradientMap[x][y] == preferredRange) { 2888 for (Coord goal : targets) { 2889 if (los == null 2890 || los.isReachable(resMap, x, y, goal.x, goal.y) 2891 || los.isReachable(resMap, x + 1, y, goal.x, goal.y) 2892 || los.isReachable(resMap, x, y + 1, goal.x, goal.y) 2893 || los.isReachable(resMap, x + 1, y + 1, goal.x, goal.y)) { 2894 setGoal(x, y); 2895 gradientMap[x][y] = 0; 2896 continue CELL; 2897 } 2898 } 2899 gradientMap[x][y] = FLOOR; 2900 } else 2901 gradientMap[x][y] = FLOOR; 2902 } 2903 } 2904 measurement = mess; 2905 scan(impassable2, size); 2906 2907 Coord currentPos = start; 2908 double paidLength = 0.0; 2909 while (true) { 2910 if (frustration > 500) { 2911 path.clear(); 2912 break; 2913 } 2914 double best = gradientMap[currentPos.x][currentPos.y]; 2915 appendDirToShuffle(rng); 2916 int choice = 0; 2917 2918 for (int d = 0; d <= measurement.directionCount(); d++) { 2919 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 2920 if(!pt.isWithin(width, height)) 2921 continue; 2922 if (gradientMap[pt.x][pt.y] < best && !impassable2.contains(pt)) { 2923 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 2924 best = gradientMap[pt.x][pt.y]; 2925 choice = d; 2926 } 2927 } 2928 } 2929 2930 if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 2931 cutShort = true; 2932 frustration = 0; 2933 return new ArrayList<>(path); 2934 } 2935 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 2936 path.add(currentPos); 2937 frustration++; 2938 paidLength += costMap[currentPos.x][currentPos.y]; 2939 if (paidLength > moveLength - 1.0) { 2940 if (onlyPassable != null && onlyPassable.contains(currentPos)) { 2941 tempSet.clear(); 2942 tempSet.addAll(impassable2); 2943 tempSet.add(currentPos); 2944 return findAttackPathLarge(size, moveLength, preferredRange, los, tempSet, onlyPassable, start, targets); 2945 } 2946 break; 2947 } 2948 if (gradientMap[currentPos.x][currentPos.y] == 0) 2949 break; 2950 } 2951 cutShort = false; 2952 frustration = 0; 2953 goals.clear(); 2954 return new ArrayList<>(path); 2955 } 2956 2957 /** 2958 * Scans the dungeon using DijkstraMap.scan with the listed goals and start point, and returns a list 2959 * of Coord positions (using the current measurement) needed to get closer to a goal, until a cell is reached with 2960 * a distance from a goal that is at least equal to minPreferredRange and no more than maxPreferredRange, 2961 * which may go further from a goal if the minPreferredRange has not been met at the current distance. 2962 * The maximum length of the returned list is given by moveLength; if moving the full length of 2963 * the list would place the mover in a position shared by one of the positions in onlyPassable 2964 * (which is typically filled with friendly units that can be passed through in multi-tile- 2965 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 2966 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2967 * through, and will be ignored if there is a goal overlapping one. 2968 * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The 2969 * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and 2970 * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. 2971 * <br> 2972 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 2973 * each call to a pathfinding method. 2974 * 2975 * @param size the side length of the creature trying to find a path 2976 * @param moveLength the length of the path to calculate 2977 * @param minPreferredRange the (inclusive) lower bound of the distance this unit will try to keep from a target 2978 * @param maxPreferredRange the (inclusive) upper bound of the distance this unit will try to keep from a target 2979 * @param los a squidgrid.LOS object if the preferredRange should try to stay in line of sight, or null if LoS 2980 * should be disregarded. 2981 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 2982 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 2983 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 2984 * @param targets a vararg or array of Coord that this will try to pathfind toward 2985 * @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. 2986 */ 2987 public ArrayList<Coord> findAttackPathLarge(int size, int moveLength, int minPreferredRange, int maxPreferredRange, LOS los, 2988 Collection<Coord> impassable, Collection<Coord> onlyPassable, Coord start, Coord... targets) { 2989 if (!initialized) return null; 2990 if (minPreferredRange < 0) minPreferredRange = 0; 2991 if (maxPreferredRange < minPreferredRange) maxPreferredRange = minPreferredRange; 2992 double[][] resMap = new double[width][height]; 2993 if (los != null) { 2994 for (int x = 0; x < width; x++) { 2995 for (int y = 0; y < height; y++) { 2996 resMap[x][y] = (physicalMap[x][y] == WALL) ? 1.0 : 0.0; 2997 } 2998 } 2999 } 3000 path.clear(); 3001 if (impassable == null) 3002 impassable2.clear(); 3003 else 3004 { 3005 impassable2.clear(); 3006 impassable2.addAll(impassable); 3007 } 3008 if(onlyPassable != null && moveLength == 1) 3009 impassable2.addAll(onlyPassable); 3010 3011 resetMap(); 3012 for (Coord goal : targets) { 3013 setGoal(goal); 3014 } 3015 if (goals.isEmpty()) 3016 { 3017 cutShort = true; 3018 return new ArrayList<>(path); 3019 } 3020 3021 Measurement mess = measurement; 3022 if (measurement == Measurement.EUCLIDEAN) { 3023 measurement = Measurement.CHEBYSHEV; 3024 } 3025 scan(impassable2, size); 3026 goals.clear(); 3027 3028 for (int x = 0; x < width; x++) { 3029 CELL: 3030 for (int y = 0; y < height; y++) { 3031 if (gradientMap[x][y] == WALL || gradientMap[x][y] == DARK) 3032 continue; 3033 if (x + 2 < width && y + 2 < height && gradientMap[x][y] >= minPreferredRange && gradientMap[x][y] <= maxPreferredRange) { 3034 for (Coord goal : targets) { 3035 if (los == null 3036 || los.isReachable(resMap, x, y, goal.x, goal.y) 3037 || los.isReachable(resMap, x + 1, y, goal.x, goal.y) 3038 || los.isReachable(resMap, x, y + 1, goal.x, goal.y) 3039 || los.isReachable(resMap, x + 1, y + 1, goal.x, goal.y)) { 3040 setGoal(x, y); 3041 gradientMap[x][y] = 0; 3042 continue CELL; 3043 } 3044 } 3045 gradientMap[x][y] = FLOOR; 3046 } else 3047 gradientMap[x][y] = FLOOR; 3048 } 3049 } 3050 measurement = mess; 3051 scan(impassable2, size); 3052 3053 Coord currentPos = start; 3054 double paidLength = 0.0; 3055 while (true) { 3056 if (frustration > 500) { 3057 path.clear(); 3058 break; 3059 } 3060 3061 double best = gradientMap[currentPos.x][currentPos.y]; 3062 appendDirToShuffle(rng); 3063 int choice = 0; 3064 3065 for (int d = 0; d <= measurement.directionCount(); d++) { 3066 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 3067 if(!pt.isWithin(width, height)) 3068 continue; 3069 if (gradientMap[pt.x][pt.y] < best && !impassable2.contains(pt)) { 3070 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 3071 best = gradientMap[pt.x][pt.y]; 3072 choice = d; 3073 } 3074 } 3075 } 3076 if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 3077 cutShort = true; 3078 frustration = 0; 3079 return new ArrayList<>(path); 3080 } 3081 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 3082 3083 path.add(currentPos); 3084 frustration++; 3085 paidLength += costMap[currentPos.x][currentPos.y]; 3086 if (paidLength > moveLength - 1.0) { 3087 if (onlyPassable != null && onlyPassable.contains(currentPos)) { 3088 tempSet.clear(); 3089 tempSet.addAll(impassable2); 3090 tempSet.add(currentPos); 3091 return findAttackPathLarge(size, moveLength, minPreferredRange, maxPreferredRange, los, tempSet, 3092 onlyPassable, start, targets); 3093 } 3094 break; 3095 } 3096 if (gradientMap[currentPos.x][currentPos.y] == 0) 3097 break; 3098 } 3099 cutShort = false; 3100 frustration = 0; 3101 goals.clear(); 3102 return new ArrayList<>(path); 3103 } 3104 3105 /** 3106 * Scans the dungeon using DijkstraMap.scan with the listed fearSources and start point, and returns a list 3107 * of Coord positions (using Manhattan distance) needed to get further from the closest fearSources, meant 3108 * for running away. The maximum length of the returned list is given by length; if moving the full 3109 * length of the list would place the mover in a position shared by one of the positions in onlyPassable 3110 * (which is typically filled with friendly units that can be passed through in multi-tile- 3111 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 3112 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 3113 * through, and will be ignored if there is a fearSource overlapping one. The preferLongerPaths parameter 3114 * is meant to be tweaked and adjusted; higher values should make creatures prefer to escape out of 3115 * doorways instead of hiding in the closest corner, and a value of 1.2 should be typical for many maps. 3116 * The parameters size, preferLongerPaths, impassable, and the varargs used for fearSources will be cached, and 3117 * any subsequent calls that use the same values as the last values passed will avoid recalculating 3118 * unnecessary scans. Calls to findFleePath will cache as if size is 1, and may share a cache with this function. 3119 * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The 3120 * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and 3121 * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. 3122 * <br> 3123 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 3124 * each call to a pathfinding method. 3125 * 3126 * @param size the side length of the creature trying the find a path 3127 * @param length the length of the path to calculate 3128 * @param preferLongerPaths Set this to 1.2 if you aren't sure; it will probably need tweaking for different maps. 3129 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 3130 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 3131 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 3132 * @param fearSources a vararg or array of Coord positions to run away from 3133 * @return an ArrayList of Coord that will contain the locations of this creature as it goes away from fear sources. Copy of path. 3134 */ 3135 public ArrayList<Coord> findFleePathLarge(int size, int length, double preferLongerPaths, Collection<Coord> impassable, 3136 Collection<Coord> onlyPassable, Coord start, Coord... fearSources) { 3137 if (!initialized) return null; 3138 path.clear(); 3139 if (impassable == null) 3140 impassable2.clear(); 3141 else { 3142 impassable2.clear(); 3143 impassable2.addAll(impassable); 3144 } 3145 if(onlyPassable != null && length == 1) 3146 impassable2.addAll(onlyPassable); 3147 if (fearSources == null || fearSources.length < 1) { 3148 cutShort = true; 3149 return new ArrayList<>(path); 3150 } 3151 if (size == cachedSize && preferLongerPaths == cachedLongerPaths && impassable2.equals(cachedImpassable) 3152 && Arrays.equals(fearSources, cachedFearSources)) { 3153 gradientMap = cachedFleeMap; 3154 } else { 3155 cachedLongerPaths = preferLongerPaths; 3156 cachedImpassable.clear(); 3157 cachedImpassable.addAll(impassable2); 3158 cachedFearSources = new Coord[fearSources.length]; 3159 System.arraycopy(fearSources, 0, cachedFearSources, 0, fearSources.length); 3160 cachedSize = size; 3161 resetMap(); 3162 setGoals(fearSources); 3163 if (goals.isEmpty()) 3164 { 3165 cutShort = true; 3166 return new ArrayList<>(path); 3167 } 3168 3169 scan(impassable2, size); 3170 3171 for (int x = 0; x < gradientMap.length; x++) { 3172 for (int y = 0; y < gradientMap[x].length; y++) { 3173 gradientMap[x][y] *= (gradientMap[x][y] >= FLOOR) ? 1.0 : (0.0 - preferLongerPaths); 3174 } 3175 } 3176 cachedFleeMap = scan(impassable2, size); 3177 } 3178 Coord currentPos = start; 3179 double paidLength = 0.0; 3180 while (true) { 3181 if (frustration > 500) { 3182 path.clear(); 3183 break; 3184 } 3185 3186 double best = gradientMap[currentPos.x][currentPos.y]; 3187 appendDirToShuffle(rng); 3188 int choice = 0; 3189 3190 for (int d = 0; d <= measurement.directionCount(); d++) { 3191 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 3192 if(!pt.isWithin(width, height)) 3193 continue; 3194 if (gradientMap[pt.x][pt.y] < best && !impassable2.contains(pt)) { 3195 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 3196 best = gradientMap[pt.x][pt.y]; 3197 choice = d; 3198 } 3199 } 3200 } 3201 if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 3202 cutShort = true; 3203 frustration = 0; 3204 return new ArrayList<>(path); 3205 } 3206 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 3207 3208 if (path.size() > 0) { 3209 Coord last = path.get(path.size() - 1); 3210 if (gradientMap[last.x][last.y] <= gradientMap[currentPos.x][currentPos.y]) 3211 break; 3212 } 3213 path.add(currentPos); 3214 frustration++; 3215 paidLength += costMap[currentPos.x][currentPos.y]; 3216 if (paidLength > length - 1.0) { 3217 if (onlyPassable != null && onlyPassable.contains(currentPos)) { 3218 tempSet.clear(); 3219 tempSet.addAll(impassable2); 3220 tempSet.add(currentPos); 3221 return findFleePathLarge(size, length, preferLongerPaths, tempSet, onlyPassable, start, fearSources); 3222 } 3223 break; 3224 } 3225 } 3226 cutShort = false; 3227 frustration = 0; 3228 goals.clear(); 3229 return new ArrayList<>(path); 3230 } 3231 3232 3233 /** 3234 * When you can control how often the (relatively time-intensive) scan() method is called, but may need simple paths 3235 * very frequently (such as for a path that follows the mouse), you can use this method to reduce the amount of work 3236 * needed to find paths. Needs scan() or partialScan() to already be called and at least one goal to already be set, 3237 * and does not restrict the length of the path or behave as if the pathfinder has allies or enemies. 3238 * <br> 3239 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 3240 * each call to a pathfinding method. 3241 * 3242 * @param target the target cell 3243 * @return an ArrayList of Coord that make up the best path. Copy of path. 3244 */ 3245 public ArrayList<Coord> findPathPreScanned(Coord target) { 3246 return findPathPreScanned(null, target); 3247 } 3248 /** 3249 * When you can control how often the (relatively time-intensive) scan() method is called, but may need simple paths 3250 * very frequently (such as for a path that follows the mouse), you can use this method to reduce the amount of work 3251 * needed to find paths. Needs scan() or partialScan() to already be called and at least one goal to already be set, 3252 * and does not restrict the length of the path or behave as if the pathfinder has allies or enemies. 3253 * <br> 3254 * This overload takes a buffer parameter, an ArrayList of Coord, that the results will be appended to. If the 3255 * buffer is null, a new ArrayList will be made and appended to. This caches its result in a member field, path, 3256 * which can be fetched after finding a path and will change with each call to a pathfinding method. Any existing 3257 * contents of buffer will not affect the path field of this DijkstraMap. 3258 * 3259 * @param buffer an existing ArrayList of Coord that will have the result appended to it (in-place); if null, this will make a new ArrayList 3260 * @param target the target cell 3261 * @return an ArrayList of Coord that make up the best path, appended to buffer (if non-null) 3262 */ 3263 public ArrayList<Coord> findPathPreScanned(ArrayList<Coord> buffer, Coord target) { 3264 path.clear(); 3265 if (!initialized || goals == null || goals.isEmpty()) 3266 { 3267 if(buffer == null) 3268 return new ArrayList<>(); 3269 else 3270 { 3271 return buffer; 3272 } 3273 } 3274 Coord currentPos = target; 3275 if(gradientMap[currentPos.x][currentPos.y] <= FLOOR) 3276 path.add(currentPos); 3277 else 3278 { 3279 if(buffer == null) 3280 return new ArrayList<>(); 3281 else 3282 { 3283 return buffer; 3284 } 3285 3286 } 3287 while (true) { 3288 if (frustration > 2000) { 3289 path.clear(); 3290 break; 3291 } 3292 double best = gradientMap[currentPos.x][currentPos.y]; 3293 appendDirToShuffle(rng); 3294 int choice = 0; 3295 3296 for (int d = 0; d <= measurement.directionCount(); d++) { 3297 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 3298 if(!pt.isWithin(width, height)) 3299 continue; 3300 if (gradientMap[pt.x][pt.y] < best) { 3301 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 3302 best = gradientMap[pt.x][pt.y]; 3303 choice = d; 3304 } 3305 } 3306 } 3307 3308 if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 3309 cutShort = true; 3310 frustration = 0; 3311 if(buffer == null) 3312 return new ArrayList<>(path); 3313 else 3314 { 3315 buffer.addAll(path); 3316 return buffer; 3317 } 3318 } 3319 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 3320 path.add(0, currentPos); 3321 frustration++; 3322 3323 if (gradientMap[currentPos.x][currentPos.y] == 0) 3324 break; 3325 } 3326 cutShort = false; 3327 frustration = 0; 3328 if(buffer == null) 3329 return new ArrayList<>(path); 3330 else 3331 { 3332 buffer.addAll(path); 3333 return buffer; 3334 } 3335 3336 } 3337 3338 /** 3339 * A simple limited flood-fill that returns a OrderedMap of Coord keys to the Double values in the DijkstraMap, only 3340 * calculating out to a number of steps determined by limit. This can be useful if you need many flood-fills and 3341 * don't need a large area for each, or if you want to have an effect spread to a certain number of cells away. 3342 * 3343 * @param radius the number of steps to take outward from each starting position. 3344 * @param starts a vararg group of Points to step outward from; this often will only need to be one Coord. 3345 * @return A OrderedMap of Coord keys to Double values; the starts are included in this with the value 0.0. 3346 */ 3347 public OrderedMap<Coord, Double> floodFill(int radius, Coord... starts) { 3348 if (!initialized) return null; 3349 OrderedMap<Coord, Double> fill = new OrderedMap<>(); 3350 3351 resetMap(); 3352 for (Coord goal : starts) { 3353 setGoal(goal.x, goal.y); 3354 } 3355 if (goals.isEmpty()) 3356 return fill; 3357 3358 partialScan(radius, null); 3359 double temp; 3360 for (int x = 1; x < width - 1; x++) { 3361 for (int y = 1; y < height - 1; y++) { 3362 temp = gradientMap[x][y]; 3363 if (temp < FLOOR) { 3364 fill.put(Coord.get(x, y), temp); 3365 } 3366 } 3367 } 3368 goals.clear(); 3369 return fill; 3370 } 3371 3372 public int getMappedCount() { 3373 return mappedCount; 3374 } 3375 3376 /** 3377 * If you want obstacles present in orthogonal cells to prevent pathfinding along the diagonal between them, this 3378 * can be used to make thin diagonal walls non-viable to move through, or even to prevent diagonal movement if any 3379 * one obstacle is orthogonally adjacent to both the start and target cell of a diagonal move. If you haven't set 3380 * this yet, then the default is 2. 3381 * <br> 3382 * If this is 0, as a special case no orthogonal obstacles will block diagonal moves. 3383 * <br> 3384 * If this is 1, having one orthogonal obstacle adjacent to both the current cell and the cell the pathfinder is 3385 * trying to diagonally enter will block diagonal moves. This generally blocks movement around corners, the "hard 3386 * corner" rule used in some games. 3387 * <br> 3388 * If this is 2 (the default), having two orthogonal obstacles adjacent to both the current cell and the cell the 3389 * pathfinder is trying to diagonally enter will block diagonal moves. As an example, if there is a wall to the 3390 * north and a wall to the east, then the pathfinder won't be able to move northeast even if there is a floor there. 3391 * @return the current level of blocking required to stop a diagonal move 3392 */ 3393 public int getBlockingRequirement() { 3394 return blockingRequirement; 3395 } 3396 3397 /** 3398 * If you want obstacles present in orthogonal cells to prevent pathfinding along the diagonal between them, this 3399 * can be used to make thin diagonal walls non-viable to move through, or even to prevent diagonal movement if any 3400 * one obstacle is orthogonally adjacent to both the start and target cell of a diagonal move. If you haven't set 3401 * this yet, then the default is 2. 3402 * <br> 3403 * If this is 0, as a special case no orthogonal obstacles will block diagonal moves. 3404 * <br> 3405 * If this is 1, having one orthogonal obstacle adjacent to both the current cell and the cell the pathfinder is 3406 * trying to diagonally enter will block diagonal moves. This generally blocks movement around corners, the "hard 3407 * corner" rule used in some games. 3408 * <br> 3409 * If this is 2 (the default), having two orthogonal obstacles adjacent to both the current cell and the cell the 3410 * pathfinder is trying to diagonally enter will block diagonal moves. As an example, if there is a wall to the 3411 * north and a wall to the east, then the pathfinder won't be able to move northeast even if there is a floor there. 3412 * @param blockingRequirement the desired level of blocking required to stop a diagonal move 3413 */ 3414 public void setBlockingRequirement(int blockingRequirement) { 3415 this.blockingRequirement = blockingRequirement > 2 ? 2 : Math.max(blockingRequirement, 0); 3416 } 3417 3418 private void appendDirToShuffle(IRNG rng) { 3419 final Direction[] src = measurement == Measurement.MANHATTAN 3420 ? Direction.CARDINALS : Direction.OUTWARDS; 3421 final int n = measurement.directionCount(); 3422 System.arraycopy(src, 0, dirs, 0, n); 3423 for (int i = n - 1; i > 0; i--) { 3424 final int r = rng.nextInt(i+1); 3425 Direction t = dirs[r]; 3426 dirs[r] = dirs[i]; 3427 dirs[i] = t; 3428 } 3429 dirs[n] = Direction.NONE; 3430 } 3431}