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