001package squidpony.squidai; 002 003import squidpony.squidgrid.Measurement; 004import squidpony.squidgrid.Adjacency; 005import squidpony.squidgrid.Adjacency.BasicAdjacency; 006import squidpony.squidgrid.Adjacency.RotationAdjacency; 007import squidpony.squidgrid.Radius; 008import squidpony.squidmath.*; 009 010import java.io.Serializable; 011 012/** 013 * An alternative to AStarSearch when you want to fully explore a search space, or when you want a gradient 014 * floodfill, with customizable rules for what is considered adjacent. This can be used for games where 015 * rotation matters (and possibly costs movement), for games with thin walls (where a wall between cells 016 * prevents travel between those two cells even if the wall doesn't occupy a walkable cell), for games where 017 * the edges between cells may have some requisite to travel across, like a vertical amount that must be 018 * hopped up or down between cells, and for games that have portals between distant cells on the same map. 019 * <br> 020 * As a bit of introduction, the article http://www.roguebasin.com/index.php?title=Dijkstra_Maps_Visualized can 021 * provide some useful information on how these work and how to visualize the information they can produce, while 022 * http://www.roguebasin.com/index.php?title=The_Incredible_Power_of_Dijkstra_Maps is an inspiring list of the 023 * various features Dijkstra Maps can enable. 024 * <br> 025 * If you can't remember how to spell this, just remember: Does It Just Know Stuff? That's Really Awesome! 026 * Created by Tommy Ettinger on 4/4/2015. 027 */ 028public class CustomDijkstraMap implements Serializable { 029 private static final long serialVersionUID = -2456306898212944440L; 030 031 /** 032 * The main factor in determining the "Custom" behavior of CustomDijkstraMap; using an Adjacency implementation like 033 * {@link BasicAdjacency} should cause this class to mimic {@link DijkstraMap}, but using 034 * {@link RotationAdjacency} will be very different. 035 */ 036 public Adjacency adjacency; 037 038 /** 039 * Stores which parts of the map are accessible and which are not. Should not be changed unless the actual physical 040 * terrain has changed. You should call initialize() with a new map instead of changing this directly. 041 */ 042 public double[] physicalMap; 043 /** 044 * The frequently-changing values that are often the point of using this class; goals will have a value of 0, and 045 * any cells that can have a character reach a goal in n steps will have a value of n. Cells that cannot be 046 * entered because they are solid will have a very high value equal to the WALL constant in this class, and cells 047 * that cannot be entered because they cannot reach a goal will have a different very high value equal to the 048 * DARK constant in this class. 049 */ 050 public double[] gradientMap; 051 /** 052 * This stores the type of each cell for the purposes of determining its cost to enter; in most cases this type is 053 * the char used to represent the cell, but any int can be used if you need more information. An int from costMap 054 * is looked up in {@link Adjacency#costRules} to get the actual cost as a double; this collection should almost 055 * always start with a reasonable default value for when the int key is not present. It's common to simply assign 056 * a char like '#' or '.' to an element in costMap. 057 */ 058 public int[] costMap; 059 060 /** 061 * The neighbors map, as produced by adjacency; can be modified by passing neighbors as the first argument to 062 * {@link Adjacency#portal(int[][][], int, int, boolean)} if you want to create portals between non-adjacent cells. 063 */ 064 public int[][][] neighbors; 065 /** 066 * Height of the map. Exciting stuff. Don't change this, instead call initialize(). 067 */ 068 public int height; 069 /** 070 * Width of the map. Exciting stuff. Don't change this, instead call initialize(). 071 */ 072 public int width; 073 /** 074 * The latest path that was obtained by calling findPath(). It will not contain the value passed as a starting 075 * cell; only steps that require movement will be included, and so if the path has not been found or a valid 076 * path toward a goal is impossible, this ArrayList will be empty. 077 */ 078 public IntVLA path; 079 /** 080 * Goals are always marked with 0. 081 */ 082 public static final double GOAL = 0; 083 /** 084 * Floor cells, which include any walkable cell, are marked with a high number equal to 999200 . 085 */ 086 public static final double FLOOR = 999200; 087 /** 088 * Walls, which are solid no-entry cells, are marked with a high number equal to 999500 . 089 */ 090 public static final double WALL = 999500; 091 /** 092 * This is used to mark cells that the scan couldn't reach, and these dark cells are marked with a high number 093 * equal to 999800 . 094 */ 095 public static final double DARK = 999800; 096 097 protected IntVLA goals = new IntVLA(256), fresh = new IntVLA(256); 098 099 /** 100 * The RNG used to decide which one of multiple equally-short paths to take. 101 */ 102 public IRNG rng; 103 private int frustration; 104 105 private int[] reuse = new int[9]; 106 107 private boolean initialized; 108 109 private int mappedCount; 110 private double[] heuristics; 111 112 /** 113 * Construct a CustomDijkstraMap without a level to actually scan. If you use this constructor, you must call an 114 * initialize() method before using this class. 115 */ 116 public CustomDijkstraMap() { 117 rng = new GWTRNG(); 118 path = new IntVLA(); 119 } 120 121 /** 122 * Construct a CustomDijkstraMap without a level to actually scan. This constructor allows you to specify an RNG before 123 * it is ever used in this class. If you use this constructor, you must call an initialize() method before using 124 * any other methods in the class. 125 */ 126 public CustomDijkstraMap(IRNG random) { 127 rng = random; 128 path = new IntVLA(); 129 } 130 131 /** 132 * Used to construct a CustomDijkstraMap from the output of another. 133 * 134 * @param level 135 */ 136 public CustomDijkstraMap(final double[] level, int width, int height) { 137 this(level, new BasicAdjacency(width, height, Measurement.MANHATTAN)); 138 } 139 140 /** 141 * Used to construct a CustomDijkstraMap from the output of another, specifying a distance calculation. 142 * 143 * @param level 144 * @param adjacency 145 */ 146 public CustomDijkstraMap(final double[] level, Adjacency adjacency) { 147 rng = new GWTRNG(); 148 this.adjacency = adjacency; 149 path = new IntVLA(); 150 151 initialize(level); 152 } 153 154 /** 155 * Constructor meant to take a char[][] returned by DungeonBoneGen.generate(), or any other 156 * char[][] where '#' means a wall and anything else is a walkable tile. If you only have 157 * a map that uses box-drawing characters, use DungeonUtility.linesToHashes() to get a 158 * map that can be used here. 159 * 160 * @param level 161 */ 162 public CustomDijkstraMap(final char[][] level) { 163 this(level, new BasicAdjacency(level.length, level[0].length, Measurement.MANHATTAN), new GWTRNG()); 164 } 165 166 /** 167 * Constructor meant to take a char[][] returned by DungeonBoneGen.generate(), or any other 168 * char[][] where '#' means a wall and anything else is a walkable tile. If you only have 169 * a map that uses box-drawing characters, use DungeonUtility.linesToHashes() to get a 170 * map that can be used here. Also takes an RNG that ensures predictable path choices given 171 * otherwise identical inputs and circumstances. 172 * 173 * @param level 174 * @param rng The RNG to use for certain decisions; only affects find* methods like findPath, not scan. 175 */ 176 public CustomDijkstraMap(final char[][] level, IRNG rng) { 177 this(level, new BasicAdjacency(level.length, level[0].length, Measurement.MANHATTAN), rng); 178 } 179 180 /** 181 * Constructor meant to take a char[][] returned by DungeonBoneGen.generate(), or any other 182 * char[][] where one char means a wall and anything else is a walkable tile. If you only have 183 * a map that uses box-drawing characters, use DungeonUtility.linesToHashes() to get a 184 * map that can be used here. You can specify the character used for walls. 185 * 186 * @param level 187 */ 188 public CustomDijkstraMap(final char[][] level, char alternateWall) { 189 rng = new GWTRNG(); 190 path = new IntVLA(); 191 adjacency = new BasicAdjacency(level.length, level[0].length, Measurement.MANHATTAN); 192 193 initialize(level, alternateWall); 194 } 195 196 /** 197 * Constructor meant to take a char[][] returned by DungeonBoneGen.generate(), or any other 198 * char[][] where '#' means a wall and anything else is a walkable tile. If you only have 199 * a map that uses box-drawing characters, use DungeonUtility.linesToHashes() to get a 200 * map that can be used here. This constructor specifies a distance measurement. 201 * 202 * @param level 203 * @param adjacency 204 */ 205 public CustomDijkstraMap(final char[][] level, Adjacency adjacency) { 206 this(level, adjacency, new GWTRNG()); 207 } 208 209 /** 210 * Constructor meant to take a char[][] returned by DungeonBoneGen.generate(), or any other 211 * char[][] where '#' means a wall and anything else is a walkable tile. If you only have 212 * a map that uses box-drawing characters, use DungeonUtility.linesToHashes() to get a 213 * map that can be used here. Also takes a distance measurement and an IRNG that can be seeded 214 * to ensure predictable path choices given otherwise identical inputs and circumstances. 215 * 216 * @param level 217 * @param rng The IRNG, such as an RNG, to use for certain decisions; only affects find* methods like findPath, not scan. 218 */ 219 public CustomDijkstraMap(final char[][] level, Adjacency adjacency, IRNG rng) { 220 this.rng = rng; 221 path = new IntVLA(); 222 this.adjacency = adjacency; 223 224 initialize(level); 225 } 226 227 /** 228 * Used to initialize or re-initialize a CustomDijkstraMap that needs a new PhysicalMap because it either wasn't given 229 * one when it was constructed, or because the contents of the terrain have changed permanently (not if a 230 * creature moved; for that you pass the positions of creatures that block paths to scan() or findPath() ). 231 * 232 * @param level 233 * @return 234 */ 235 public CustomDijkstraMap initialize(final double[] level) { 236 width = adjacency.width; 237 height = adjacency.height; 238 int len = level.length; 239 gradientMap = new double[len]; 240 physicalMap = new double[len]; 241 costMap = new int[len]; 242 System.arraycopy(level, 0, gradientMap, 0, len); 243 System.arraycopy(level, 0, physicalMap, 0, len); 244 for (int i = 0; i < len; i++) { 245 costMap[i] = (gradientMap[i] > FLOOR) ? '#' : '.'; 246 } 247 adjacency.costRules.putAt('#', WALL, 0); 248 249 neighbors = adjacency.neighborMaps(); 250 heuristics = new double[adjacency.directions.length]; 251 for (int i = 0; i < heuristics.length; i++) { 252 heuristics[i] = adjacency.measurement.heuristic(adjacency.directions[i]); 253 } 254 initialized = true; 255 return this; 256 } 257 258 /** 259 * Used to initialize or re-initialize a CustomDijkstraMap that needs a new PhysicalMap because it either wasn't given 260 * one when it was constructed, or because the contents of the terrain have changed permanently (not if a 261 * creature moved; for that you pass the positions of creatures that block paths to scan() or findPath() ). 262 * 263 * @param level 264 * @return 265 */ 266 public CustomDijkstraMap initialize(final char[][] level) { 267 return initialize(level, '#'); 268 } 269 270 /** 271 * Used to initialize or re-initialize a CustomDijkstraMap that needs a new PhysicalMap because it either wasn't given 272 * one when it was constructed, or because the contents of the terrain have changed permanently (not if a 273 * creature moved; for that you pass the positions of creatures that block paths to scan() or findPath() ). This 274 * initialize() method allows you to specify an alternate wall char other than the default character, '#' . 275 * 276 * @param level 277 * @param alternateWall 278 * @return 279 */ 280 public CustomDijkstraMap initialize(final char[][] level, char alternateWall) { 281 width = level.length; 282 height = level[0].length; 283 int rot = adjacency.rotations, len = width*height*rot, dex; 284 gradientMap = new double[len]; 285 physicalMap = new double[len]; 286 costMap = new int[len]; 287 IntDoubleOrderedMap cst = adjacency.costRules; 288 cst.putAt(alternateWall, WALL, 0); 289 int c; 290 for (int x = 0; x < width; x++) { 291 for (int y = 0; y < height; y++) { 292 c = level[x][y]; 293 double t = (c == alternateWall) ? WALL : FLOOR; 294 for (int r = 0; r < rot; r++) { 295 dex = adjacency.composite(x, y, r, 0); 296 gradientMap[dex] = t; 297 physicalMap[dex] = t; 298 costMap[dex] = c; 299 } 300 } 301 } 302 neighbors = adjacency.neighborMaps(); 303 heuristics = new double[adjacency.directions.length]; 304 for (int i = 0; i < heuristics.length; i++) { 305 heuristics[i] = adjacency.measurement.heuristic(adjacency.directions[i]); 306 } 307 initialized = true; 308 return this; 309 } 310 311 /** 312 * Used to initialize the entry cost modifiers for games that require variable costs to enter squares. This expects 313 * a char[][] of the same exact dimensions as the 2D array that was used to previously initialize() this 314 * CustomDijkstraMap, treating the '#' char as a wall (impassable) and anything else as having a normal cost to enter. 315 * The costs can be accessed later by using costMap directly (which will have a valid value when this does not 316 * throw an exception), or by calling setCost(). 317 * 318 * @param level a 2D char array that uses '#' for walls 319 * @return this CustomDijkstraMap for chaining. 320 */ 321 public CustomDijkstraMap initializeCost(final char[][] level) { 322 if (!initialized) throw new IllegalStateException("CustomDijkstraMap must be initialized first!"); 323 int rot = adjacency.rotations; 324 int c; 325 for (int x = 0; x < width; x++) { 326 for (int y = 0; y < height; y++) { 327 c = level[x][y]; 328 for (int r = 0; r < rot; r++) { 329 costMap[adjacency.composite(x, y, r, 0)] = c; 330 } 331 } 332 } 333 return this; 334 } 335 336 /** 337 * Used to initialize the entry cost modifiers for games that require variable costs to enter squares. This expects 338 * an int[] with length equal to the length of any inner array of neighbors (a field that is given a value during 339 * initialize() by this object's Adjacency value), using the int corresponding to a location as the tile type to 340 * look up for that location, as a key in {@link Adjacency#costRules}, even if an int isn't what this class would 341 * assign normally -- although, walls and other impassable values should be given '#' (which can be put in an int 342 * array) or the value of alternateWall, if this was given one, as a value. The tiles can be accessed later by using 343 * costMap directly (which will have a valid value when this does not throw an exception), or by calling setCost(). 344 * <p/> 345 * This method should be slightly more efficient than the other initializeCost methods. 346 * 347 * @param tiles an int array that already has tile types that {@link #adjacency} can find values for 348 * @return this CustomDijkstraMap for chaining. 349 */ 350 public CustomDijkstraMap initializeCost(final int[] tiles) { 351 if (!initialized) 352 throw new IllegalStateException("CustomDijkstraMap must be initialized first!"); 353 if(tiles.length != gradientMap.length) 354 throw new IllegalArgumentException("costs.length must equal gradientMap.length"); 355 costMap = new int[tiles.length]; 356 System.arraycopy(tiles, 0, costMap, 0, tiles.length); 357 return this; 358 } 359 360 /** 361 * Gets the appropriate DijkstraMap.Measurement to pass to a constructor if you already have a Radius. 362 * Matches SQUARE or CUBE to CHEBYSHEV, DIAMOND or OCTAHEDRON to MANHATTAN, and CIRCLE or SPHERE to EUCLIDEAN. 363 * 364 * @param radius the Radius to find the corresponding Measurement for 365 * @return a DijkstraMap.Measurement that matches radius; SQUARE to CHEBYSHEV, DIAMOND to MANHATTAN, etc. 366 */ 367 public static Measurement findMeasurement(Radius radius) { 368 if (radius.equals2D(Radius.SQUARE)) 369 return Measurement.CHEBYSHEV; 370 else if (radius.equals2D(Radius.DIAMOND)) 371 return Measurement.MANHATTAN; 372 else 373 return Measurement.EUCLIDEAN; 374 } 375 376 /** 377 * Gets the appropriate Radius corresponding to a DijkstraMap.Measurement. 378 * Matches CHEBYSHEV to SQUARE, MANHATTAN to DIAMOND, and EUCLIDEAN to CIRCLE. 379 * 380 * @param measurement the Measurement to find the corresponding Radius for 381 * @return a DijkstraMap.Measurement that matches radius; CHEBYSHEV to SQUARE, MANHATTAN to DIAMOND, etc. 382 */ 383 public static Radius findRadius(Measurement measurement) { 384 switch (measurement) { 385 case CHEBYSHEV: 386 return Radius.SQUARE; 387 case EUCLIDEAN: 388 return Radius.CIRCLE; 389 default: 390 return Radius.DIAMOND; 391 } 392 } 393 394 /** 395 * Resets the gradientMap to its original value from physicalMap. 396 */ 397 public void resetMap() { 398 if (!initialized) return; 399 System.arraycopy(physicalMap, 0, gradientMap, 0, physicalMap.length); 400 } 401 402 /** 403 * Resets this CustomDijkstraMap to a state with no goals, no discovered path, and no changes made to gradientMap 404 * relative to physicalMap. 405 */ 406 public void reset() { 407 resetMap(); 408 goals.clear(); 409 fresh.clear(); 410 path.clear(); 411 frustration = 0; 412 } 413 414 /** 415 * Marks a cell as a goal for pathfinding, unless the cell is a wall or unreachable area (then it does nothing). 416 * 417 * @param pt 418 */ 419 public void setGoal(int pt) { 420 if (!initialized || !adjacency.validate(pt)) return; 421 if (physicalMap[pt] > FLOOR) { 422 return; 423 } 424 adjacency.putAllVariants(goals, gradientMap, pt, 0.0); 425 } 426 427 /** 428 * Marks a cell's type for pathfinding cost as tile (it still will look up the tile in the 429 * {@link Adjacency#costRules} field of {@link #adjacency} when it tries to move through one), unless the cell is a 430 * wall or unreachable area (then it always sets the cost to a value that should have the same cost as a wall). 431 * The normal usage of this is something like {@code setCost(position, '.')} for maps without rotation (this sets 432 * the cost of moving into the cell position to the cost of entering a floor marked with '.'; thos is looked up in 433 * the Adjacency's cost rules and those can be set with {@link Adjacency#addCostRule(char, double)}). If the map has 434 * rotation, {@code setCost(position, '.' | 0x10000)} will change the cost to turn while standing on a tile to the 435 * cost of turning on a '.' floor, though this is just one way Adjacency can be implemented (it's how 436 * RotationAdjacency works). 437 * 438 * @param pt the encoded position/rotation/height to set the cost for 439 * @param tile typically a char such as '.' for floors, but if this uses rotation, turns on that tile are different 440 */ 441 public void setCost(int pt, int tile) { 442 if (!initialized || !adjacency.validate(pt)) return; 443 if (physicalMap[pt] > FLOOR) { 444 costMap[pt] = adjacency.costRules.keyAt(0); 445 return; 446 } 447 costMap[pt] = tile; 448 } 449 450 /** 451 * Marks a specific cell in gradientMap as completely impossible to enter. 452 * 453 * @param pt 454 */ 455 public void setOccupied(int pt) { 456 if (!initialized || !adjacency.validate(pt)) return; 457 gradientMap[pt] = WALL; 458 } 459 460 /** 461 * Reverts a cell to the value stored in the original state of the level as known by physicalMap. 462 * 463 * @param pt 464 */ 465 public void resetCell(int pt) { 466 if (!initialized || !adjacency.validate(pt)) return; 467 gradientMap[pt] = physicalMap[pt]; 468 } 469 470 /** 471 * Used to remove all goals and undo any changes to gradientMap made by having a goal present. 472 */ 473 public void clearGoals() { 474 if (!initialized) 475 return; 476 int sz = goals.size; 477 for (int i = 0; i < sz; i++) { 478 resetCell(goals.pop()); 479 } 480 } 481 482 protected void setFresh(final int pt, double counter) { 483 if (!initialized || !adjacency.validate(pt)) return; 484 if(gradientMap[pt] < counter && gradientMap[pt] < FLOOR) 485 return; 486 gradientMap[pt] = counter; 487 fresh.add(pt); 488 } 489 490 public boolean isBlocked(int start, int direction) { 491 return adjacency.isBlocked(start, direction, neighbors, gradientMap, WALL); 492 /* 493 if (rotations != 1) { 494 if (rotations <= 4 || (direction & 1) == 0) 495 return !adjacency.validate(start); 496 return neighbors[1][0][start] < 0 || gradientMap[neighbors[1][0][start]] >= WALL 497 || neighbors[1][2][start] < 0 || gradientMap[neighbors[1][2][start]] >= WALL; 498 } else { 499 if (direction < 4) 500 return !adjacency.validate(start); 501 int[][] near = neighbors[0]; 502 switch (direction) { 503 case 4: //UP_LEFT 504 return (near[0][start] < 0 || gradientMap[near[0][start]] >= WALL) 505 && (near[2][start] < 0 || gradientMap[near[2][start]] >= WALL); 506 case 5: //UP_RIGHT 507 return (near[0][start] < 0 || gradientMap[near[0][start]] >= WALL) 508 && (near[3][start] < 0 || gradientMap[near[3][start]] >= WALL); 509 case 6: //DOWN_LEFT 510 return (near[1][start] < 0 || gradientMap[near[1][start]] >= WALL) 511 && (near[2][start] < 0 || gradientMap[near[2][start]] >= WALL); 512 default: //DOWN_RIGHT 513 return (near[1][start] < 0 || gradientMap[near[1][start]] >= WALL) 514 && (near[3][start] < 0 || gradientMap[near[3][start]] >= WALL); 515 } 516 } 517 */ 518 } 519 520 /** 521 * Recalculate the CustomDijkstra map and return it. Cells that were marked as goals with setGoal will have 522 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 523 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 524 * which will have a value defined by the WALL constant in this class, and areas that the scan was 525 * unable to reach, which will have a value defined by the DARK constant in this class (typically, 526 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 527 * current measurement. 528 * 529 * @param usable how much of impassable to actually use; should usually be equal to impassable.length, but can be 530 * anything if impassable is null (then, it is ignored). This exists to differentiate this method from 531 * the overload that takes an IntVLA when that argument is null, but also to give some flexibility. 532 * @param impassable An array of int keys (encoded by an Adjacency, usually) representing the locations of enemies 533 * or other moving obstacles to a path that cannot be moved through; this can be null (meaning no obstacles). 534 * @return An int array using the dimensions of what this knows about the physical map. 535 */ 536 public double[] scan(int usable, int[] impassable) { 537 if(impassable == null) 538 scanInternal(-1, null, -1); 539 else 540 scanInternal(-1, impassable, Math.min(usable, impassable.length)); 541 int maxLength = gradientMap.length; 542 double[] gradientClone = new double[maxLength]; 543 for (int l = 0; l < maxLength; l++) { 544 if (gradientMap[l] == FLOOR) { 545 gradientMap[l] = DARK; 546 } 547 } 548 System.arraycopy(gradientMap, 0, gradientClone, 0, maxLength); 549 return gradientClone; 550 } 551 /** 552 * Recalculate the CustomDijkstra map and return it. Cells that were marked as goals with setGoal will have 553 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 554 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 555 * which will have a value defined by the WALL constant in this class, and areas that the scan was 556 * unable to reach, which will have a value defined by the DARK constant in this class (typically, 557 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 558 * current measurement. 559 * 560 * @param impassable An array of int keys (encoded by an Adjacency, usually) representing the locations of enemies 561 * or other moving obstacles to a path that cannot be moved through; this can be null (meaning no obstacles). 562 * @return An int array using the dimensions of what this knows about the physical map. 563 */ 564 public double[] scan(IntVLA impassable) { 565 if(impassable == null) 566 scanInternal(-1, null, -1); 567 else 568 scanInternal(-1, impassable.items, impassable.size); 569 int maxLength = gradientMap.length; 570 double[] gradientClone = new double[maxLength]; 571 for (int l = 0; l < maxLength; l++) { 572 if (gradientMap[l] == FLOOR) { 573 gradientMap[l] = DARK; 574 } 575 } 576 System.arraycopy(gradientMap, 0, gradientClone, 0, maxLength); 577 return gradientClone; 578 579 } 580 581 /** 582 * Recalculate the CustomDijkstra map, stopping early if it has a path from a goal to start, and return that map. 583 * Cells that were marked as goals with setGoal will have 584 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 585 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 586 * which will have a value defined by the WALL constant in this class, and areas that the scan was 587 * unable to reach, which will have a value defined by the DARK constant in this class (typically, 588 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 589 * current measurement. 590 * 591 * @param start the encoded index of the start of the pathfinder; when this has a path from goal to start, it ends 592 * @param usable how much of impassable to actually use; should usually be equal to impassable.length, but can be 593 * anything if impassable is null (then, it is ignored). This exists to differentiate this method from 594 * the overload that takes an IntVLA when that argument is null, but also to give some flexibility. 595 * @param impassable An array of int keys (encoded by an Adjacency, usually) representing the locations of enemies 596 * or other moving obstacles to a path that cannot be moved through; this can be null (meaning no obstacles). 597 * @return An int array using the dimensions of what this knows about the physical map. 598 */ 599 public double[] scanToStart(int start, int usable, int[] impassable) { 600 if(impassable == null) 601 scanInternal(start, null, -1); 602 else 603 scanInternal(start, impassable, Math.min(usable, impassable.length)); 604 return gradientMap; 605 } 606 /** 607 * Recalculate the CustomDijkstra map, stopping early if it has a path from a goal to start, and return that map. 608 * Cells that were marked as goals with setGoal will have 609 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 610 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 611 * which will have a value defined by the WALL constant in this class, and areas that the scan was 612 * unable to reach, which will have a value defined by the DARK constant in this class (typically, 613 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 614 * current measurement. 615 * 616 * @param start the encoded index of the start of the pathfinder; when this has a path from goal to start, it ends 617 * @param impassable An array of int keys (encoded by an Adjacency, usually) representing the locations of enemies 618 * or other moving obstacles to a path that cannot be moved through; this can be null (meaning no obstacles). 619 * @return An int array using the dimensions of what this knows about the physical map. 620 */ 621 public double[] scanToStart(int start, IntVLA impassable) { 622 if(impassable == null) 623 scanInternal(start, null, -1); 624 else 625 scanInternal(start, impassable.items, impassable.size); 626 return gradientMap; 627 } 628 629 protected void scanInternal(int start, int[] impassable, int usable) 630 { 631 if (!initialized) return; 632 Adjacency adjacency = this.adjacency; 633 int[][] fromNeighbors = neighbors[0]; 634 int near, cen, mid, neighborCount = fromNeighbors.length; 635 636 if (impassable != null) { 637 if(usable > impassable.length) 638 usable = impassable.length; 639 for (int i = 0; i < usable; i++) { 640 adjacency.putAllVariants(null, gradientMap, impassable[i], WALL); 641 } 642 } 643 boolean standardCosts = adjacency.hasStandardCost(); 644 mappedCount = goals.size; 645 for (int i = 0; i < mappedCount; i++) { 646 gradientMap[goals.get(i)] = 0; 647 } 648 double currentLowest = 999000, cs, csm, dist; 649 fresh.clear(); 650 int maxLength = gradientMap.length; 651 for (int l = 0; l < maxLength; l++) { 652 if (gradientMap[l] <= FLOOR) { 653 if (gradientMap[l] < currentLowest) { 654 currentLowest = gradientMap[l]; 655 fresh.clear(); 656 fresh.add(l); 657 } else if (gradientMap[l] == currentLowest) { 658 fresh.add(l); 659 } 660 } 661 } 662 int fsz, numAssigned = fresh.size; 663 IntDoubleOrderedMap costs = adjacency.costRules; 664 while (numAssigned > 0) { 665 numAssigned = 0; 666 fsz = fresh.size; 667 for (int ci = fsz-1; ci >= 0; ci--) { 668 cen = fresh.removeIndex(ci); 669 dist = gradientMap[cen]; 670 for (int d = 0; d < neighborCount; d++) { 671 near = fromNeighbors[d][cen]; 672 if (!adjacency.validate(near)) 673 // Outside the map 674 continue; 675 if(isBlocked(cen, d)) 676 continue; 677 if(adjacency.twoStepRule) { 678 near = fromNeighbors[d][mid = near]; 679 // Outside the map 680 if (!adjacency.validate(near)) 681 continue; 682 if(isBlocked(mid, d)) 683 continue; 684 csm = (costs.get(costMap[mid]) * heuristics[d]); 685 cs = (costs.get(costMap[near]) * heuristics[d]); 686 if ((gradientMap[mid] = dist + csm) + cs < gradientMap[near]) { 687 setFresh(near, dist + cs + csm); 688 ++numAssigned; 689 ++mappedCount; 690 if(start == near && standardCosts) 691 { 692 if (impassable != null) 693 adjacency.resetAllVariants(gradientMap, impassable, usable, physicalMap, 1); 694 return; 695 } 696 } 697 } 698 else 699 { 700 cs = (costs.get(costMap[near] | (adjacency.extractR(cen) == adjacency.extractR(near) ? 0 : 0x10000)) 701 * heuristics[d]); 702 //int h = adjacency.measurement.heuristic(adjacency.directions[d]); 703 if (gradientMap[cen] + cs < gradientMap[near]) { 704 setFresh(near, dist + cs); 705 ++numAssigned; 706 ++mappedCount; 707 if(start == near && standardCosts) 708 { 709 if (impassable != null) 710 adjacency.resetAllVariants(gradientMap, impassable, usable, physicalMap, 1); 711 return; 712 } 713 } 714 } 715 } 716 } 717 } 718 if (impassable != null) 719 adjacency.resetAllVariants(gradientMap, impassable, usable, physicalMap, 1); 720 } 721 722 /** 723 * Recalculate the CustomDijkstra map up to a limit and return it. Cells that were marked as goals with setGoal will have 724 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 725 * from goals will have a value equal to the distance from the nearest goal. If a cell would take more steps to 726 * reach than the given limit, it will have a value of DARK if it was passable instead of the distance. The 727 * exceptions are walls, which will have a value defined by the WALL constant in this class, and areas that the scan 728 * was unable to reach, which will have a value defined by the DARK constant in this class. This uses the 729 * current measurement. 730 * 731 * @param limit The maximum number of steps to scan outward from a goal. 732 * @param usable how much of impassable to actually use; should usually be equal to impassable.length, but can be 733 * anything if impassable is null (then, it is ignored). This exists to differentiate this method from 734 * the overload that takes an IntVLA when that argument is null, but also to give some flexibility. 735 * @param impassable An array or vararg of int keys representing the locations of enemies or other moving obstacles 736 * to a path that cannot be moved through; this can be null if there are no such obstacles. 737 * @return A int array using the dimensions of what this knows about the physical map. 738 */ 739 public double[] partialScan(int limit, int usable, int[] impassable) { 740 if(impassable == null) 741 partialScanInternal(-1, limit, null, -1); 742 else 743 partialScanInternal(-1, -1, impassable, Math.min(usable, impassable.length)); 744 int maxLength = gradientMap.length; 745 double[] gradientClone = new double[maxLength]; 746 for (int l = 0; l < maxLength; l++) { 747 if (gradientMap[l] == FLOOR) { 748 gradientMap[l] = DARK; 749 } 750 } 751 System.arraycopy(gradientMap, 0, gradientClone, 0, maxLength); 752 return gradientClone; 753 } 754 755 /** 756 * Recalculate the CustomDijkstra map up to a limit and return it. Cells that were marked as goals with setGoal will have 757 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 758 * from goals will have a value equal to the distance from the nearest goal. If a cell would take more steps to 759 * reach than the given limit, it will have a value of DARK if it was passable instead of the distance. The 760 * exceptions are walls, which will have a value defined by the WALL constant in this class, and areas that the scan 761 * was unable to reach, which will have a value defined by the DARK constant in this class. This uses the 762 * current measurement. 763 * 764 * @param limit The maximum number of steps to scan outward from a goal. 765 * @param impassable An IntVLA of int keys representing the locations of enemies or other moving obstacles 766 * to a path that cannot be moved through; this can be null if there are no such obstacles. 767 * @return A int array using the dimensions of what this knows about the physical map. 768 */ 769 public double[] partialScan(int limit, IntVLA impassable) { 770 if(impassable == null) 771 partialScanInternal(-1, limit, null, -1); 772 else 773 partialScanInternal(-1, limit, impassable.items, impassable.size); 774 int maxLength = gradientMap.length; 775 double[] gradientClone = new double[maxLength]; 776 for (int l = 0; l < maxLength; l++) { 777 if (gradientMap[l] == FLOOR) { 778 gradientMap[l] = DARK; 779 } 780 } 781 System.arraycopy(gradientMap, 0, gradientClone, 0, maxLength); 782 return gradientClone; 783 } 784 785 786 /** 787 * Recalculate the CustomDijkstra map up to a limit, stopping early if it has a path from a goal to start, and 788 * return it. Cells that were marked as goals with setGoal will have 789 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 790 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 791 * which will have a value defined by the WALL constant in this class, and areas that the scan was 792 * unable to reach, which will have a value defined by the DARK constant in this class (typically, 793 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 794 * current measurement. 795 * 796 * @param start the encoded index of the start of the pathfinder; when this has a path from goal to start, it ends 797 * @param limit The maximum number of steps to scan outward from a goal. 798 * @param usable how much of impassable to actually use; should usually be equal to impassable.length, but can be 799 * anything if impassable is null (then, it is ignored). This exists to differentiate this method from 800 * the overload that takes an IntVLA when that argument is null, but also to give some flexibility. 801 * @param impassable An array of int keys (encoded by an Adjacency, usually) representing the locations of enemies 802 * or other moving obstacles to a path that cannot be moved through; this can be null (meaning no obstacles). 803 * @return An int array using the dimensions of what this knows about the physical map. 804 */ 805 public double[] partialScanToStart(int limit, int start, int usable, int[] impassable) { 806 if(impassable == null) 807 partialScanInternal(start, limit, null, -1); 808 else 809 partialScanInternal(start, limit, impassable, Math.min(usable, impassable.length)); 810 return gradientMap; 811 } 812 /** 813 * Recalculate the CustomDijkstra map up to a limit, stopping early if it has a path from a goal to start, and 814 * return that map. Cells that were marked as goals with setGoal will have 815 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 816 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 817 * which will have a value defined by the WALL constant in this class, and areas that the scan was 818 * unable to reach, which will have a value defined by the DARK constant in this class (typically, 819 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 820 * current measurement. 821 * 822 * @param start the encoded index of the start of the pathfinder; when this has a path from goal to start, it ends 823 * @param limit The maximum number of steps to scan outward from a goal. 824 * @param impassable An array of int keys (encoded by an Adjacency, usually) representing the locations of enemies 825 * or other moving obstacles to a path that cannot be moved through; this can be null (meaning no obstacles). 826 * @return An int array using the dimensions of what this knows about the physical map. 827 */ 828 public double[] partialScanToStart(int limit, int start, IntVLA impassable) { 829 if(impassable == null) 830 partialScanInternal(start, limit, null, -1); 831 else 832 partialScanInternal(start, limit, impassable.items, impassable.size); 833 return gradientMap; 834 } 835 836 protected void partialScanInternal(int start, int limit, int[] impassable, int usable) 837 { 838 if (!initialized) return; 839 Adjacency adjacency = this.adjacency; 840 int[][] fromNeighbors = neighbors[0]; 841 int near, cen, mid, neighborCount = fromNeighbors.length; 842 843 if (impassable != null) { 844 if(usable > impassable.length) 845 usable = impassable.length; 846 for (int i = 0; i < usable; i++) { 847 adjacency.putAllVariants(null, gradientMap, impassable[i], WALL); 848 } 849 } 850 boolean standardCosts = adjacency.hasStandardCost(); 851 mappedCount = goals.size; 852 for (int i = 0; i < mappedCount; i++) { 853 gradientMap[goals.get(i)] = 0; 854 } 855 double currentLowest = 999000, cs, csm, dist; 856 fresh.clear(); 857 int maxLength = gradientMap.length; 858 for (int l = 0; l < maxLength; l++) { 859 if (gradientMap[l] <= FLOOR) { 860 if (gradientMap[l] < currentLowest) { 861 currentLowest = gradientMap[l]; 862 fresh.clear(); 863 fresh.add(l); 864 } else if (gradientMap[l] == currentLowest) { 865 fresh.add(l); 866 } 867 } 868 } 869 int fsz, numAssigned = fresh.size; 870 IntDoubleOrderedMap costs = adjacency.costRules; 871 int iter = 0; 872 while (numAssigned > 0 && iter++ < limit) { 873 numAssigned = 0; 874 fsz = fresh.size; 875 for (int ci = fsz-1; ci >= 0; ci--) { 876 cen = fresh.removeIndex(ci); 877 dist = gradientMap[cen]; 878 for (int d = 0; d < neighborCount; d++) { 879 near = fromNeighbors[d][cen]; 880 if (!adjacency.validate(near)) 881 // Outside the map 882 continue; 883 if(isBlocked(cen, d)) 884 continue; 885 if(adjacency.twoStepRule) { 886 near = fromNeighbors[d][mid = near]; 887 // Outside the map 888 if (!adjacency.validate(near)) 889 continue; 890 if(isBlocked(mid, d)) 891 continue; 892 csm = (costs.get(costMap[mid]) * heuristics[d]); 893 cs = (costs.get(costMap[near]) * heuristics[d]); 894 if ((gradientMap[mid] = dist + csm) + cs < gradientMap[near]) { 895 setFresh(near, dist + cs + csm); 896 ++numAssigned; 897 ++mappedCount; 898 if(start == near && standardCosts) 899 { 900 if (impassable != null) 901 adjacency.resetAllVariants(gradientMap, impassable, usable, physicalMap, 1); 902 return; 903 } 904 } 905 } 906 else 907 { 908 cs = (costs.get(costMap[near] | (adjacency.extractR(cen) == adjacency.extractR(near) ? 0 : 0x10000)) 909 * heuristics[d]); 910 //int h = adjacency.measurement.heuristic(adjacency.directions[d]); 911 if (gradientMap[cen] + cs < gradientMap[near]) { 912 setFresh(near, dist + cs); 913 ++numAssigned; 914 ++mappedCount; 915 if(start == near && standardCosts) 916 { 917 if (impassable != null) 918 adjacency.resetAllVariants(gradientMap, impassable, usable, physicalMap, 1); 919 return; 920 } 921 } 922 } 923 } 924 } 925 } 926 if (impassable != null) 927 adjacency.resetAllVariants(gradientMap, impassable, usable, physicalMap, 1); 928 } 929 930 /* 931 * Recalculate the CustomDijkstra map until it reaches a cell index in targets, then returns the first target found. 932 * This uses the current measurement. 933 * 934 * @param start the cell to use as the origin for finding the nearest target 935 * @param targets the cell indices that this is trying to find; it will stop once it finds one 936 * @return the cell index that it found first. 937 * / 938 public int findNearest(int start, int... targets) { 939 if (!initialized) return -1; 940 if (targets == null) 941 return -1; 942 for (int i = 0; i < targets.length; i++) { 943 if(targets[i] == start) 944 return start; 945 } 946 resetMap(); 947 int xShift = width / 8, yShift = height / 8; 948 while (physicalMap[start.x][start.y] >= WALL && frustration < 50) { 949 start2 = Coord.get(Math.min(Math.max(1, start.x + rng.nextInt(1 + xShift * 2) - xShift), width - 2), 950 Math.min(Math.max(1, start.y + rng.nextInt(1 + yShift * 2) - yShift), height - 2)); 951 } 952 if (closed.containsKey(start2.encode())) 953 closed.remove(start2.encode()); 954 gradientMap[start2.x][start2.y] = 0; 955 956 for (int y = 0; y < height; y++) { 957 for (int x = 0; x < width; x++) { 958 if (gradientMap[x][y] > FLOOR && !goals.containsKey(Coord.pureEncode(x, y))) 959 closed.put(Coord.pureEncode(x, y), physicalMap[x][y]); 960 } 961 } 962 int numAssigned = 1; 963 mappedCount = 1; 964 open.put(start2.encode(), 0); 965 int near, cen; 966 int enc; 967 968 while (numAssigned > 0) { 969 970 numAssigned = 0; 971 972 for (IntDoubleOrderedMap.MapEntry cell : open.mapEntrySet()) { 973 cen = cell.getIntKey(); 974 for (int d = 0; d < neighbors.length; d++) { 975 near = neighbors[d][cen]; 976 if (!adjacency.validate(near)) 977 // Outside the map 978 continue; 979 dir = adjacency.directions[d]; 980 if(adjacency.isBlocked(cen, d, neighbors, gradientMap, WALL)) 981 continue; 982 int h = adjacency.measurement.heuristic(dir); 983 if (!closed.containsKey(near) && !open.containsKey(near) && gradientMap[cen] + h * costMap[near] < gradientMap[near]) { 984 setFresh(near, cell.getDoubleValue() + h * costMap[near]); 985 ++numAssigned; 986 ++mappedCount; 987 } 988 } 989 } 990 open.clear(); 991 open.putAll(fresh); 992 fresh.clear(); 993 994 995 numAssigned = 0; 996 997 for (IntDoubleOrderedMap.MapEntry cell : open.mapEntrySet()) { 998 cen = Coord.decode(cell.getIntKey()); 999 for (int d = 0; d < dirs.length; d++) { 1000 adj = cen.translate(dirs[d].deltaX, dirs[d].deltaY); 1001 if (adj.x < 0 || adj.y < 0 || width <= adj.x || height <= adj.y) 1002 // Outside the map 1003 continue; 1004 enc = adj.encode(); 1005 int h = heuristic(dirs[d]); 1006 if (!closed.containsKey(enc) && !open.containsKey(enc) && 1007 gradientMap[cen.x][cen.y] + h * costMap[adj.x][adj.y] < gradientMap[adj.x][adj.y]) { 1008 setFresh(adj, cell.getDoubleValue() + h * costMap[adj.x][adj.y]); 1009 ++numAssigned; 1010 ++mappedCount; 1011 if (targets.contains(adj)) { 1012 fresh.clear(); 1013 closed.clear(); 1014 open.clear(); 1015 return adj; 1016 } 1017 } 1018 } 1019 } 1020 1021 open.clear(); 1022 open.putAll(fresh); 1023 fresh.clear(); 1024 } 1025 closed.clear(); 1026 open.clear(); 1027 return null; 1028 } 1029 */ 1030 1031 1032 /* 1033 * Recalculate the CustomDijkstra map until it reaches a Coord in targets, then returns the first target found. 1034 * This uses the current measurement. 1035 * 1036 * @param start the cell to use as the origin for finding the nearest target 1037 * @param targets the Coords that this is trying to find; it will stop once it finds one 1038 * @return the Coord that it found first. 1039 * 1040 public Coord findNearest(Coord start, Coord... targets) { 1041 OrderedSet<Coord> tgts = new OrderedSet<>(targets.length); 1042 Collections.addAll(tgts, targets); 1043 return findNearest(start, tgts); 1044 } 1045 */ 1046 1047 /* 1048 * If you have a target or group of targets you want to pathfind to without scanning the full map, this can be good. 1049 * It may find sub-optimal paths in the presence of costs to move into cells. It is useful when you want to move in 1050 * a straight line to a known nearby goal. 1051 * 1052 * @param start your starting location 1053 * @param targets an array or vararg of Coords to pathfind to the nearest of 1054 * @return an ArrayList of Coord that goes from a cell adjacent to start and goes to one of the targets. Copy of path. 1055 * 1056 public ArrayList<Coord> findShortcutPath(Coord start, Coord... targets) { 1057 if (targets.length == 0) { 1058 path.clear(); 1059 return new IntVLA(path); 1060 } 1061 Coord currentPos = findNearest(start, targets); 1062 while (true) { 1063 if (frustration > 500) { 1064 path.clear(); 1065 break; 1066 } 1067 int best = gradientMap[currentPos.x][currentPos.y]; 1068 final Direction[] dirs = appendDirToShuffle(rng); 1069 int choice = rng.nextInt(dirs.length); 1070 1071 for (int d = 0; d < dirs.length; d++) { 1072 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 1073 if (gradientMap[pt.x][pt.y] < best) { 1074 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 1075 best = gradientMap[pt.x][pt.y]; 1076 choice = d; 1077 } 1078 } 1079 } 1080 1081 if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 1082 path.clear(); 1083 break; 1084 } 1085 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 1086 if (gradientMap[currentPos.x][currentPos.y] == 0) 1087 break; 1088 path.add(currentPos); 1089 frustration++; 1090 } 1091 frustration = 0; 1092 Collections.reverse(path); 1093 return new ArrayList<>(path); 1094 1095 } 1096 */ 1097 1098 1099 /* 1100 * Recalculate the CustomDijkstra map until it reaches a Coord in targets, then returns the first several targets found, 1101 * up to limit or less if the map is fully searched without finding enough. 1102 * This uses the current measurement. 1103 * 1104 * @param start the cell to use as the origin for finding the nearest targets 1105 * @param limit the maximum number of targets to find before returning 1106 * @param targets the Coords that this is trying to find; it will stop once it finds enough (based on limit) 1107 * @return the Coords that it found first. 1108 * 1109 public ArrayList<Coord> findNearestMultiple(Coord start, int limit, Set<Coord> targets) { 1110 if (!initialized) return null; 1111 if (targets == null) 1112 return null; 1113 ArrayList<Coord> found = new ArrayList<>(limit); 1114 if (targets.contains(start)) 1115 return found; 1116 Coord start2 = start, adj, cen; 1117 int enc; 1118 while (physicalMap[start.x][start.y] >= WALL && frustration < 50) { 1119 start2 = Coord.get(Math.min(Math.max(1, start.x + rng.nextInt(15) - 7), width - 2), 1120 Math.min(Math.max(1, start.y + rng.nextInt(15) - 7), height - 2)); 1121 frustration++; 1122 } 1123 if (closed.containsKey(start2.encode())) 1124 closed.remove(start2.encode()); 1125 gradientMap[start2.x][start2.y] = 0; 1126 1127 for (int y = 0; y < height; y++) { 1128 for (int x = 0; x < width; x++) { 1129 if (gradientMap[x][y] > FLOOR && !goals.containsKey(Coord.pureEncode(x, y))) 1130 closed.put(Coord.pureEncode(x, y), physicalMap[x][y]); 1131 } 1132 } 1133 int numAssigned = 1; 1134 mappedCount = 1; 1135 open.put(start2.encode(), 0); 1136 1137 Direction[] dirs = (measurement == Measurement.MANHATTAN) ? Direction.CARDINALS : Direction.OUTWARDS; 1138 while (numAssigned > 0) { 1139// ++iter; 1140 numAssigned = 0; 1141 for (IntDoubleOrderedMap.MapEntry cell : open.mapEntrySet()) { 1142 cen = Coord.decode(cell.getIntKey()); 1143 for (int d = 0; d < dirs.length; d++) { 1144 adj = cen.translate(dirs[d].deltaX, dirs[d].deltaY); 1145 if (adj.x < 0 || adj.y < 0 || width <= adj.x || height <= adj.y) 1146 // Outside the map 1147 continue; 1148 enc = adj.encode(); 1149 1150 int h = heuristic(dirs[d]); 1151 if (!closed.containsKey(enc) && !open.containsKey(enc) && 1152 gradientMap[cen.x][cen.y] + h * costMap[adj.x][adj.y] < gradientMap[adj.x][adj.y]) { 1153 setFresh(adj, cell.getDoubleValue() + h * costMap[adj.x][adj.y]); 1154 ++numAssigned; 1155 ++mappedCount; 1156 if (targets.contains(adj)) { 1157 found.add(adj); 1158 if (found.size() >= limit) { 1159 fresh.clear(); 1160 open.clear(); 1161 closed.clear(); 1162 return found; 1163 } 1164 } 1165 } 1166 } 1167 } 1168// closed.putAll(open); 1169 open = new IntDoubleOrderedMap(fresh); 1170 fresh.clear(); 1171 } 1172 closed.clear(); 1173 open.clear(); 1174 return found; 1175 } 1176 */ 1177 1178 /** 1179 * Recalculate the CustomDijkstra map for a creature that is potentially larger than 1x1 cell and return it. The value of 1180 * a cell in the returned CustomDijkstra map assumes that a creature is square, with a side length equal to the passed 1181 * size, that its minimum-x, minimum-y cell is the starting cell, and that any cell with a distance number 1182 * represents the distance for the creature's minimum-x, minimum-y cell to reach it. Cells that cannot be entered 1183 * by the minimum-x, minimum-y cell because of sizing (such as a floor cell next to a maximum-x and/or maximum-y 1184 * wall if size is > 1) will be marked as DARK. Cells that were marked as goals with setGoal will have 1185 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 1186 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 1187 * which will have a value defined by the WALL constant in this class, and areas that the scan was 1188 * unable to reach, which will have a value defined by the DARK constant in this class. (typically, 1189 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 1190 * current measurement. 1191 * <br> 1192 * Portals and wrapping are not currently recommended in conjunction with multi-square creatures, since a 1193 * 2x2 creature could easily occupy two cells on the east edge and two cells on the west edge of the map, 1194 * and that poses all sorts of issues for creatures trying to pathfind to it, not to mention the more 1195 * general issues of how to display a bisected, but mobile, creature. 1196 * 1197 * @param size The length of one side of a square creature using this to find a path, i.e. 2 for a 2x2 cell 1198 * creature. Non-square creatures are not supported because turning is really hard. 1199 * @param usable how much of impassable to actually use; should usually be equal to impassable.length, but can be 1200 * anything if impassable is null (then, it is ignored). This exists to differentiate this method from 1201 * the overload that takes an IntVLA when that argument is null, but also to give some flexibility. 1202 * @param impassable An array of encoded int keys representing the locations of enemies or other moving obstacles to 1203 * a path that cannot be moved through; this can be null if there are no such obstacles. 1204 * @return An int array using the dimensions/rotations of what this knows about the physical map. 1205 */ 1206 public double[] scanLarge(int size, int usable, int[] impassable) { 1207 if(impassable == null) 1208 scanLargeInternal(-1, size, null, -1); 1209 else 1210 scanLargeInternal(-1, size, impassable, Math.min(usable, impassable.length)); 1211 int maxLength = gradientMap.length; 1212 double[] gradientClone = new double[maxLength]; 1213 for (int l = 0; l < maxLength; l++) { 1214 if (gradientMap[l] == FLOOR) { 1215 gradientMap[l] = DARK; 1216 } 1217 } 1218 System.arraycopy(gradientMap, 0, gradientClone, 0, maxLength); 1219 return gradientClone; 1220 } 1221 /** 1222 * Recalculate the CustomDijkstra map for a creature that is potentially larger than 1x1 cell and return it. The value of 1223 * a cell in the returned CustomDijkstra map assumes that a creature is square, with a side length equal to the passed 1224 * size, that its minimum-x, minimum-y cell is the starting cell, and that any cell with a distance number 1225 * represents the distance for the creature's minimum-x, minimum-y cell to reach it. Cells that cannot be entered 1226 * by the minimum-x, minimum-y cell because of sizing (such as a floor cell next to a maximum-x and/or maximum-y 1227 * wall if size is > 1) will be marked as DARK. Cells that were marked as goals with setGoal will have 1228 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 1229 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 1230 * which will have a value defined by the WALL constant in this class, and areas that the scan was 1231 * unable to reach, which will have a value defined by the DARK constant in this class. (typically, 1232 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 1233 * current measurement. 1234 * <br> 1235 * Portals and wrapping are not currently recommended in conjunction with multi-square creatures, since a 1236 * 2x2 creature could easily occupy two cells on the east edge and two cells on the west edge of the map, 1237 * and that poses all sorts of issues for creatures trying to pathfind to it, not to mention the more 1238 * general issues of how to display a bisected, but mobile, creature. 1239 * 1240 * @param size The length of one side of a square creature using this to find a path, i.e. 2 for a 2x2 cell 1241 * creature. Non-square creatures are not supported because turning is really hard. 1242 * @param impassable An IntVLA where items are ints representing the locations of enemies or other moving obstacles 1243 * to a path that cannot be moved through; this can be null if there are no such obstacles. 1244 * @return A 2D int[width][height] using the width and height of what this knows about the physical map. 1245 */ 1246 public double[] scanLarge(int size, IntVLA impassable) { 1247 if(impassable == null) 1248 scanLargeInternal(-1, size, null, -1); 1249 else 1250 scanLargeInternal(-1, size, impassable.items, impassable.size); 1251 int maxLength = gradientMap.length; 1252 double[] gradientClone = new double[maxLength]; 1253 for (int l = 0; l < maxLength; l++) { 1254 if (gradientMap[l] == FLOOR) { 1255 gradientMap[l] = DARK; 1256 } 1257 } 1258 System.arraycopy(gradientMap, 0, gradientClone, 0, maxLength); 1259 return gradientClone; 1260 } 1261 /** 1262 * Recalculate the CustomDijkstra map for a creature that is potentially larger than 1x1 cell and return it. The value of 1263 * a cell in the returned CustomDijkstra map assumes that a creature is square, with a side length equal to the passed 1264 * size, that its minimum-x, minimum-y cell is the starting cell, and that any cell with a distance number 1265 * represents the distance for the creature's minimum-x, minimum-y cell to reach it. Cells that cannot be entered 1266 * by the minimum-x, minimum-y cell because of sizing (such as a floor cell next to a maximum-x and/or maximum-y 1267 * wall if size is > 1) will be marked as DARK. Cells that were marked as goals with setGoal will have 1268 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 1269 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 1270 * which will have a value defined by the WALL constant in this class, and areas that the scan was 1271 * unable to reach, which will have a value defined by the DARK constant in this class. (typically, 1272 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 1273 * current measurement. 1274 * <br> 1275 * Portals and wrapping are not currently recommended in conjunction with multi-square creatures, since a 1276 * 2x2 creature could easily occupy two cells on the east edge and two cells on the west edge of the map, 1277 * and that poses all sorts of issues for creatures trying to pathfind to it, not to mention the more 1278 * general issues of how to display a bisected, but mobile, creature. 1279 * 1280 * @param size The length of one side of a square creature using this to find a path, i.e. 2 for a 2x2 cell 1281 * creature. Non-square creatures are not supported because turning is really hard. 1282 * @param start the encoded index of the start of the pathfinder; when this has a path from goal to start, it ends 1283 * @param usable how much of impassable to actually use; should usually be equal to impassable.length, but can be 1284 * anything if impassable is null (then, it is ignored). This exists to differentiate this method from 1285 * the overload that takes an IntVLA when that argument is null, but also to give some flexibility. 1286 * @param impassable An array of encoded int keys representing the locations of enemies or other moving obstacles to 1287 * a path that cannot be moved through; this can be null if there are no such obstacles. 1288 * @return An int array using the dimensions/rotations of what this knows about the physical map. 1289 */ 1290 public double[] scanToStartLarge(int size, int start, int usable, int[] impassable) { 1291 if(impassable == null) 1292 scanLargeInternal(start, size, null, -1); 1293 else 1294 scanLargeInternal(start, size, impassable, Math.min(usable, impassable.length)); 1295 int maxLength = gradientMap.length; 1296 double[] gradientClone = new double[maxLength]; 1297 for (int l = 0; l < maxLength; l++) { 1298 if (gradientMap[l] == FLOOR) { 1299 gradientMap[l] = DARK; 1300 } 1301 } 1302 System.arraycopy(gradientMap, 0, gradientClone, 0, maxLength); 1303 return gradientClone; 1304 } 1305 /** 1306 * Recalculate the CustomDijkstra map for a creature that is potentially larger than 1x1 cell and return it. The value of 1307 * a cell in the returned CustomDijkstra map assumes that a creature is square, with a side length equal to the passed 1308 * size, that its minimum-x, minimum-y cell is the starting cell, and that any cell with a distance number 1309 * represents the distance for the creature's minimum-x, minimum-y cell to reach it. Cells that cannot be entered 1310 * by the minimum-x, minimum-y cell because of sizing (such as a floor cell next to a maximum-x and/or maximum-y 1311 * wall if size is > 1) will be marked as DARK. Cells that were marked as goals with setGoal will have 1312 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 1313 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 1314 * which will have a value defined by the WALL constant in this class, and areas that the scan was 1315 * unable to reach, which will have a value defined by the DARK constant in this class. (typically, 1316 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 1317 * current measurement. 1318 * <br> 1319 * Portals and wrapping are not currently recommended in conjunction with multi-square creatures, since a 1320 * 2x2 creature could easily occupy two cells on the east edge and two cells on the west edge of the map, 1321 * and that poses all sorts of issues for creatures trying to pathfind to it, not to mention the more 1322 * general issues of how to display a bisected, but mobile, creature. 1323 * 1324 * @param size The length of one side of a square creature using this to find a path, i.e. 2 for a 2x2 cell 1325 * creature. Non-square creatures are not supported because turning is really hard. 1326 * @param start the encoded index of the start of the pathfinder; when this has a path from goal to start, it ends 1327 * @param impassable An IntVLA where items are ints representing the locations of enemies or other moving obstacles 1328 * to a path that cannot be moved through; this can be null if there are no such obstacles. 1329 * @return A 2D int[width][height] using the width and height of what this knows about the physical map. 1330 */ 1331 public double[] scanToStartLarge(int size, int start, IntVLA impassable) { 1332 if(impassable == null) 1333 scanLargeInternal(start, size, null, -1); 1334 else 1335 scanLargeInternal(start, size, impassable.items, impassable.size); 1336 int maxLength = gradientMap.length; 1337 double[] gradientClone = new double[maxLength]; 1338 for (int l = 0; l < maxLength; l++) { 1339 if (gradientMap[l] == FLOOR) { 1340 gradientMap[l] = DARK; 1341 } 1342 } 1343 System.arraycopy(gradientMap, 0, gradientClone, 0, maxLength); 1344 return gradientClone; 1345 } 1346 1347 protected void scanLargeInternal(int start, int size, int[] impassable, int usable) 1348 { 1349 if (!initialized) return; 1350 Adjacency adjacency = this.adjacency; 1351 int[][] fromNeighbors = neighbors[0]; 1352 int near, cen, mid, neighborCount = fromNeighbors.length, sx, sy, sr, sn; 1353 1354 if (impassable != null) { 1355 if(usable > impassable.length) 1356 usable = impassable.length; 1357 for (int i = 0; i < usable; i++) { 1358 adjacency.putAllVariants(null, gradientMap, impassable[i], WALL, -size); 1359 } 1360 } 1361 mappedCount = goals.size; 1362 for (int i = 0; i < mappedCount; i++) { 1363 cen = goals.get(i); 1364 sx = adjacency.extractX(cen); 1365 sy = adjacency.extractY(cen); 1366 sr = adjacency.extractR(cen); 1367 sn = adjacency.extractN(cen); 1368 for (int xx = 0; xx < size; xx++) { 1369 for (int yy = 0; yy < size; yy++) { 1370 cen = adjacency.composite(sx - xx, sy - yy, sr, sn); 1371 if(cen >= 0) 1372 gradientMap[cen] = 0; 1373 } 1374 } 1375 } 1376 double currentLowest = 999000, cs, csm, dist; 1377 fresh.clear(); 1378 int maxLength = gradientMap.length; 1379 for (int l = 0; l < maxLength; l++) { 1380 if (gradientMap[l] <= FLOOR) { 1381 if (gradientMap[l] < currentLowest) { 1382 currentLowest = gradientMap[l]; 1383 fresh.clear(); 1384 fresh.add(l); 1385 } else if (gradientMap[l] == currentLowest) { 1386 fresh.add(l); 1387 } 1388 } 1389 } 1390 int fsz, numAssigned = fresh.size; 1391 IntDoubleOrderedMap costs = adjacency.costRules; 1392 boolean standardCosts = adjacency.hasStandardCost(); 1393 while (numAssigned > 0) { 1394 numAssigned = 0; 1395 fsz = fresh.size; 1396 for (int ci = fsz-1; ci >= 0; ci--) { 1397 cen = fresh.removeIndex(ci); 1398 dist = gradientMap[cen]; 1399 for (int d = 0; d < neighborCount; d++) { 1400 near = fromNeighbors[d][cen]; 1401 if (!adjacency.validate(near)) 1402 // Outside the map 1403 continue; 1404 if(isBlocked(cen, d)) 1405 continue; 1406 if(adjacency.twoStepRule) { 1407 near = fromNeighbors[d][mid = near]; 1408 // Outside the map 1409 if (!adjacency.validate(near)) 1410 continue; 1411 if(isBlocked(mid, d)) 1412 continue; 1413 csm = (costs.get(costMap[mid]) * heuristics[d]); 1414 cs = (costs.get(costMap[near]) * heuristics[d]); 1415 if ((gradientMap[mid] = dist + csm) + cs < gradientMap[near]) { 1416 setFresh(near, dist + cs + csm); 1417 ++numAssigned; 1418 ++mappedCount; 1419 if(start == near && standardCosts) 1420 { 1421 if (impassable != null) 1422 adjacency.resetAllVariants(gradientMap, impassable, usable, physicalMap, -size); 1423 return; 1424 } 1425 } 1426 } 1427 else 1428 { 1429 cs = (costs.get(costMap[near] | (adjacency.extractR(cen) == adjacency.extractR(near) ? 0 : 0x10000)) 1430 * heuristics[d]); 1431 //int h = adjacency.measurement.heuristic(adjacency.directions[d]); 1432 if (gradientMap[cen] + cs < gradientMap[near]) { 1433 setFresh(near, dist + cs); 1434 ++numAssigned; 1435 ++mappedCount; 1436 if(start == near && standardCosts) 1437 { 1438 if (impassable != null) 1439 adjacency.resetAllVariants(gradientMap, impassable, usable, physicalMap, -size); 1440 return; 1441 } 1442 } 1443 } 1444 } 1445 } 1446 } 1447 1448 if (impassable != null) 1449 adjacency.resetAllVariants(gradientMap, impassable, usable, physicalMap, -size); 1450 } 1451 1452 1453 /** 1454 * Recalculate the CustomDijkstra map for a creature that is potentially larger than 1x1 cell and return it. The value of 1455 * a cell in the returned CustomDijkstra map assumes that a creature is square, with a side length equal to the passed 1456 * size, that its minimum-x, minimum-y cell is the starting cell, and that any cell with a distance number 1457 * represents the distance for the creature's minimum-x, minimum-y cell to reach it. Cells that cannot be entered 1458 * by the minimum-x, minimum-y cell because of sizing (such as a floor cell next to a maximum-x and/or maximum-y 1459 * wall if size is > 1) will be marked as DARK. Cells that were marked as goals with setGoal will have 1460 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 1461 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 1462 * which will have a value defined by the WALL constant in this class, and areas that the scan was 1463 * unable to reach, which will have a value defined by the DARK constant in this class. (typically, 1464 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 1465 * current measurement. 1466 * <br> 1467 * Portals and wrapping are not currently recommended in conjunction with multi-square creatures, since a 1468 * 2x2 creature could easily occupy two cells on the east edge and two cells on the west edge of the map, 1469 * and that poses all sorts of issues for creatures trying to pathfind to it, not to mention the more 1470 * general issues of how to display a bisected, but mobile, creature. 1471 * 1472 * @param size The length of one side of a square creature using this to find a path, i.e. 2 for a 2x2 cell 1473 * creature. Non-square creatures are not supported because turning is really hard. 1474 * @param limit The maximum number of steps to scan outward from a goal. 1475 * @param usable how much of impassable to actually use; should usually be equal to impassable.length, but can be 1476 * anything if impassable is null (then, it is ignored). This exists to differentiate this method from 1477 * the overload that takes an IntVLA when that argument is null, but also to give some flexibility. 1478 * @param impassable An array of encoded int keys representing the locations of enemies or other moving obstacles to 1479 * a path that cannot be moved through; this can be null if there are no such obstacles. 1480 * @return A 2D int[width][height] using the width and height of what this knows about the physical map. 1481 */ 1482 public double[] partialScanLarge(int size, int limit, int usable, int[] impassable) { 1483 if(impassable == null) 1484 partialScanLargeInternal(-1, size, limit, null, -1); 1485 else 1486 partialScanLargeInternal(-1, size, limit, impassable, Math.min(usable, impassable.length)); 1487 int maxLength = gradientMap.length; 1488 double[] gradientClone = new double[maxLength]; 1489 for (int l = 0; l < maxLength; l++) { 1490 if (gradientMap[l] == FLOOR) { 1491 gradientMap[l] = DARK; 1492 } 1493 } 1494 System.arraycopy(gradientMap, 0, gradientClone, 0, maxLength); 1495 return gradientClone; 1496 } 1497 /** 1498 * Recalculate the CustomDijkstra map, up to a limit, for a creature that is potentially larger than 1x1 cell and 1499 * return it. The value of a cell in the returned CustomDijkstra map assumes that a creature is square, with a side 1500 * length equal to the passed size, that its minimum-x, minimum-y cell is the starting cell, and that any cell with 1501 * a distance number represents the distance for the creature's minimum-x, minimum-y cell to reach it. Cells that 1502 * cannot be entered by the minimum-x, minimum-y cell because of sizing (such as a floor cell next to a maximum-x 1503 * and/or maximum-y wall if size is > 1) will be marked as DARK. Cells that were marked as goals with setGoal 1504 * will have a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 1505 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 1506 * which will have a value defined by the WALL constant in this class, and areas that the scan was 1507 * unable to reach, which will have a value defined by the DARK constant in this class. (typically, 1508 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 1509 * current measurement. 1510 * <br> 1511 * Portals and wrapping are not currently recommended in conjunction with multi-square creatures, since a 1512 * 2x2 creature could easily occupy two cells on the east edge and two cells on the west edge of the map, 1513 * and that poses all sorts of issues for creatures trying to pathfind to it, not to mention the more 1514 * general issues of how to display a bisected, but mobile, creature. 1515 * 1516 * @param size The length of one side of a square creature using this to find a path, i.e. 2 for a 2x2 cell 1517 * creature. Non-square creatures are not supported because turning is really hard. 1518 * @param limit The maximum number of steps to scan outward from a goal. 1519 * @param impassable An IntVLA where items are ints representing the locations of enemies or other moving obstacles 1520 * to a path that cannot be moved through; this can be null if there are no such obstacles. 1521 * @return A 2D int[width][height] using the width and height of what this knows about the physical map. 1522 */ 1523 public double[] partialScanLarge(int size, int limit, IntVLA impassable) { 1524 if(impassable == null) 1525 partialScanLargeInternal(-1, size, limit, null, -1); 1526 else 1527 partialScanLargeInternal(-1, size, limit, impassable.items, impassable.size); 1528 int maxLength = gradientMap.length; 1529 double[] gradientClone = new double[maxLength]; 1530 for (int l = 0; l < maxLength; l++) { 1531 if (gradientMap[l] == FLOOR) { 1532 gradientMap[l] = DARK; 1533 } 1534 } 1535 System.arraycopy(gradientMap, 0, gradientClone, 0, maxLength); 1536 return gradientClone; 1537 } 1538 /** 1539 * Recalculate the CustomDijkstra map, up to a limit, for a creature that is potentially larger than 1x1 cell, 1540 * stopping early if a path is found between a goal and start, and return that map. The value of 1541 * a cell in the returned CustomDijkstra map assumes that a creature is square, with a side length equal to the passed 1542 * size, that its minimum-x, minimum-y cell is the starting cell, and that any cell with a distance number 1543 * represents the distance for the creature's minimum-x, minimum-y cell to reach it. Cells that cannot be entered 1544 * by the minimum-x, minimum-y cell because of sizing (such as a floor cell next to a maximum-x and/or maximum-y 1545 * wall if size is > 1) will be marked as DARK. Cells that were marked as goals with setGoal will have 1546 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 1547 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 1548 * which will have a value defined by the WALL constant in this class, and areas that the scan was 1549 * unable to reach, which will have a value defined by the DARK constant in this class. (typically, 1550 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 1551 * current measurement. 1552 * <br> 1553 * Portals and wrapping are not currently recommended in conjunction with multi-square creatures, since a 1554 * 2x2 creature could easily occupy two cells on the east edge and two cells on the west edge of the map, 1555 * and that poses all sorts of issues for creatures trying to pathfind to it, not to mention the more 1556 * general issues of how to display a bisected, but mobile, creature. 1557 * 1558 * @param size The length of one side of a square creature using this to find a path, i.e. 2 for a 2x2 cell 1559 * creature. Non-square creatures are not supported because turning is really hard. 1560 * @param limit The maximum number of steps to scan outward from a goal. 1561 * @param start the encoded index of the start of the pathfinder; when this has a path from goal to start, it ends 1562 * @param usable how much of impassable to actually use; should usually be equal to impassable.length, but can be 1563 * anything if impassable is null (then, it is ignored). This exists to differentiate this method from 1564 * the overload that takes an IntVLA when that argument is null, but also to give some flexibility. 1565 * @param impassable An array of encoded int keys representing the locations of enemies or other moving obstacles to 1566 * a path that cannot be moved through; this can be null if there are no such obstacles. 1567 * @return An int array using the dimensions/rotations of what this knows about the physical map. 1568 */ 1569 public double[] partialScanToStartLarge(int size, int limit, int start, int usable, int[] impassable) { 1570 if(impassable == null) 1571 partialScanLargeInternal(start, size, limit, null, -1); 1572 else 1573 partialScanLargeInternal(start, size, limit, impassable, Math.min(usable, impassable.length)); 1574 int maxLength = gradientMap.length; 1575 double[] gradientClone = new double[maxLength]; 1576 for (int l = 0; l < maxLength; l++) { 1577 if (gradientMap[l] == FLOOR) { 1578 gradientMap[l] = DARK; 1579 } 1580 } 1581 System.arraycopy(gradientMap, 0, gradientClone, 0, maxLength); 1582 return gradientClone; 1583 } 1584 /** 1585 * Recalculate the CustomDijkstra map, up to a limit, for a creature that is potentially larger than 1x1 cell, 1586 * stopping early if a path is found between a goal and start, and return that map. The value of 1587 * a cell in the returned CustomDijkstra map assumes that a creature is square, with a side length equal to the passed 1588 * size, that its minimum-x, minimum-y cell is the starting cell, and that any cell with a distance number 1589 * represents the distance for the creature's minimum-x, minimum-y cell to reach it. Cells that cannot be entered 1590 * by the minimum-x, minimum-y cell because of sizing (such as a floor cell next to a maximum-x and/or maximum-y 1591 * wall if size is > 1) will be marked as DARK. Cells that were marked as goals with setGoal will have 1592 * a value of 0, the cells adjacent to goals will have a value of 1, and cells progressively further 1593 * from goals will have a value equal to the distance from the nearest goal. The exceptions are walls, 1594 * which will have a value defined by the WALL constant in this class, and areas that the scan was 1595 * unable to reach, which will have a value defined by the DARK constant in this class. (typically, 1596 * these areas should not be used to place NPCs or items and should be filled with walls). This uses the 1597 * current measurement. 1598 * <br> 1599 * Portals and wrapping are not currently recommended in conjunction with multi-square creatures, since a 1600 * 2x2 creature could easily occupy two cells on the east edge and two cells on the west edge of the map, 1601 * and that poses all sorts of issues for creatures trying to pathfind to it, not to mention the more 1602 * general issues of how to display a bisected, but mobile, creature. 1603 * 1604 * @param size The length of one side of a square creature using this to find a path, i.e. 2 for a 2x2 cell 1605 * creature. Non-square creatures are not supported because turning is really hard. 1606 * @param limit The maximum number of steps to scan outward from a goal. 1607 * @param start the encoded index of the start of the pathfinder; when this has a path from goal to start, it ends 1608 * @param impassable An IntVLA where items are ints representing the locations of enemies or other moving obstacles 1609 * to a path that cannot be moved through; this can be null if there are no such obstacles. 1610 * @return A 2D int[width][height] using the width and height of what this knows about the physical map. 1611 */ 1612 public double[] partialScanToStartLarge(int size, int limit, int start, IntVLA impassable) { 1613 if(impassable == null) 1614 partialScanLargeInternal(start, size, limit, null, -1); 1615 else 1616 partialScanLargeInternal(start, size, limit, impassable.items, impassable.size); 1617 int maxLength = gradientMap.length; 1618 double[] gradientClone = new double[maxLength]; 1619 for (int l = 0; l < maxLength; l++) { 1620 if (gradientMap[l] == FLOOR) { 1621 gradientMap[l] = DARK; 1622 } 1623 } 1624 System.arraycopy(gradientMap, 0, gradientClone, 0, maxLength); 1625 return gradientClone; 1626 } 1627 1628 protected void partialScanLargeInternal(int start, int size, int limit, int[] impassable, int usable) 1629 { 1630 if (!initialized) return; 1631 Adjacency adjacency = this.adjacency; 1632 int[][] fromNeighbors = neighbors[0]; 1633 int near, cen, mid, neighborCount = fromNeighbors.length, sx, sy, sr, sn; 1634 1635 if (impassable != null) { 1636 if(usable > impassable.length) 1637 usable = impassable.length; 1638 for (int i = 0; i < usable; i++) { 1639 adjacency.putAllVariants(null, gradientMap, impassable[i], WALL, -size); 1640 } 1641 } 1642 mappedCount = goals.size; 1643 for (int i = 0; i < mappedCount; i++) { 1644 cen = goals.get(i); 1645 sx = adjacency.extractX(cen); 1646 sy = adjacency.extractY(cen); 1647 sr = adjacency.extractR(cen); 1648 sn = adjacency.extractN(cen); 1649 for (int xx = 0; xx < size; xx++) { 1650 for (int yy = 0; yy < size; yy++) { 1651 cen = adjacency.composite(sx - xx, sy - yy, sr, sn); 1652 if(cen >= 0) 1653 gradientMap[cen] = 0; 1654 } 1655 } 1656 } 1657 double currentLowest = 999000, cs, csm, dist; 1658 fresh.clear(); 1659 int maxLength = gradientMap.length; 1660 for (int l = 0; l < maxLength; l++) { 1661 if (gradientMap[l] <= FLOOR) { 1662 if (gradientMap[l] < currentLowest) { 1663 currentLowest = gradientMap[l]; 1664 fresh.clear(); 1665 fresh.add(l); 1666 } else if (gradientMap[l] == currentLowest) { 1667 fresh.add(l); 1668 } 1669 } 1670 } 1671 int fsz, numAssigned = fresh.size; 1672 IntDoubleOrderedMap costs = adjacency.costRules; 1673 boolean standardCosts = adjacency.hasStandardCost(); 1674 int iter = 0; 1675 while (numAssigned > 0 && iter++ < limit) { 1676 numAssigned = 0; 1677 fsz = fresh.size; 1678 for (int ci = fsz-1; ci >= 0; ci--) { 1679 cen = fresh.removeIndex(ci); 1680 dist = gradientMap[cen]; 1681 for (int d = 0; d < neighborCount; d++) { 1682 near = fromNeighbors[d][cen]; 1683 if (!adjacency.validate(near)) 1684 // Outside the map 1685 continue; 1686 if(isBlocked(cen, d)) 1687 continue; 1688 if(adjacency.twoStepRule) { 1689 near = fromNeighbors[d][mid = near]; 1690 // Outside the map 1691 if (!adjacency.validate(near)) 1692 continue; 1693 if(isBlocked(mid, d)) 1694 continue; 1695 csm = (costs.get(costMap[mid]) * heuristics[d]); 1696 cs = (costs.get(costMap[near]) * heuristics[d]); 1697 if ((gradientMap[mid] = dist + csm) + cs < gradientMap[near]) { 1698 setFresh(near, dist + cs + csm); 1699 ++numAssigned; 1700 ++mappedCount; 1701 if(start == near && standardCosts) 1702 { 1703 if (impassable != null) 1704 adjacency.resetAllVariants(gradientMap, impassable, usable, physicalMap, -size); 1705 return; 1706 } 1707 } 1708 } 1709 else 1710 { 1711 cs = (costs.get(costMap[near] | (adjacency.extractR(cen) == adjacency.extractR(near) ? 0 : 0x10000)) 1712 * heuristics[d]); 1713 //int h = adjacency.measurement.heuristic(adjacency.directions[d]); 1714 if (gradientMap[cen] + cs < gradientMap[near]) { 1715 setFresh(near, dist + cs); 1716 ++numAssigned; 1717 ++mappedCount; 1718 if(start == near && standardCosts) 1719 { 1720 if (impassable != null) 1721 adjacency.resetAllVariants(gradientMap, impassable, usable, physicalMap, -size); 1722 return; 1723 } 1724 } 1725 } 1726 } 1727 } 1728 } 1729 1730 if (impassable != null) 1731 adjacency.resetAllVariants(gradientMap, impassable, usable, physicalMap, -size); 1732 } 1733 1734 /** 1735 * Scans the dungeon using CustomDijkstraMap.scan with the listed goals and start point, and returns a list 1736 * of Coord positions (using the current measurement) needed to get closer to the closest reachable 1737 * goal. The maximum length of the returned list is given by length; if moving the full length of 1738 * the list would place the mover in a position shared by one of the positions in onlyPassable 1739 * (which is typically filled with friendly units that can be passed through in multi-tile- 1740 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 1741 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 1742 * through, and will be ignored if there is a goal overlapping one. 1743 * <br> 1744 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 1745 * each call to a pathfinding method. 1746 * @param length the length of the path to calculate 1747 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 1748 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 1749 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 1750 * @param targets a vararg or array of Coord that this will try to pathfind toward 1751 * @return an ArrayList of Coord that will contain the locations of this creature as it goes toward a target. Copy of path. 1752 */ 1753 public IntVLA findPath(int length, IntVLA impassable, 1754 IntVLA onlyPassable, int start, int... targets) 1755 { 1756 return findPath(length, -1, impassable, onlyPassable, start, targets); 1757 } 1758 1759 1760 /** 1761 * Scans the dungeon using CustomDijkstraMap.scan with the listed goals and start point, and returns a list 1762 * of Coord positions (using the current measurement) needed to get closer to the closest reachable 1763 * goal. The maximum length of the returned list is given by length; if moving the full length of 1764 * the list would place the mover in a position shared by one of the positions in onlyPassable 1765 * (which is typically filled with friendly units that can be passed through in multi-tile- 1766 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 1767 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 1768 * through, and will be ignored if there is a goal overlapping one. 1769 * <br> 1770 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 1771 * each call to a pathfinding method. 1772 * @param length the length of the path to calculate 1773 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 1774 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 1775 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 1776 * @param targets a vararg or array of Coord that this will try to pathfind toward 1777 * @return an ArrayList of Coord that will contain the locations of this creature as it goes toward a target. Copy of path. 1778 */ 1779 public IntVLA findPath(int length, int scanLimit, IntVLA impassable, 1780 IntVLA onlyPassable, int start, int... targets) 1781 { 1782 if (!initialized) return null; 1783 path.clear(); 1784 IntVLA impassable2; 1785 if (impassable == null) 1786 impassable2 = new IntVLA(); 1787 else { 1788 impassable2 = new IntVLA(impassable); 1789 impassable2.removeValue(start); 1790 } 1791 if (onlyPassable == null) 1792 onlyPassable = new IntVLA(); 1793 1794 resetMap(); 1795 for (int g = 0; g < targets.length; g++) { 1796 setGoal(targets[g]); 1797 } 1798 if (goals.size == 0) 1799 return new IntVLA(path); 1800 Adjacency adjacency = this.adjacency; 1801 int[][] toNeighbors = neighbors[1]; 1802 if(length < 0) 1803 length = 0; 1804 if(scanLimit <= 0 || scanLimit < length) 1805 scanInternal(start, impassable2.items, impassable2.size); 1806 else 1807 partialScanInternal(start, scanLimit, impassable2.items, impassable2.size); 1808 1809 int currentPos = start, pt; 1810 int paidLength = 0; 1811 while (true) { 1812 if (frustration > 500) { 1813 path.clear(); 1814 break; 1815 } 1816 double best = gradientMap[currentPos]; 1817 rng.randomOrdering(adjacency.maxAdjacent, reuse); 1818 int choice = rng.nextInt(adjacency.maxAdjacent); 1819 1820 for (int d = 0; d < adjacency.maxAdjacent; d++) { 1821 pt = toNeighbors[reuse[d]][currentPos]; 1822 if(adjacency.twoStepRule) 1823 pt = toNeighbors[reuse[d]][pt]; 1824 if (gradientMap[pt] < best && !path.contains(pt)) { 1825 best = gradientMap[pt]; 1826 choice = reuse[d];// adjacency.invertAdjacent[reuse[d]]; 1827 } 1828 } 1829 1830 1831 if (best >= gradientMap[currentPos] || physicalMap[toNeighbors[choice][currentPos]] > FLOOR) { 1832 break; 1833 } 1834 currentPos = toNeighbors[choice][pt = currentPos]; 1835 if(adjacency.twoStepRule) 1836 currentPos = toNeighbors[choice][currentPos]; 1837 path.add(currentPos); 1838 paidLength += adjacency.costRules.get(costMap[currentPos] | (adjacency.extractR(pt) == adjacency.extractR(currentPos) ? 0 : 0x10000)); 1839 frustration++; 1840 if (paidLength > length - 1.0) { 1841 if (onlyPassable.contains(currentPos)) { 1842 setOccupied(currentPos); 1843 impassable2.add(currentPos); 1844 return findPath(length, scanLimit, impassable2, onlyPassable, start, targets); 1845 } 1846 break; 1847 } 1848 if (gradientMap[currentPos] == 0) 1849 break; 1850 } 1851 frustration = 0; 1852 goals.clear(); 1853 return new IntVLA(path); 1854 } 1855 1856 1857 // TODO: Tackle these next two once there's a CustomLOS class 1858 /* 1859 * Scans the dungeon using CustomDijkstraMap.scan with the listed goals and start point, and returns a list 1860 * of Coord positions (using the current measurement) needed to get closer to a goal, until preferredRange is 1861 * reached, or further from a goal if the preferredRange has not been met at the current distance. 1862 * The maximum length of the returned list is given by moveLength; if moving the full length of 1863 * the list would place the mover in a position shared by one of the positions in onlyPassable 1864 * (which is typically filled with friendly units that can be passed through in multi-tile- 1865 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 1866 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 1867 * through, and will be ignored if there is a goal overlapping one. 1868 * <br> 1869 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 1870 * each call to a pathfinding method. 1871 * 1872 * @param moveLength the length of the path to calculate 1873 * @param preferredRange the distance this unit will try to keep from a target 1874 * @param los a squidgrid.LOS object if the preferredRange should try to stay in line of sight, or null if LoS 1875 * should be disregarded. 1876 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 1877 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 1878 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 1879 * @param targets a vararg or array of Coord that this will try to pathfind toward 1880 * @return an ArrayList of Coord that will contain the locations of this creature as it goes toward a target. Copy of path. 1881 * / 1882 public ArrayList<Coord> findAttackPath(int moveLength, int preferredRange, LOS los, Set<Coord> impassable, 1883 Set<Coord> onlyPassable, Coord start, Coord... targets) { 1884 return findAttackPath(moveLength, preferredRange, preferredRange, los, impassable, onlyPassable, start, targets); 1885 } 1886 */ 1887 /* 1888 * Scans the dungeon using CustomDijkstraMap.scan with the listed goals and start point, and returns a list 1889 * of Coord positions (using the current measurement) needed to get closer to a goal, until a cell is reached with 1890 * a distance from a goal that is at least equal to minPreferredRange and no more than maxPreferredRange, 1891 * which may go further from a goal if the minPreferredRange has not been met at the current distance. 1892 * The maximum length of the returned list is given by moveLength; if moving the full length of 1893 * the list would place the mover in a position shared by one of the positions in onlyPassable 1894 * (which is typically filled with friendly units that can be passed through in multi-tile- 1895 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 1896 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 1897 * through, and will be ignored if there is a goal overlapping one. 1898 * <br> 1899 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 1900 * each call to a pathfinding method. 1901 * 1902 * @param moveLength the length of the path to calculate 1903 * @param minPreferredRange the (inclusive) lower bound of the distance this unit will try to keep from a target 1904 * @param maxPreferredRange the (inclusive) upper bound of the distance this unit will try to keep from a target 1905 * @param los a squidgrid.LOS object if the preferredRange should try to stay in line of sight, or null if LoS 1906 * should be disregarded. 1907 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 1908 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 1909 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 1910 * @param targets a vararg or array of Coord that this will try to pathfind toward 1911 * @return an ArrayList of Coord that will contain the locations of this creature as it goes toward a target. Copy of path. 1912 * / 1913 public ArrayList<Coord> findAttackPath(int moveLength, int minPreferredRange, int maxPreferredRange, LOS los, 1914 Set<Coord> impassable, Set<Coord> onlyPassable, Coord start, Coord... targets) { 1915 if (!initialized) return null; 1916 if (minPreferredRange < 0) minPreferredRange = 0; 1917 if (maxPreferredRange < minPreferredRange) maxPreferredRange = minPreferredRange; 1918 int[][] resMap = new int[width][height]; 1919 if (los != null) { 1920 for (int x = 0; x < width; x++) { 1921 for (int y = 0; y < height; y++) { 1922 resMap[x][y] = (physicalMap[x][y] == WALL) ? 1.0 : 0; 1923 } 1924 } 1925 } 1926 path.clear(); 1927 OrderedSet<Coord> impassable2; 1928 if (impassable == null) 1929 impassable2 = new OrderedSet<>(); 1930 else 1931 impassable2 = new OrderedSet<>(impassable); 1932 if (onlyPassable == null) 1933 onlyPassable = new OrderedSet<>(); 1934 1935 resetMap(); 1936 for (Coord goal : targets) { 1937 setGoal(goal.x, goal.y); 1938 } 1939 if (goals.isEmpty()) 1940 return new ArrayList<>(path); 1941 1942 Measurement mess = measurement; 1943 if (measurement == Measurement.EUCLIDEAN) { 1944 measurement = Measurement.CHEBYSHEV; 1945 } 1946 scan(impassable2); 1947 goals.clear(); 1948 1949 for (int x = 0; x < width; x++) { 1950 CELL: 1951 for (int y = 0; y < height; y++) { 1952 if (gradientMap[x][y] == WALL || gradientMap[x][y] == DARK) 1953 continue; 1954 if (gradientMap[x][y] >= minPreferredRange && gradientMap[x][y] <= maxPreferredRange) { 1955 1956 for (Coord goal : targets) { 1957 if (los == null || los.isReachable(resMap, x, y, goal.x, goal.y)) { 1958 setGoal(x, y); 1959 gradientMap[x][y] = 0; 1960 continue CELL; 1961 } 1962 } 1963 gradientMap[x][y] = FLOOR; 1964 } else 1965 gradientMap[x][y] = FLOOR; 1966 } 1967 } 1968 measurement = mess; 1969 scan(impassable2); 1970 1971 Coord currentPos = start; 1972 int paidLength = 0; 1973 while (true) { 1974 if (frustration > 500) { 1975 path.clear(); 1976 break; 1977 } 1978 int best = gradientMap[currentPos.x][currentPos.y]; 1979 final Direction[] dirs = appendDirToShuffle(rng); 1980 int choice = rng.nextInt(dirs.length); 1981 1982 for (int d = 0; d < dirs.length; d++) { 1983 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 1984 if (gradientMap[pt.x][pt.y] < best) { 1985 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 1986 best = gradientMap[pt.x][pt.y]; 1987 choice = d; 1988 } 1989 } 1990 } 1991 1992 if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 1993 path.clear(); 1994 break; 1995 } 1996 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 1997 path.add(Coord.get(currentPos.x, currentPos.y)); 1998 paidLength += costMap[currentPos.x][currentPos.y]; 1999 frustration++; 2000 if (paidLength > moveLength - 1) { 2001 2002 if (onlyPassable.contains(currentPos)) { 2003 2004 closed.put(currentPos.encode(), WALL); 2005 impassable2.add(currentPos); 2006 return findAttackPath(moveLength, minPreferredRange, maxPreferredRange, los, impassable2, 2007 onlyPassable, start, targets); 2008 } 2009 break; 2010 } 2011 if (gradientMap[currentPos.x][currentPos.y] == 0) 2012 break; 2013 } 2014 frustration = 0; 2015 goals.clear(); 2016 return new ArrayList<>(path); 2017 } 2018 */ 2019 2020 2021 private double cachedLongerPaths = 1.2; 2022 private long cachedImpassable, cachedFearSources; 2023 private double[] cachedFleeMap; 2024 private int cachedSize = 1; 2025 2026 /** 2027 * Scans the dungeon using CustomDijkstraMap.scan with the listed fearSources and start point, and returns a list 2028 * of Coord positions (using Manhattan distance) needed to get further from the closest fearSources, meant 2029 * for running away. The maximum length of the returned list is given by length; if moving the full 2030 * length of the list would place the mover in a position shared by one of the positions in onlyPassable 2031 * (which is typically filled with friendly units that can be passed through in multi-tile- 2032 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 2033 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2034 * through, and will be ignored if there is a fearSource overlapping one. The preferLongerPaths parameter 2035 * is meant to be tweaked and adjusted; higher values should make creatures prefer to escape out of 2036 * doorways instead of hiding in the closest corner, and a value of 1.2 should be typical for many maps. 2037 * The parameters preferLongerPaths, impassable, and the varargs used for fearSources will be cached, and 2038 * any subsequent calls that use the same values as the last values passed will avoid recalculating 2039 * unnecessary scans. 2040 * <br> 2041 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 2042 * each call to a pathfinding method. 2043 * 2044 * @param length the length of the path to calculate 2045 * @param preferLongerPaths Set this to 1.2 if you aren't sure; it will probably need tweaking for different maps. 2046 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 2047 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 2048 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 2049 * @param fearSources a vararg or array of Coord positions to run away from 2050 * @return an ArrayList of Coord that will contain the locations of this creature as it goes away from fear sources. Copy of path. 2051 */ 2052 public IntVLA findFleePath(int length, double preferLongerPaths, IntVLA impassable, 2053 IntVLA onlyPassable, int start, int... fearSources) { 2054 return findFleePath(length, -1, preferLongerPaths, impassable, onlyPassable, start, fearSources); 2055 } 2056 /** 2057 * Scans the dungeon using CustomDijkstraMap.scan with the listed fearSources and start point, and returns a list 2058 * of Coord positions (using Manhattan distance) needed to get further from the closest fearSources, meant 2059 * for running away. The maximum length of the returned list is given by length; if moving the full 2060 * length of the list would place the mover in a position shared by one of the positions in onlyPassable 2061 * (which is typically filled with friendly units that can be passed through in multi-tile- 2062 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 2063 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2064 * through, and will be ignored if there is a fearSource overlapping one. The preferLongerPaths parameter 2065 * is meant to be tweaked and adjusted; higher values should make creatures prefer to escape out of 2066 * doorways instead of hiding in the closest corner, and a value of 1.2 should be typical for many maps. 2067 * The parameters preferLongerPaths, impassable, and the varargs used for fearSources will be cached, and 2068 * any subsequent calls that use the same values as the last values passed will avoid recalculating 2069 * unnecessary scans. 2070 * <br> 2071 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 2072 * each call to a pathfinding method. 2073 * 2074 * @param length the length of the path to calculate 2075 * @param scanLimit how many steps away from a fear source to calculate; negative scans the whole map 2076 * @param preferLongerPaths Set this to 1.2 if you aren't sure; it will probably need tweaking for different maps. 2077 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 2078 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 2079 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 2080 * @param fearSources a vararg or array of Coord positions to run away from 2081 * @return an ArrayList of Coord that will contain the locations of this creature as it goes away from fear sources. Copy of path. 2082 */ 2083 public IntVLA findFleePath(int length, int scanLimit, double preferLongerPaths, IntVLA impassable, 2084 IntVLA onlyPassable, int start, int... fearSources) { 2085 2086 if (!initialized) return null; 2087 path.clear(); 2088 IntVLA impassable2; 2089 if (impassable == null) 2090 impassable2 = new IntVLA(); 2091 else 2092 impassable2 = new IntVLA(impassable); 2093 2094 if (onlyPassable == null) 2095 onlyPassable = new IntVLA(); 2096 if (fearSources == null || fearSources.length < 1) { 2097 path.clear(); 2098 return new IntVLA(1); 2099 } 2100 if (cachedSize == 1 && preferLongerPaths == cachedLongerPaths && impassable2.hash64() == cachedImpassable && 2101 CrossHash.hash64(fearSources) == cachedFearSources) { 2102 gradientMap = cachedFleeMap; 2103 } else { 2104 cachedLongerPaths = preferLongerPaths; 2105 cachedImpassable = impassable2.hash64(); 2106 cachedFearSources = CrossHash.hash64(fearSources); 2107 cachedSize = 1; 2108 resetMap(); 2109 for (int g = 0; g < fearSources.length; g++) { 2110 setGoal(fearSources[g]); 2111 } 2112 if (goals.size == 0) 2113 return new IntVLA(path); 2114 if(length < 0) length = 0; 2115 if(scanLimit <= 0 || scanLimit < length) 2116 cachedFleeMap = scan(impassable2); 2117 else 2118 cachedFleeMap = partialScan(scanLimit, impassable2); 2119 2120 for (int l = 0; l < gradientMap.length; l++) { 2121 gradientMap[l] *= (gradientMap[l] >= FLOOR) ? 1 : - preferLongerPaths; 2122 } 2123 if(scanLimit <= 0 || scanLimit < length) 2124 cachedFleeMap = scan(impassable2); 2125 else 2126 cachedFleeMap = partialScan(scanLimit, impassable2); 2127 2128 } 2129 Adjacency adjacency = this.adjacency; 2130 int[][] toNeighbors = neighbors[1]; 2131 int currentPos = start, pt; 2132 int paidLength = 0; 2133 while (true) { 2134 if (frustration > 500) { 2135 path.clear(); 2136 break; 2137 } 2138 double best = gradientMap[currentPos]; 2139 rng.randomOrdering(adjacency.maxAdjacent, reuse); 2140 int choice = rng.nextInt(adjacency.maxAdjacent); 2141 2142 for (int d = 0; d < adjacency.maxAdjacent; d++) { 2143 pt = toNeighbors[reuse[d]][currentPos]; 2144 if (gradientMap[pt] < best && !path.contains(pt)) { 2145 best = gradientMap[pt]; 2146 choice = reuse[d]; 2147 } 2148 } 2149 2150 2151 if (best >= gradientMap[currentPos] || physicalMap[toNeighbors[choice][currentPos]] > FLOOR) { 2152 path.clear(); 2153 break; 2154 } 2155 currentPos = toNeighbors[choice][pt = currentPos]; 2156 path.add(currentPos); 2157 paidLength += adjacency.costRules.get(costMap[currentPos] | (adjacency.extractR(pt) == adjacency.extractR(currentPos) ? 0 : 0x10000)); 2158 frustration++; 2159 if (paidLength > length - 1) { 2160 if (onlyPassable.contains(currentPos)) { 2161 setOccupied(currentPos); 2162 impassable2.add(currentPos); 2163 return findFleePath(length, scanLimit, preferLongerPaths, impassable2, onlyPassable, start, fearSources); 2164 } 2165 break; 2166 } 2167 } 2168 frustration = 0; 2169 goals.clear(); 2170 return new IntVLA(path); 2171 } 2172 2173 /** 2174 * Scans the dungeon using CustomDijkstraMap.scan with the listed goals and start point, and returns a list 2175 * of Coord positions (using the current measurement) needed to get closer to the closest reachable 2176 * goal. The maximum length of the returned list is given by length; if moving the full length of 2177 * the list would place the mover in a position shared by one of the positions in onlyPassable 2178 * (which is typically filled with friendly units that can be passed through in multi-tile- 2179 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 2180 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2181 * through, and will be ignored if there is a goal overlapping one. 2182 * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The 2183 * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and 2184 * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. 2185 * <br> 2186 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 2187 * each call to a pathfinding method. 2188 * 2189 * @param size the side length of the creature trying to find a path 2190 * @param length the length of the path to calculate 2191 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 2192 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 2193 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 2194 * @param targets a vararg or array of Coord that this will try to pathfind toward 2195 * @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. 2196 */ 2197 public IntVLA findPathLarge (int size, int length, IntVLA impassable, 2198 IntVLA onlyPassable, int start, int... targets) { 2199 return findPathLarge(size, length, -1, impassable, onlyPassable, start, targets); 2200 } 2201 /** 2202 * Scans the dungeon using CustomDijkstraMap.scanLarge with the listed goals and start point, and returns a list 2203 * of Coord positions (using the current measurement) needed to get closer to the closest reachable 2204 * goal. The maximum length of the returned list is given by length; if moving the full length of 2205 * the list would place the mover in a position shared by one of the positions in onlyPassable 2206 * (which is typically filled with friendly units that can be passed through in multi-tile- 2207 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 2208 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2209 * through, and will be ignored if there is a goal overlapping one. 2210 * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The 2211 * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and 2212 * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. 2213 * <br> 2214 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 2215 * each call to a pathfinding method. 2216 * 2217 * @param size the side length of the creature trying to find a path 2218 * @param length the length of the path to calculate 2219 * @param scanLimit how many steps away from a goal to calculate; negative scans the whole map 2220 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 2221 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 2222 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 2223 * @param targets a vararg or array of Coord that this will try to pathfind toward 2224 * @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. 2225 */ 2226 public IntVLA findPathLarge (int size, int length, int scanLimit, IntVLA impassable, 2227 IntVLA onlyPassable, int start, int... targets) { 2228 if (!initialized) return null; 2229 path.clear(); 2230 IntVLA impassable2; 2231 if (impassable == null) 2232 impassable2 = new IntVLA(); 2233 else 2234 impassable2 = new IntVLA(impassable); 2235 if (onlyPassable == null) 2236 onlyPassable = new IntVLA(); 2237 2238 resetMap(); 2239 for (int g = 0; g < targets.length; g++) { 2240 setGoal(targets[g]); 2241 } 2242 if (goals.size == 0) 2243 return new IntVLA(path); 2244 Adjacency adjacency = this.adjacency; 2245 int[][] toNeighbors = neighbors[1]; 2246 if(length < 0) 2247 length = 0; 2248 if(scanLimit <= 0 || scanLimit < length) 2249 scanLargeInternal(start, size, impassable2.items, impassable2.size); 2250 else 2251 partialScanLargeInternal(start, size, scanLimit, impassable2.items, impassable2.size); 2252 2253 int currentPos = start, pt; 2254 int paidLength = 0; 2255 while (true) { 2256 if (frustration > 500) { 2257 path.clear(); 2258 break; 2259 } 2260 double best = gradientMap[currentPos]; 2261 rng.randomOrdering(adjacency.maxAdjacent, reuse); 2262 int choice = rng.nextInt(adjacency.maxAdjacent); 2263 2264 for (int d = 0; d < adjacency.maxAdjacent; d++) { 2265 pt = toNeighbors[reuse[d]][currentPos]; 2266 if (gradientMap[pt] < best && !path.contains(pt)) { 2267 best = gradientMap[pt]; 2268 choice = reuse[d]; 2269 } 2270 } 2271 2272 2273 if (best >= gradientMap[currentPos] || physicalMap[toNeighbors[choice][currentPos]] > FLOOR) { 2274 path.clear(); 2275 break; 2276 } 2277 currentPos = toNeighbors[choice][pt = currentPos]; 2278 path.add(currentPos); 2279 paidLength += adjacency.costRules.get(costMap[currentPos] | (adjacency.extractR(pt) == adjacency.extractR(currentPos) ? 0 : 0x10000)); 2280 frustration++; 2281 if (paidLength > length - 1) { 2282 if (onlyPassable.contains(currentPos)) { 2283 setOccupied(currentPos); 2284 impassable2.add(currentPos); 2285 return findPathLarge(size, scanLimit, length, impassable2, onlyPassable, start, targets); 2286 } 2287 break; 2288 } 2289 if (gradientMap[currentPos] == 0) 2290 break; 2291 } 2292 frustration = 0; 2293 goals.clear(); 2294 return new IntVLA(path); 2295 } 2296 2297 /* 2298 public ArrayList<Coord> findPathLarge(int size, int length, Set<Coord> impassable, 2299 Set<Coord> onlyPassable, Coord start, Coord... targets) { 2300 if (!initialized) return null; 2301 path.clear(); 2302 OrderedSet<Coord> impassable2; 2303 if (impassable == null) 2304 impassable2 = new OrderedSet<>(); 2305 else 2306 impassable2 = new OrderedSet<>(impassable); 2307 2308 if (onlyPassable == null) 2309 onlyPassable = new OrderedSet<>(); 2310 2311 resetMap(); 2312 for (Coord goal : targets) { 2313 setGoal(goal.x, goal.y); 2314 } 2315 if (goals.isEmpty()) 2316 return new ArrayList<>(path); 2317 2318 scan(impassable2, size); 2319 Coord currentPos = start; 2320 int paidLength = 0; 2321 while (true) { 2322 if (frustration > 500) { 2323 path.clear(); 2324 break; 2325 } 2326 int best = gradientMap[currentPos.x][currentPos.y]; 2327 final Direction[] dirs = appendDirToShuffle(rng); 2328 int choice = rng.nextInt(dirs.length); 2329 2330 for (int d = 0; d < dirs.length; d++) { 2331 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 2332 if (gradientMap[pt.x][pt.y] < best) { 2333 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 2334 best = gradientMap[pt.x][pt.y]; 2335 choice = d; 2336 } 2337 } 2338 } 2339 2340 if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 2341 path.clear(); 2342 break; 2343 } 2344 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 2345 2346 path.add(currentPos); 2347 paidLength += costMap[currentPos.x][currentPos.y]; 2348 frustration++; 2349 if (paidLength > length - 1) { 2350 if (onlyPassable.contains(currentPos)) { 2351 2352 closed.put(currentPos.encode(), WALL); 2353 impassable2.add(currentPos); 2354 return findPathLarge(size, length, impassable2, onlyPassable, start, targets); 2355 } 2356 break; 2357 } 2358 if (gradientMap[currentPos.x][currentPos.y] == 0) 2359 break; 2360 } 2361 frustration = 0; 2362 goals.clear(); 2363 return new ArrayList<>(path); 2364 } 2365 */ 2366 // TODO: Again, this needs CustomLOS 2367 /* 2368 * Scans the dungeon using CustomDijkstraMap.scan with the listed goals and start point, and returns a list 2369 * of Coord positions (using the current measurement) needed to get closer to a goal, until preferredRange is 2370 * reached, or further from a goal if the preferredRange has not been met at the current distance. 2371 * The maximum length of the returned list is given by moveLength; if moving the full length of 2372 * the list would place the mover in a position shared by one of the positions in onlyPassable 2373 * (which is typically filled with friendly units that can be passed through in multi-tile- 2374 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 2375 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2376 * through, and will be ignored if there is a goal overlapping one. 2377 * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The 2378 * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and 2379 * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. 2380 * <br> 2381 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 2382 * each call to a pathfinding method. 2383 * 2384 * @param size the side length of the creature trying to find a path 2385 * @param moveLength the length of the path to calculate 2386 * @param preferredRange the distance this unit will try to keep from a target 2387 * @param los a squidgrid.LOS object if the preferredRange should try to stay in line of sight, or null if LoS 2388 * should be disregarded. 2389 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 2390 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 2391 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 2392 * @param targets a vararg or array of Coord that this will try to pathfind toward 2393 * @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. 2394 * / 2395 public ArrayList<Coord> findAttackPathLarge(int size, int moveLength, int preferredRange, LOS los, Set<Coord> impassable, 2396 Set<Coord> onlyPassable, Coord start, Coord... targets) { 2397 if (!initialized) return null; 2398 if (preferredRange < 0) preferredRange = 0; 2399 int[][] resMap = new int[width][height]; 2400 if (los != null) { 2401 for (int x = 0; x < width; x++) { 2402 for (int y = 0; y < height; y++) { 2403 resMap[x][y] = (physicalMap[x][y] == WALL) ? 1 : 0; 2404 } 2405 } 2406 } 2407 path.clear(); 2408 OrderedSet<Coord> impassable2; 2409 if (impassable == null) 2410 impassable2 = new OrderedSet<>(); 2411 else 2412 impassable2 = new OrderedSet<>(impassable); 2413 2414 if (onlyPassable == null) 2415 onlyPassable = new OrderedSet<>(); 2416 2417 resetMap(); 2418 for (Coord goal : targets) { 2419 setGoal(goal.x, goal.y); 2420 } 2421 if (goals.isEmpty()) 2422 return new ArrayList<>(path); 2423 2424 Measurement mess = measurement; 2425 if (measurement == Measurement.EUCLIDEAN) { 2426 measurement = Measurement.CHEBYSHEV; 2427 } 2428 scan(impassable2, size); 2429 goals.clear(); 2430 2431 for (int x = 0; x < width; x++) { 2432 CELL: 2433 for (int y = 0; y < height; y++) { 2434 if (gradientMap[x][y] == WALL || gradientMap[x][y] == DARK) 2435 continue; 2436 if (x + 2 < width && y + 2 < height && gradientMap[x][y] == preferredRange) { 2437 for (Coord goal : targets) { 2438 if (los == null 2439 || los.isReachable(resMap, x, y, goal.x, goal.y) 2440 || los.isReachable(resMap, x + 1, y, goal.x, goal.y) 2441 || los.isReachable(resMap, x, y + 1, goal.x, goal.y) 2442 || los.isReachable(resMap, x + 1, y + 1, goal.x, goal.y)) { 2443 setGoal(x, y); 2444 gradientMap[x][y] = 0; 2445 continue CELL; 2446 } 2447 } 2448 gradientMap[x][y] = FLOOR; 2449 } else 2450 gradientMap[x][y] = FLOOR; 2451 } 2452 } 2453 measurement = mess; 2454 scan(impassable2, size); 2455 2456 Coord currentPos = start; 2457 int paidLength = 0; 2458 while (true) { 2459 if (frustration > 500) { 2460 path.clear(); 2461 break; 2462 } 2463 int best = gradientMap[currentPos.x][currentPos.y]; 2464 final Direction[] dirs = appendDirToShuffle(rng); 2465 int choice = rng.nextInt(dirs.length); 2466 2467 for (int d = 0; d < dirs.length; d++) { 2468 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 2469 if (gradientMap[pt.x][pt.y] < best) { 2470 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 2471 best = gradientMap[pt.x][pt.y]; 2472 choice = d; 2473 } 2474 } 2475 } 2476 2477 if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 2478 path.clear(); 2479 break; 2480 } 2481 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 2482 path.add(currentPos); 2483 frustration++; 2484 paidLength += costMap[currentPos.x][currentPos.y]; 2485 if (paidLength > moveLength - 1) { 2486 if (onlyPassable.contains(currentPos)) { 2487 2488 closed.put(currentPos.encode(), WALL); 2489 impassable2.add(currentPos); 2490 return findAttackPathLarge(size, moveLength, preferredRange, los, impassable2, onlyPassable, start, targets); 2491 } 2492 break; 2493 } 2494 if (gradientMap[currentPos.x][currentPos.y] == 0) 2495 break; 2496 } 2497 frustration = 0; 2498 goals.clear(); 2499 return new ArrayList<>(path); 2500 } 2501 2502 /* 2503 * Scans the dungeon using CustomDijkstraMap.scan with the listed goals and start point, and returns a list 2504 * of Coord positions (using the current measurement) needed to get closer to a goal, until a cell is reached with 2505 * a distance from a goal that is at least equal to minPreferredRange and no more than maxPreferredRange, 2506 * which may go further from a goal if the minPreferredRange has not been met at the current distance. 2507 * The maximum length of the returned list is given by moveLength; if moving the full length of 2508 * the list would place the mover in a position shared by one of the positions in onlyPassable 2509 * (which is typically filled with friendly units that can be passed through in multi-tile- 2510 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 2511 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2512 * through, and will be ignored if there is a goal overlapping one. 2513 * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The 2514 * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and 2515 * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. 2516 * <br> 2517 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 2518 * each call to a pathfinding method. 2519 * 2520 * @param size the side length of the creature trying to find a path 2521 * @param moveLength the length of the path to calculate 2522 * @param minPreferredRange the (inclusive) lower bound of the distance this unit will try to keep from a target 2523 * @param maxPreferredRange the (inclusive) upper bound of the distance this unit will try to keep from a target 2524 * @param los a squidgrid.LOS object if the preferredRange should try to stay in line of sight, or null if LoS 2525 * should be disregarded. 2526 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 2527 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 2528 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 2529 * @param targets a vararg or array of Coord that this will try to pathfind toward 2530 * @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. 2531 * / 2532 public ArrayList<Coord> findAttackPathLarge(int size, int moveLength, int minPreferredRange, int maxPreferredRange, LOS los, 2533 Set<Coord> impassable, Set<Coord> onlyPassable, Coord start, Coord... targets) { 2534 if (!initialized) return null; 2535 if (minPreferredRange < 0) minPreferredRange = 0; 2536 if (maxPreferredRange < minPreferredRange) maxPreferredRange = minPreferredRange; 2537 int[][] resMap = new int[width][height]; 2538 if (los != null) { 2539 for (int x = 0; x < width; x++) { 2540 for (int y = 0; y < height; y++) { 2541 resMap[x][y] = (physicalMap[x][y] == WALL) ? 1 : 0; 2542 } 2543 } 2544 } 2545 path.clear(); 2546 OrderedSet<Coord> impassable2; 2547 if (impassable == null) 2548 impassable2 = new OrderedSet<>(); 2549 else 2550 impassable2 = new OrderedSet<>(impassable); 2551 2552 if (onlyPassable == null) 2553 onlyPassable = new OrderedSet<>(); 2554 2555 resetMap(); 2556 for (Coord goal : targets) { 2557 setGoal(goal); 2558 } 2559 if (goals.isEmpty()) 2560 return new ArrayList<>(path); 2561 2562 Measurement mess = measurement; 2563 if (measurement == Measurement.EUCLIDEAN) { 2564 measurement = Measurement.CHEBYSHEV; 2565 } 2566 scan(impassable2, size); 2567 goals.clear(); 2568 2569 for (int x = 0; x < width; x++) { 2570 CELL: 2571 for (int y = 0; y < height; y++) { 2572 if (gradientMap[x][y] == WALL || gradientMap[x][y] == DARK) 2573 continue; 2574 if (x + 2 < width && y + 2 < height && gradientMap[x][y] >= minPreferredRange && gradientMap[x][y] <= maxPreferredRange) { 2575 for (Coord goal : targets) { 2576 if (los == null 2577 || los.isReachable(resMap, x, y, goal.x, goal.y) 2578 || los.isReachable(resMap, x + 1, y, goal.x, goal.y) 2579 || los.isReachable(resMap, x, y + 1, goal.x, goal.y) 2580 || los.isReachable(resMap, x + 1, y + 1, goal.x, goal.y)) { 2581 setGoal(x, y); 2582 gradientMap[x][y] = 0; 2583 continue CELL; 2584 } 2585 } 2586 gradientMap[x][y] = FLOOR; 2587 } else 2588 gradientMap[x][y] = FLOOR; 2589 } 2590 } 2591 measurement = mess; 2592 scan(impassable2, size); 2593 2594 Coord currentPos = start; 2595 int paidLength = 0; 2596 while (true) { 2597 if (frustration > 500) { 2598 path.clear(); 2599 break; 2600 } 2601 2602 int best = gradientMap[currentPos.x][currentPos.y]; 2603 final Direction[] dirs = appendDirToShuffle(rng); 2604 int choice = rng.nextInt(dirs.length); 2605 2606 for (int d = 0; d < dirs.length; d++) { 2607 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 2608 if (gradientMap[pt.x][pt.y] < best) { 2609 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 2610 best = gradientMap[pt.x][pt.y]; 2611 choice = d; 2612 } 2613 } 2614 } 2615 if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 2616 path.clear(); 2617 break; 2618 } 2619 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 2620 2621 path.add(currentPos); 2622 frustration++; 2623 paidLength += costMap[currentPos.x][currentPos.y]; 2624 if (paidLength > moveLength - 1) { 2625 if (onlyPassable.contains(currentPos)) { 2626 2627 closed.put(currentPos.encode(), WALL); 2628 impassable2.add(currentPos); 2629 return findAttackPathLarge(size, moveLength, minPreferredRange, maxPreferredRange, los, impassable2, 2630 onlyPassable, start, targets); 2631 } 2632 break; 2633 } 2634 if (gradientMap[currentPos.x][currentPos.y] == 0) 2635 break; 2636 } 2637 frustration = 0; 2638 goals.clear(); 2639 return new ArrayList<>(path); 2640 } 2641 */ 2642 /** 2643 * Scans the dungeon using CustomDijkstraMap.scanLarge with the listed fearSources and start point, and returns a list 2644 * of Coord positions (using Manhattan distance) needed to get further from the closest fearSources, meant 2645 * for running away. The maximum length of the returned list is given by length; if moving the full 2646 * length of the list would place the mover in a position shared by one of the positions in onlyPassable 2647 * (which is typically filled with friendly units that can be passed through in multi-tile- 2648 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 2649 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2650 * through, and will be ignored if there is a fearSource overlapping one. The preferLongerPaths parameter 2651 * is meant to be tweaked and adjusted; higher values should make creatures prefer to escape out of 2652 * doorways instead of hiding in the closest corner, and a value of 1.2 should be typical for many maps. 2653 * The parameters size, preferLongerPaths, impassable, and the varargs used for fearSources will be cached, and 2654 * any subsequent calls that use the same values as the last values passed will avoid recalculating 2655 * unnecessary scans. Calls to findFleePath will cache as if size is 1, and may share a cache with this function. 2656 * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The 2657 * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and 2658 * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. 2659 * <br> 2660 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 2661 * each call to a pathfinding method. 2662 * 2663 * @param size the side length of the creature trying the find a path 2664 * @param length the length of the path to calculate 2665 * @param preferLongerPaths Set this to 1.2 if you aren't sure; it will probably need tweaking for different maps. 2666 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 2667 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 2668 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 2669 * @param fearSources a vararg or array of Coord positions to run away from 2670 * @return an ArrayList of Coord that will contain the locations of this creature as it goes away from fear sources. Copy of path. 2671 */ 2672 public IntVLA findFleePathLarge(int size, int length, int preferLongerPaths, IntVLA impassable, 2673 IntVLA onlyPassable, int start, int... fearSources) { 2674 return findFleePathLarge(size, length, -1, preferLongerPaths, impassable, onlyPassable, start, fearSources); 2675 } 2676 2677 2678 /** 2679 * Scans the dungeon using CustomDijkstraMap.scanLarge with the listed fearSources and start point, and returns a list 2680 * of Coord positions (using Manhattan distance) needed to get further from the closest fearSources, meant 2681 * for running away. The maximum length of the returned list is given by length; if moving the full 2682 * length of the list would place the mover in a position shared by one of the positions in onlyPassable 2683 * (which is typically filled with friendly units that can be passed through in multi-tile- 2684 * movement scenarios), it will recalculate a move so that it does not pass into that cell. 2685 * The keys in impassable should be the positions of enemies and obstacles that cannot be moved 2686 * through, and will be ignored if there is a fearSource overlapping one. The preferLongerPaths parameter 2687 * is meant to be tweaked and adjusted; higher values should make creatures prefer to escape out of 2688 * doorways instead of hiding in the closest corner, and a value of 1.2 should be typical for many maps. 2689 * The parameters size, preferLongerPaths, impassable, and the varargs used for fearSources will be cached, and 2690 * any subsequent calls that use the same values as the last values passed will avoid recalculating 2691 * unnecessary scans. Calls to findFleePath will cache as if size is 1, and may share a cache with this function. 2692 * The parameter size refers to the side length of a square unit, such as 2 for a 2x2 unit. The 2693 * parameter start must refer to the minimum-x, minimum-y cell of that unit if size is > 1, and 2694 * all positions in the returned path will refer to movement of the minimum-x, minimum-y cell. 2695 * <br> 2696 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 2697 * each call to a pathfinding method. 2698 * 2699 * @param size the side length of the creature trying the find a path 2700 * @param length the length of the path to calculate 2701 * @param scanLimit how many steps away from a goal to calculate; negative scans the whole map 2702 * @param preferLongerPaths Set this to 1.2 if you aren't sure; it will probably need tweaking for different maps. 2703 * @param impassable a Set of impassable Coord positions that may change (not constant like walls); can be null 2704 * @param onlyPassable a Set of Coord positions that this pathfinder cannot end a path occupying (typically allies); can be null 2705 * @param start the start of the path, should correspond to the minimum-x, minimum-y position of the pathfinder 2706 * @param fearSources a vararg or array of Coord positions to run away from 2707 * @return an ArrayList of Coord that will contain the locations of this creature as it goes away from fear sources. Copy of path. 2708 */ 2709 public IntVLA findFleePathLarge(int size, int length, int scanLimit, int preferLongerPaths, IntVLA impassable, 2710 IntVLA onlyPassable, int start, int... fearSources) { 2711 if (!initialized) return null; 2712 path.clear(); 2713 IntVLA impassable2; 2714 if (impassable == null) 2715 impassable2 = new IntVLA(); 2716 else 2717 impassable2 = new IntVLA(impassable); 2718 2719 if (onlyPassable == null) 2720 onlyPassable = new IntVLA(); 2721 if (fearSources == null || fearSources.length < 1) { 2722 path.clear(); 2723 return new IntVLA(1); 2724 } 2725 if (cachedSize == size && preferLongerPaths == cachedLongerPaths && impassable2.hash64() == cachedImpassable && 2726 CrossHash.hash64(fearSources) == cachedFearSources) { 2727 gradientMap = cachedFleeMap; 2728 } else { 2729 cachedLongerPaths = preferLongerPaths; 2730 cachedImpassable = impassable2.hash64(); 2731 cachedFearSources = CrossHash.hash64(fearSources); 2732 cachedSize = size; 2733 resetMap(); 2734 for (int g = 0; g < fearSources.length; g++) { 2735 setGoal(fearSources[g]); 2736 } 2737 if (goals.size == 0) 2738 return new IntVLA(path); 2739 2740 if(scanLimit <= 0 || scanLimit < length) 2741 cachedFleeMap = scanLarge(size, impassable2); 2742 else 2743 cachedFleeMap = partialScanLarge(size, scanLimit, impassable2); 2744 2745 for (int l = 0; l < gradientMap.length; l++) { 2746 gradientMap[l] *= (gradientMap[l] >= FLOOR) ? 1 : -preferLongerPaths; 2747 } 2748 if(scanLimit <= 0 || scanLimit < length) 2749 cachedFleeMap = scanLarge(size, impassable2); 2750 else 2751 cachedFleeMap = partialScanLarge(size, scanLimit, impassable2); 2752 } 2753 Adjacency adjacency = this.adjacency; 2754 int[][] toNeighbors = neighbors[1]; 2755 int currentPos = start, pt; 2756 int paidLength = 0; 2757 while (true) { 2758 if (frustration > 500) { 2759 path.clear(); 2760 break; 2761 } 2762 double best = gradientMap[currentPos]; 2763 rng.randomOrdering(adjacency.maxAdjacent, reuse); 2764 int choice = rng.nextInt(adjacency.maxAdjacent); 2765 2766 for (int d = 0; d < adjacency.maxAdjacent; d++) { 2767 pt = toNeighbors[reuse[d]][currentPos]; 2768 if (gradientMap[pt] < best && !path.contains(pt)) { 2769 best = gradientMap[pt]; 2770 choice = reuse[d]; 2771 } 2772 } 2773 2774 2775 if (best >= gradientMap[currentPos] || physicalMap[toNeighbors[choice][currentPos]] > FLOOR) { 2776 path.clear(); 2777 break; 2778 } 2779 currentPos = toNeighbors[choice][pt = currentPos]; 2780 path.add(currentPos); 2781 paidLength += adjacency.costRules.get(costMap[currentPos] | (adjacency.extractR(pt) == adjacency.extractR(currentPos) ? 0 : 0x10000)); 2782 frustration++; 2783 if (paidLength > length - 1) { 2784 if (onlyPassable.contains(currentPos)) { 2785 setOccupied(currentPos); 2786 impassable2.add(currentPos); 2787 return findFleePathLarge(size, scanLimit, length, preferLongerPaths, impassable2, onlyPassable, start, fearSources); 2788 } 2789 break; 2790 } 2791 } 2792 frustration = 0; 2793 goals.clear(); 2794 return new IntVLA(path); 2795 } 2796 2797 /* 2798 * Intended primarily for internal use. Needs scan() to already be called and at least one goal to already be set, 2799 * and does not restrict the length of the path or behave as if the pathfinder has allies or enemies. 2800 * <br> 2801 * This caches its result in a member field, path, which can be fetched after finding a path and will change with 2802 * each call to a pathfinding method. 2803 * 2804 * @param target the target cell 2805 * @return an ArrayList of Coord that make up the best path. Copy of path. 2806 * / 2807 public ArrayList<Coord> findPathPreScanned(Coord target) { 2808 if (!initialized || goals == null || goals.size == 0) return null; 2809 RNG rng2 = new StatefulRNG(new LightRNG(0xf00d)); 2810 path.clear(); 2811 Coord currentPos = target; 2812 while (true) { 2813 if (frustration > 2000) { 2814 path.clear(); 2815 break; 2816 } 2817 int best = gradientMap[currentPos.x][currentPos.y]; 2818 final Direction[] dirs = appendDirToShuffle(rng2); 2819 int choice = rng2.nextInt(dirs.length); 2820 2821 for (int d = 0; d < dirs.length; d++) { 2822 Coord pt = Coord.get(currentPos.x + dirs[d].deltaX, currentPos.y + dirs[d].deltaY); 2823 if (gradientMap[pt.x][pt.y] < best) { 2824 if (dirs[choice] == Direction.NONE || !path.contains(pt)) { 2825 best = gradientMap[pt.x][pt.y]; 2826 choice = d; 2827 } 2828 } 2829 } 2830 2831 if (best >= gradientMap[currentPos.x][currentPos.y] || physicalMap[currentPos.x + dirs[choice].deltaX][currentPos.y + dirs[choice].deltaY] > FLOOR) { 2832 path.clear(); 2833 break; 2834 } 2835 currentPos = currentPos.translate(dirs[choice].deltaX, dirs[choice].deltaY); 2836 path.add(0, currentPos); 2837 frustration++; 2838 2839 if (gradientMap[currentPos.x][currentPos.y] == 0) 2840 break; 2841 } 2842 frustration = 0; 2843 return new ArrayList<>(path); 2844 } 2845 */ 2846 /** 2847 * A simple limited flood-fill that returns a OrderedMap of Coord keys to the Double values in the CustomDijkstraMap, only 2848 * calculating out to a number of steps determined by limit. This can be useful if you need many flood-fills and 2849 * don't need a large area for each, or if you want to have an effect spread to a certain number of cells away. 2850 * 2851 * @param radius the number of steps to take outward from each starting position. 2852 * @param starts a vararg group of Points to step outward from; this often will only need to be one Coord. 2853 * @return A OrderedMap of Coord keys to Double values; the starts are included in this with the value 0. 2854 */ 2855 public IntDoubleOrderedMap floodFill(int radius, int... starts) { 2856 if (!initialized || starts == null) return null; 2857 IntDoubleOrderedMap fill = new IntDoubleOrderedMap(); 2858 2859 resetMap(); 2860 for (int g = 0; g < starts.length; g++) { 2861 setGoal(starts[g]); 2862 } 2863 2864 if (goals.size == 0) 2865 return fill; 2866 2867 partialScan(radius, -1, null); 2868 double temp; 2869 for (int l = 0; l < gradientMap.length; l++) { 2870 temp = gradientMap[l]; 2871 if (temp < FLOOR) { 2872 fill.put(l, temp); 2873 } 2874 } 2875 goals.clear(); 2876 return fill; 2877 } 2878 2879 public int getMappedCount() { 2880 return mappedCount; 2881 } 2882 2883 2884 /* 2885 public static void main(String[] args) { 2886 squidpony.squidgrid.mapping.DungeonGenerator dungeonGen = 2887 new squidpony.squidgrid.mapping.DungeonGenerator(40, 40, new RNG(0x1337BEEFDEAL)); 2888 char[][] map = dungeonGen.generate(); 2889 squidpony.squidgrid.mapping.DungeonUtility.debugPrint(map); 2890 squidpony.squidmath.GreasedRegion floors = new squidpony.squidmath.GreasedRegion(map, '.'); 2891 System.out.println("Floors: " + floors.size()); 2892 System.out.println("Percentage walkable: " + floors.size() / 16.0 + "%"); 2893 Adjacency adj = new BasicAdjacency(40, 40, Measurement.EUCLIDEAN); 2894 adj.blockingRule = 2; 2895 RNG rng = new RNG(0x1337BEEF); 2896 CustomDijkstraMap dijkstra = new CustomDijkstraMap( 2897 map, adj, rng); 2898 double[] scanned; 2899 short[][] sMap = new short[40][40]; 2900 //for (int x = 1; x < 39; x++) { 2901 // for (int y = 1; y < 39; y++) { 2902 squidpony.squidmath.Coord c = floors.singleRandom(rng); 2903 dijkstra.setGoal(adj.composite(c.x, c.y, 0, 0)); 2904 scanned = dijkstra.scan(null); 2905 dijkstra.clearGoals(); 2906 dijkstra.resetMap(); 2907 System.out.println("MAPPED: " + dijkstra.getMappedCount()); 2908 for (int i = 0; i < 1600; i++) { 2909 sMap[adj.extractX(i)][adj.extractY(i)] = (scanned[i] >= FLOOR ? -999 : (short) (scanned[i] * 100)); 2910 } 2911 for (int yy = 0; yy < 40; yy++) { 2912 for (int xx = 0; xx < 40; xx++) { 2913 System.out.print((sMap[xx][yy] == -999) ? "#### " : squidpony.StringKit.hex(sMap[xx][yy]) + ' '); 2914 } 2915 System.out.println(); 2916 } 2917 // } 2918 //} 2919 } 2920 */ 2921}