001package squidpony.squidai; 002 003import squidpony.squidgrid.FOV; 004import squidpony.squidgrid.Measurement; 005import squidpony.squidgrid.Radius; 006import squidpony.squidgrid.mapping.DungeonUtility; 007import squidpony.squidmath.Coord; 008import squidpony.squidmath.NumberTools; 009import squidpony.squidmath.OrderedMap; 010import squidpony.squidmath.OrderedSet; 011 012import java.io.Serializable; 013import java.util.ArrayList; 014import java.util.Arrays; 015import java.util.Collection; 016 017/** 018 * An AOE type that has an origin, a radius, an angle, and a span; it will blast from the origin to a length equal to 019 * radius along the angle (in degrees), moving somewhat around corners/obstacles, and also spread a total of span 020 * degrees around the angle (a span of 90 will affect a full quadrant, centered on angle). You can specify the 021 * RadiusType to Radius.DIAMOND for Manhattan distance, RADIUS.SQUARE for Chebyshev, or RADIUS.CIRCLE for Euclidean. 022 * 023 * RADIUS.CIRCLE (Euclidean measurement) will produce the most real-looking cones. This will produce doubles for its 024 * {@link #findArea()} method which are greater than 0.0 and less than or equal to 1.0. 025 * 026 * This class uses {@link FOV} to create its area of effect. 027 * Created by Tommy Ettinger on 7/13/2015. 028 */ 029public class ConeAOE implements AOE, Serializable { 030 private static final long serialVersionUID = 2L; 031 private FOV fov; 032 private Coord origin; 033 private double radius, angle, span; 034 private double[][] map; 035 private char[][] dungeon; 036 private Radius radiusType; 037 private Reach reach = new Reach(1, 1, Radius.SQUARE, AimLimit.FREE); 038 039 public ConeAOE(Coord origin, Coord endCenter, double span, Radius radiusType) 040 { 041 fov = new FOV(FOV.RIPPLE_LOOSE); 042 this.origin = origin; 043 radius = radiusType.radius(origin.x, origin.y, endCenter.x, endCenter.y); 044 045 angle = NumberTools.atan2_(endCenter.y - origin.y, endCenter.x - origin.x) * 360.0; 046// this.startAngle = Math.abs((angle - span / 2.0) % 360.0); 047// this.endAngle = Math.abs((angle + span / 2.0) % 360.0); 048 this.span = span; 049 this.radiusType = radiusType; 050 } 051 public ConeAOE(Coord origin, int radius, double angle, double span, Radius radiusType) 052 { 053 fov = new FOV(FOV.RIPPLE_LOOSE); 054 this.origin = origin; 055 this.radius = radius; 056// this.startAngle = Math.abs((angle - span / 2.0) % 360.0); 057// this.endAngle = Math.abs((angle + span / 2.0) % 360.0); 058 this.angle = angle; 059 this.span = span; 060 this.radiusType = radiusType; 061 } 062 063 @Override 064 public Coord getOrigin() { 065 return origin; 066 } 067 068 @Override 069 public void setOrigin(Coord origin) { 070 this.origin = origin; 071 } 072 073 @Override 074 public AimLimit getLimitType() { 075 return reach.limit; 076 } 077 078 @Override 079 public int getMinRange() { 080 return reach.minDistance; 081 } 082 083 @Override 084 public int getMaxRange() { 085 return reach.maxDistance; 086 } 087 088 @Override 089 public Radius getMetric() { 090 return reach.metric; 091 } 092 093 /** 094 * Gets the same values returned by getLimitType(), getMinRange(), getMaxRange(), and getMetric() bundled into one 095 * Reach object. 096 * 097 * @return a non-null Reach object. 098 */ 099 @Override 100 public Reach getReach() { 101 return reach; 102 } 103 104 @Override 105 public void setLimitType(AimLimit limitType) { 106 reach.limit = limitType; 107 108 } 109 110 @Override 111 public void setMinRange(int minRange) { 112 reach.minDistance = minRange; 113 } 114 115 @Override 116 public void setMaxRange(int maxRange) { 117 reach.maxDistance = maxRange; 118 119 } 120 121 @Override 122 public void setMetric(Radius metric) { 123 reach.metric = metric; 124 } 125 126 /** 127 * Sets the same values as setLimitType(), setMinRange(), setMaxRange(), and setMetric() using one Reach object. 128 * 129 * @param reach a non-null Reach object. 130 */ 131 @Override 132 public void setReach(Reach reach) { 133 if(reach != null) 134 this.reach = reach; 135 } 136 137 public double getRadius() { 138 return radius; 139 } 140 141 public void setRadius(double radius) { 142 this.radius = radius; 143 } 144 145 public double getAngle() { 146 return angle; 147 } 148 149 public void setAngle(double angle) { 150 if (reach.limit == null || reach.limit == AimLimit.FREE || 151 (reach.limit == AimLimit.EIGHT_WAY && (int) angle % 45 == 0) || 152 (reach.limit == AimLimit.DIAGONAL && (int) angle % 90 == 45) || 153 (reach.limit == AimLimit.ORTHOGONAL && (int) angle % 90 == 0)) { 154 this.angle = angle; 155// this.startAngle = Math.abs((angle - span / 2.0) % 360.0); 156// this.endAngle = Math.abs((angle + span / 2.0) % 360.0); 157 } 158 } 159 160 public void setEndCenter(Coord endCenter) { 161// radius = radiusType.radius(origin.x, origin.y, endCenter.x, endCenter.y); 162 if (AreaUtils.verifyLimit(reach.limit, origin, endCenter)) { 163 angle = NumberTools.atan2_(endCenter.y - origin.y, endCenter.x - origin.x) * 360.0; 164// startAngle = Math.abs((angle - span / 2.0) % 360.0); 165// endAngle = Math.abs((angle + span / 2.0) % 360.0); 166 } 167 } 168 169 public double getSpan() { 170 return span; 171 } 172 173 public void setSpan(double span) { 174 this.span = span; 175// this.startAngle = Math.abs((angle - span / 2.0) % 360.0); 176// this.endAngle = Math.abs((angle + span / 2.0) % 360.0); 177 } 178 179 public Radius getRadiusType() { 180 return radiusType; 181 } 182 183 public void setRadiusType(Radius radiusType) { 184 this.radiusType = radiusType; 185 } 186 187 @Override 188 public void shift(Coord aim) { 189 setEndCenter(aim); 190 } 191 192 @Override 193 public boolean mayContainTarget(Collection<Coord> targets) { 194 for (Coord p : targets) { 195 if (radiusType.radius(origin.x, origin.y, p.x, p.y) <= radius) { 196 double d = (angle - NumberTools.atan2_(p.y - origin.y, p.x - origin.x) * 360.0); 197 if(d > 180) 198 d = 360 - d; 199 if(d < span * 0.5) 200 return true; 201 } 202 } 203 return false; 204 } 205 206 @Override 207 public OrderedMap<Coord, ArrayList<Coord>> idealLocations(Collection<Coord> targets, Collection<Coord> requiredExclusions) { 208 if(targets == null) 209 return new OrderedMap<>(); 210 if(requiredExclusions == null) requiredExclusions = new OrderedSet<>(); 211 212 //requiredExclusions.remove(origin); 213 int totalTargets = targets.size(); 214 OrderedMap<Coord, ArrayList<Coord>> bestPoints = new OrderedMap<>(totalTargets * 8); 215 216 if(totalTargets == 0) 217 return bestPoints; 218 219 Coord[] ts = targets.toArray(new Coord[0]); 220 Coord[] exs = requiredExclusions.toArray(new Coord[0]); 221 Coord t; 222 223 double[][][] compositeMap = new double[totalTargets][dungeon.length][dungeon[0].length]; 224 double tAngle; 225 226 227 char[][] dungeonCopy = new char[dungeon.length][dungeon[0].length]; 228 double[][] tmpfov = new double[dungeon.length][dungeon[0].length]; 229 Coord tempPt; 230 231 for (int i = 0; i < exs.length; i++) { 232 t = exs[i]; 233 tAngle = NumberTools.atan2Degrees360(t.y - origin.y, t.x - origin.x); 234 FOV.reuseRippleFOV(map, tmpfov, 2, origin.x, origin.y, radius, radiusType, tAngle, span); 235 for (int x = 0; x < dungeon.length; x++) { 236 for (int y = 0; y < dungeon[x].length; y++) { 237 tempPt = Coord.get(x, y); 238 dungeonCopy[x][y] = !(origin.x == x && origin.y == y) && (tmpfov[x][y] > 0.0 || !AreaUtils.verifyLimit(reach.limit, origin, tempPt)) ? '!' : dungeon[x][y]; 239 } 240 } 241 } 242 243 DijkstraMap dm = new DijkstraMap(dungeon, radiusType.matchingMeasurement()); 244 for (int i = 0; i < ts.length; ++i) { 245 dm.initialize(dungeon); 246 t = ts[i]; 247 tAngle = NumberTools.atan2Degrees360(t.y - origin.y, t.x - origin.x); 248 249 tmpfov = fov.calculateFOV(map, origin.x, origin.y, radius, radiusType, tAngle, span); 250 251 252 for (int x = 0; x < dungeon.length; x++) { 253 for (int y = 0; y < dungeon[x].length; y++) { 254 if (tmpfov[x][y] > 0.0) 255 { 256 compositeMap[i][x][y] = dm.physicalMap[x][y]; 257 } 258 else compositeMap[i][x][y] = DijkstraMap.WALL; 259 } 260 } 261 if(compositeMap[i][t.x][t.y] > DijkstraMap.FLOOR) 262 { 263 for (int x = 0; x < dungeon.length; x++) { 264 Arrays.fill(compositeMap[i][x], 99999.0); 265 } 266 continue; 267 } 268 269 270 dm.initialize(compositeMap[i]); 271 dm.setGoal(t); 272 dm.scan(null, null, true); 273 for (int x = 0; x < dungeon.length; x++) { 274 for (int y = 0; y < dungeon[0].length; y++) { 275 if(dm.gradientMap[x][y] < DijkstraMap.FLOOR && dungeonCopy[x][y] != '!') 276 compositeMap[i][x][y] = dm.gradientMap[x][y]; 277 else 278 compositeMap[i][x][y] = 99999.0; 279 } 280 } 281 } 282 double bestQuality = 99999 * ts.length; 283 double[][] qualityMap = new double[dungeon.length][dungeon[0].length]; 284 for (int x = 0; x < qualityMap.length; x++) { 285 for (int y = 0; y < qualityMap[x].length; y++) { 286 qualityMap[x][y] = 0.0; 287 long bits = 0; 288 for (int i = 0; i < ts.length; ++i) { 289 qualityMap[x][y] += compositeMap[i][x][y]; 290 if(compositeMap[i][x][y] < 99999.0 && i < 63) 291 bits |= 1 << i; 292 } 293 if(qualityMap[x][y] < bestQuality) 294 { 295 ArrayList<Coord> ap = new ArrayList<>(); 296 297 for (int i = 0; i < ts.length && i < 63; ++i) { 298 if((bits & (1 << i)) != 0) 299 ap.add(ts[i]); 300 } 301 if(ap.size() > 0) { 302 bestQuality = qualityMap[x][y]; 303 bestPoints.clear(); 304 bestPoints.put(Coord.get(x, y), ap); 305 } 306 } 307 else if(qualityMap[x][y] == bestQuality) { 308 ArrayList<Coord> ap = new ArrayList<>(); 309 310 for (int i = 0; i < ts.length && i < 63; ++i) { 311 if ((bits & (1 << i)) != 0) 312 ap.add(ts[i]); 313 } 314 if (ap.size() > 0) { 315 bestPoints.put(Coord.get(x, y), ap); 316 } 317 } 318 319 } 320 } 321 322 return bestPoints; 323 } 324 325 @Override 326 public OrderedMap<Coord, ArrayList<Coord>> idealLocations(Collection<Coord> priorityTargets, Collection<Coord> lesserTargets, Collection<Coord> requiredExclusions) { 327 if(priorityTargets == null) 328 return idealLocations(lesserTargets, requiredExclusions); 329 if(requiredExclusions == null) requiredExclusions = new OrderedSet<>(); 330 331 //requiredExclusions.remove(origin); 332 int totalTargets = priorityTargets.size() + lesserTargets.size(); 333 OrderedMap<Coord, ArrayList<Coord>> bestPoints = new OrderedMap<>(totalTargets * 8); 334 335 if(totalTargets == 0) 336 return bestPoints; 337 338 Coord[] pts = priorityTargets.toArray(new Coord[0]); 339 Coord[] lts = lesserTargets.toArray(new Coord[0]); 340 Coord[] exs = requiredExclusions.toArray(new Coord[0]); 341 Coord t; 342 343 double[][][] compositeMap = new double[totalTargets][dungeon.length][dungeon[0].length]; 344 double tAngle; //, tStartAngle, tEndAngle; 345 346 char[][] dungeonCopy = new char[dungeon.length][dungeon[0].length], 347 dungeonPriorities = new char[dungeon.length][dungeon[0].length]; 348 for (int i = 0; i < dungeon.length; i++) { 349 System.arraycopy(dungeon[i], 0, dungeonCopy[i], 0, dungeon[i].length); 350 Arrays.fill(dungeonPriorities[i], '#'); 351 } 352 double[][] tmpfov; 353 Coord tempPt; 354 for (int i = 0; i < exs.length; ++i) { 355 t = exs[i]; 356 357 358 tAngle = NumberTools.atan2_(t.y - origin.y, t.x - origin.x) * 360.0; 359 // tStartAngle = Math.abs((tAngle - span / 2.0) % 360.0); 360// tEndAngle = Math.abs((tAngle + span / 2.0) % 360.0); 361 tmpfov = fov.calculateFOV(map, origin.x, origin.y, radius, radiusType, tAngle, span); 362 for (int x = 0; x < dungeon.length; x++) { 363 for (int y = 0; y < dungeon[x].length; y++) { 364 tempPt = Coord.get(x, y); 365 dungeonCopy[x][y] = (tmpfov[x][y] > 0.0 || !AreaUtils.verifyLimit(reach.limit, origin, tempPt)) ? '!' : dungeonCopy[x][y]; 366 } 367 } 368 } 369 370 Measurement dmm = Measurement.MANHATTAN; 371 if(radiusType == Radius.SQUARE || radiusType == Radius.CUBE) dmm = Measurement.CHEBYSHEV; 372 else if(radiusType == Radius.CIRCLE || radiusType == Radius.SPHERE) dmm = Measurement.EUCLIDEAN; 373 374 for (int i = 0; i < pts.length; ++i) { 375 DijkstraMap dm = new DijkstraMap(dungeon, dmm); 376 t = pts[i]; 377 tAngle = NumberTools.atan2_(t.y - origin.y, t.x - origin.x) * 360.0; 378// tStartAngle = Math.abs((tAngle - span / 2.0) % 360.0); 379// tEndAngle = Math.abs((tAngle + span / 2.0) % 360.0); 380 381 tmpfov = fov.calculateFOV(map, origin.x, origin.y, radius, radiusType, tAngle, span); 382 383 for (int x = 0; x < dungeon.length; x++) { 384 for (int y = 0; y < dungeon[x].length; y++) { 385 if (tmpfov[x][y] > 0.0){ 386 compositeMap[i][x][y] = dm.physicalMap[x][y]; 387 dungeonPriorities[x][y] = dungeon[x][y]; 388 } 389 else compositeMap[i][x][y] = DijkstraMap.WALL; 390 } 391 } 392 if(compositeMap[i][pts[i].x][pts[i].y] > DijkstraMap.FLOOR) 393 { 394 for (int x = 0; x < dungeon.length; x++) { 395 Arrays.fill(compositeMap[i][x], 399999.0); 396 } 397 continue; 398 } 399 400 401 402 dm.initialize(compositeMap[i]); 403 dm.setGoal(t); 404 dm.scan(null, null, true); 405 for (int x = 0; x < dungeon.length; x++) { 406 for (int y = 0; y < dungeon[x].length; y++) { 407 compositeMap[i][x][y] = (dm.gradientMap[x][y] < DijkstraMap.FLOOR && dungeonCopy[x][y] != '!') ? dm.gradientMap[x][y] : 399999.0; 408 } 409 } 410 } 411 412 for (int i = pts.length; i < totalTargets; ++i) { 413 DijkstraMap dm = new DijkstraMap(dungeon, dmm); 414 t = lts[i - pts.length]; 415 416 tAngle = NumberTools.atan2_(t.y - origin.y, t.x - origin.x) * 360.0; 417 // tStartAngle = Math.abs((tAngle - span / 2.0) % 360.0); 418// tEndAngle = Math.abs((tAngle + span / 2.0) % 360.0); 419 420 tmpfov = fov.calculateFOV(map, origin.x, origin.y, radius, radiusType, tAngle, span); 421 422 for (int x = 0; x < dungeon.length; x++) { 423 for (int y = 0; y < dungeon[x].length; y++) { 424 if (tmpfov[x][y] > 0.0){ 425 compositeMap[i][x][y] = dm.physicalMap[x][y]; 426 } 427 else compositeMap[i][x][y] = DijkstraMap.WALL; 428 } 429 } 430 if(compositeMap[i][lts[i - pts.length].x][lts[i - pts.length].y] > DijkstraMap.FLOOR) 431 { 432 for (int x = 0; x < dungeon.length; x++) 433 { 434 Arrays.fill(compositeMap[i][x], 99999.0); 435 } 436 continue; 437 } 438 439 440 dm.initialize(compositeMap[i]); 441 dm.setGoal(t); 442 dm.scan(null, null, true); 443 for (int x = 0; x < dungeon.length; x++) { 444 for (int y = 0; y < dungeon[x].length; y++) { 445 compositeMap[i][x][y] = (dm.gradientMap[x][y] < DijkstraMap.FLOOR && dungeonCopy[x][y] != '!' && dungeonPriorities[x][y] != '#') ? dm.gradientMap[x][y] : 99999.0; 446 } 447 } 448 } 449 double bestQuality = 99999 * lts.length + 399999 * pts.length; 450 double[][] qualityMap = new double[dungeon.length][dungeon[0].length]; 451 for (int x = 0; x < qualityMap.length; x++) { 452 for (int y = 0; y < qualityMap[x].length; y++) { 453 qualityMap[x][y] = 0.0; 454 long pbits = 0, lbits = 0; 455 for (int i = 0; i < pts.length; ++i) { 456 qualityMap[x][y] += compositeMap[i][x][y]; 457 if(compositeMap[i][x][y] < 399999.0 && i < 63) 458 pbits |= 1 << i; 459 } 460 for (int i = pts.length; i < totalTargets; ++i) { 461 qualityMap[x][y] += compositeMap[i][x][y]; 462 if(compositeMap[i][x][y] < 99999.0 && i < 63) 463 lbits |= 1 << i; 464 } 465 if(qualityMap[x][y] < bestQuality) 466 { 467 ArrayList<Coord> ap = new ArrayList<>(); 468 469 for (int i = 0; i < pts.length && i < 63; ++i) { 470 if((pbits & (1 << i)) != 0) 471 ap.add(pts[i]); 472 } 473 for (int i = pts.length; i < totalTargets && i < 63; ++i) { 474 if((lbits & (1 << i)) != 0) 475 ap.add(lts[i - pts.length]); 476 } 477 478 if(ap.size() > 0) { 479 bestQuality = qualityMap[x][y]; 480 bestPoints.clear(); 481 bestPoints.put(Coord.get(x, y), ap); 482 } 483 484 } 485 else if(qualityMap[x][y] == bestQuality) { 486 ArrayList<Coord> ap = new ArrayList<>(); 487 488 for (int i = 0; i < pts.length && i < 63; ++i) { 489 if ((pbits & (1 << i)) != 0) { 490 ap.add(pts[i]); 491 ap.add(pts[i]); 492 ap.add(pts[i]); 493 ap.add(pts[i]); 494 } 495 } 496 for (int i = pts.length; i < totalTargets && i < 63; ++i) { 497 if ((lbits & (1 << i)) != 0) 498 ap.add(lts[i - pts.length]); 499 } 500 if (ap.size() > 0) { 501 bestPoints.put(Coord.get(x, y), ap); 502 } 503 } 504 } 505 } 506 507 return bestPoints; 508 } 509 510 /* 511 @Override 512 public ArrayList<ArrayList<Coord>> idealLocations(Set<Coord> targets, Set<Coord> requiredExclusions) { 513 int totalTargets = targets.size() + 1; 514 int maxEffect = (int)(radiusType.volume2D(radius) * Math.max(5, span) / 360.0); 515 double allowed = Math.toRadians(span / 2.0); 516 ArrayList<ArrayList<Coord>> locs = new ArrayList<ArrayList<Coord>>(totalTargets); 517 518 for(int i = 0; i < totalTargets; i++) 519 { 520 locs.add(new ArrayList<Coord>(maxEffect)); 521 } 522 if(totalTargets == 1) 523 return locs; 524 525 int ctr = 0; 526 if(radius < 1) 527 { 528 locs.get(totalTargets - 2).addAll(targets); 529 return locs; 530 } 531 532 double tmpAngle, ang; 533 boolean[][] tested = new boolean[dungeon.length][dungeon[0].length]; 534 for (int x = 1; x < dungeon.length - 1; x += radius) { 535 BY_POINT: 536 for (int y = 1; y < dungeon[x].length - 1; y += radius) { 537 ang = Math.atan2(y - origin.y, x - origin.x); // between -pi and pi 538 539 for(Coord ex : requiredExclusions) { 540 if (radiusType.radius(x, y, ex.x, ex.y) <= radius) { 541 tmpAngle = Math.abs(ang - Math.atan2(ex.y - origin.y, ex.x - origin.x)); 542 if(tmpAngle > Math.PI) tmpAngle = PI2 - tmpAngle; 543 if(tmpAngle < allowed) 544 continue BY_POINT; 545 } 546 } 547 ctr = 0; 548 for(Coord tgt : targets) { 549 if (radiusType.radius(x, y, tgt.x, tgt.y) <= radius) { 550 tmpAngle = Math.abs(ang - Math.atan2(tgt.y - origin.y, tgt.x - origin.x)); 551 if(tmpAngle > Math.PI) tmpAngle = PI2 - tmpAngle; 552 if(tmpAngle < allowed) 553 ctr++; 554 } 555 } 556 if(ctr > 0) 557 locs.get(totalTargets - ctr).add(Coord.get(x, y)); 558 } 559 } 560 Coord it; 561 for(int t = 0; t < totalTargets - 1; t++) 562 { 563 if(locs.get(t).size() > 0) { 564 int numPoints = locs.get(t).size(); 565 for (int i = 0; i < numPoints; i++) { 566 it = locs.get(t).get(i); 567 for (int x = Math.max(1, it.x - (int)(radius) / 2); x < it.x + (radius + 1) / 2 && x < dungeon.length - 1; x++) { 568 BY_POINT: 569 for (int y = Math.max(1, it.y - (int)(radius) / 2); y <= it.y + (radius - 1) / 2 && y < dungeon[0].length - 1; y++) 570 { 571 if(tested[x][y]) 572 continue; 573 tested[x][y] = true; 574 ang = Math.atan2(y - origin.y, x - origin.x); // between -pi and pi 575 for(Coord ex : requiredExclusions) { 576 if (radiusType.radius(x, y, ex.x, ex.y) <= radius) { 577 tmpAngle = Math.abs(ang - Math.atan2(ex.y - origin.y, ex.x - origin.x)); 578 if(tmpAngle > Math.PI) tmpAngle = PI2 - tmpAngle; 579 if(tmpAngle < allowed) 580 continue BY_POINT; 581 } 582 } 583 584 ctr = 0; 585 for(Coord tgt : targets) { 586 if (radiusType.radius(x, y, tgt.x, tgt.y) <= radius) { 587 tmpAngle = Math.abs(ang - Math.atan2(tgt.y - origin.y, tgt.x - origin.x)); 588 if(tmpAngle > Math.PI) tmpAngle = PI2 - tmpAngle; 589 if(tmpAngle < allowed) 590 ctr++; 591 } 592 } 593 if(ctr > 0) 594 locs.get(totalTargets - ctr).add(Coord.get(x, y)); 595 } 596 } 597 } 598 } 599 } 600 return locs; 601 } 602*/ 603 @Override 604 public void setMap(char[][] map) { 605 this.map = DungeonUtility.generateResistances(map); 606 dungeon = map; 607 } 608 609 @Override 610 public OrderedMap<Coord, Double> findArea() { 611 OrderedMap<Coord, Double> r = AreaUtils.arrayToHashMap(fov.calculateFOV(map, origin.x, origin.y, radius, 612 radiusType, angle, span)); 613 r.remove(origin); 614 return r; 615 } 616 617}