001package squidpony.squidai;
002
003import squidpony.annotation.GwtIncompatible;
004import squidpony.squidgrid.FOVCache;
005import squidpony.squidgrid.Radius;
006import squidpony.squidgrid.Spill;
007import squidpony.squidmath.Coord;
008import squidpony.squidmath.LightRNG;
009import squidpony.squidmath.RNG;
010
011import java.util.*;
012
013/**
014 * An AOE type that has a center and a volume, and will randomly expand in all directions until it reaches volume or
015 * cannot expand further. Specify the RadiusType as Radius.DIAMOND for Manhattan distance (and the best results),
016 * RADIUS.SQUARE for Chebyshev, or RADIUS.CIRCLE for Euclidean. You can specify a seed for the RNG and a fresh RNG will
017 * be used for all random expansion; the RNG will reset to the specified seed after each generation so the same
018 * CloudAOE can be used in different places by just changing the center. You can cause the CloudAOE to not reset after
019 * generating each time by using setExpanding(true) and cause it to reset after the next generation by setting it back
020 * to the default of false. If expanding is true, then multiple calls to findArea with the same center and larger
021 * volumes will produce more solid clumps of affected area with fewer gaps, and can be spaced out over multiple calls.
022 *
023 * This will produce doubles for its findArea() method which are equal to 1.0.
024 *
025 * This class uses squidpony.squidgrid.Spill to create its area of effect.
026 * Created by Tommy Ettinger on 7/13/2015.
027 */
028public class CloudAOE implements AOE {
029    private Spill spill;
030    private Coord center, origin = null;
031    private int volume;
032    private long seed;
033    private boolean expanding;
034    private Radius rt;
035    private Reach reach = new Reach(1, 1, Radius.SQUARE, null);
036    private char[][] dungeon;
037
038    public CloudAOE(Coord center, int volume, Radius radiusType)
039    {
040        LightRNG l = new LightRNG();
041        seed = l.getState();
042        spill = new Spill(new RNG(l));
043        this.center = center;
044        this.volume = volume;
045        expanding = false;
046        rt = radiusType;
047        switch (radiusType)
048        {
049            case SPHERE:
050            case CIRCLE:
051                spill.measurement = Spill.Measurement.EUCLIDEAN;
052                break;
053            case CUBE:
054            case SQUARE:
055                spill.measurement = Spill.Measurement.CHEBYSHEV;
056                break;
057            default:
058                spill.measurement = Spill.Measurement.MANHATTAN;
059                break;
060        }
061    }
062
063    public CloudAOE(Coord center, int volume, Radius radiusType, int minRange, int maxRange)
064    {
065        LightRNG l = new LightRNG();
066        seed = l.getState();
067        spill = new Spill(new RNG(l));
068        this.center = center;
069        this.volume = volume;
070        expanding = false;
071        rt = radiusType;
072        reach.minDistance = minRange;
073        reach.maxDistance = maxRange;
074        switch (radiusType)
075        {
076            case SPHERE:
077            case CIRCLE:
078                spill.measurement = Spill.Measurement.EUCLIDEAN;
079                break;
080            case CUBE:
081            case SQUARE:
082                spill.measurement = Spill.Measurement.CHEBYSHEV;
083                break;
084            default:
085                spill.measurement = Spill.Measurement.MANHATTAN;
086                break;
087        }
088    }
089    public CloudAOE(Coord center, int volume, Radius radiusType, long rngSeed)
090    {
091        seed = rngSeed;
092        spill = new Spill(new RNG(new LightRNG(rngSeed)));
093        this.center = center;
094        this.volume = volume;
095        expanding = false;
096        rt = radiusType;
097        switch (radiusType)
098        {
099            case SPHERE:
100            case CIRCLE:
101                spill.measurement = Spill.Measurement.EUCLIDEAN;
102                break;
103            case CUBE:
104            case SQUARE:
105                spill.measurement = Spill.Measurement.CHEBYSHEV;
106                break;
107            default:
108                spill.measurement = Spill.Measurement.MANHATTAN;
109                break;
110        }
111    }
112    public CloudAOE(Coord center, int volume, Radius radiusType, long rngSeed, int minRange, int maxRange)
113    {
114        seed = rngSeed;
115        spill = new Spill(new RNG(new LightRNG(rngSeed)));
116        this.center = center;
117        this.volume = volume;
118        expanding = false;
119        rt = radiusType;
120        switch (radiusType)
121        {
122            case SPHERE:
123            case CIRCLE:
124                spill.measurement = Spill.Measurement.EUCLIDEAN;
125                break;
126            case CUBE:
127            case SQUARE:
128                spill.measurement = Spill.Measurement.CHEBYSHEV;
129                break;
130            default:
131                spill.measurement = Spill.Measurement.MANHATTAN;
132                break;
133        }
134        reach.minDistance = minRange;
135        reach.maxDistance = maxRange;
136    }
137
138    public Coord getCenter() {
139        return center;
140    }
141
142    public void setCenter(Coord center) {
143
144        if (dungeon != null && center.isWithin(dungeon.length, dungeon[0].length) &&
145                AreaUtils.verifyReach(reach, origin, center))
146        {
147            this.center = center;
148        }
149    }
150
151    public int getVolume() {
152        return volume;
153    }
154
155    public void setVolume(int volume) {
156        this.volume = volume;
157    }
158
159    public Radius getRadiusType() {
160        return rt;
161    }
162
163    public void setRadiusType(Radius radiusType) {
164        rt = radiusType;
165        switch (radiusType)
166        {
167            case SPHERE:
168            case CIRCLE:
169                break;
170            case CUBE:
171            case SQUARE:
172                break;
173            default:
174                break;
175        }
176    }
177
178    @Override
179    public void shift(Coord aim) {
180        setCenter(aim);
181    }
182
183    @Override
184    public boolean mayContainTarget(Set<Coord> targets) {
185        for (Coord p : targets)
186        {
187            if(rt.radius(center.x, center.y, p.x, p.y) <= Math.sqrt(volume) * 0.75)
188                return true;
189        }
190        return false;
191    }
192
193    @Override
194    public LinkedHashMap<Coord, ArrayList<Coord>> idealLocations(Set<Coord> targets, Set<Coord> requiredExclusions) {
195        if(targets == null)
196            return new LinkedHashMap<>();
197        if(requiredExclusions == null) requiredExclusions = new LinkedHashSet<>();
198
199        //requiredExclusions.remove(origin);
200        int totalTargets = targets.size();
201        LinkedHashMap<Coord, ArrayList<Coord>> bestPoints = new LinkedHashMap<>(totalTargets * 8);
202
203        if(totalTargets == 0 || volume <= 0)
204            return bestPoints;
205
206        if(volume == 1)
207        {
208            for(Coord p : targets)
209            {
210                ArrayList<Coord> ap = new ArrayList<>();
211                ap.add(p);
212                bestPoints.put(p, ap);
213            }
214            return bestPoints;
215        }
216        Coord[] ts = targets.toArray(new Coord[targets.size()]);
217        Coord[] exs = requiredExclusions.toArray(new Coord[requiredExclusions.size()]);
218        Coord t = exs[0];
219
220        double[][][] compositeMap = new double[ts.length][dungeon.length][dungeon[0].length];
221
222        Spill sp;
223
224        char[][] dungeonCopy = new char[dungeon.length][dungeon[0].length];
225        for (int i = 0; i < dungeon.length; i++) {
226            System.arraycopy(dungeon[i], 0, dungeonCopy[i], 0, dungeon[i].length);
227        }
228
229        Coord tempPt;
230        for (int i = 0; i < exs.length; ++i) {
231            t = exs[i];
232            sp = new Spill(dungeon, spill.measurement);
233            sp.lrng.setState(seed);
234
235            sp.start(t, volume, null);
236            for (int x = 0; x < dungeon.length; x++) {
237                for (int y = 0; y < dungeon[x].length; y++) {
238                    tempPt = Coord.get(x, y);
239                    dungeonCopy[x][y] = (sp.spillMap[x][y] || !AreaUtils.verifyReach(reach, origin, tempPt)) ? '!' : dungeonCopy[x][y];
240                }
241            }
242        }
243
244        DijkstraMap.Measurement dmm = DijkstraMap.Measurement.MANHATTAN;
245        if(spill.measurement == Spill.Measurement.CHEBYSHEV) dmm = DijkstraMap.Measurement.CHEBYSHEV;
246        else if(spill.measurement == Spill.Measurement.EUCLIDEAN) dmm = DijkstraMap.Measurement.EUCLIDEAN;
247
248        double radius = Math.sqrt(volume) * 0.75;
249
250        for (int i = 0; i < ts.length; ++i) {
251            DijkstraMap dm = new DijkstraMap(dungeon, dmm);
252
253            t = ts[i];
254            sp = new Spill(dungeon, spill.measurement);
255            sp.lrng.setState(seed);
256
257            sp.start(t, volume, null);
258
259            double dist = 0.0;
260            for (int x = 0; x < dungeon.length; x++) {
261                for (int y = 0; y < dungeon[x].length; y++) {
262                    if (sp.spillMap[x][y]){
263                        dist = reach.metric.radius(origin.x, origin.y, x, y);
264                        if(dist <= reach.maxDistance + radius && dist >= reach.minDistance - radius)
265                            compositeMap[i][x][y] = dm.physicalMap[x][y];
266                        else
267                            compositeMap[i][x][y] = DijkstraMap.WALL;
268                    }
269                    else compositeMap[i][x][y] = DijkstraMap.WALL;
270                }
271            }
272            if(compositeMap[i][ts[i].x][ts[i].y] > DijkstraMap.FLOOR)
273            {
274                for (int x = 0; x < dungeon.length; x++) {
275                    Arrays.fill(compositeMap[i][x], 99999.0);
276                }
277                continue;
278            }
279
280            dm.initialize(compositeMap[i]);
281            dm.setGoal(t);
282            dm.scan(null);
283            for (int x = 0; x < dungeon.length; x++) {
284                for (int y = 0; y < dungeon[x].length; y++) {
285                    compositeMap[i][x][y] = (dm.gradientMap[x][y] < DijkstraMap.FLOOR  && dungeonCopy[x][y] != '!') ? dm.gradientMap[x][y] : 99999.0;
286                }
287            }
288            dm.resetMap();
289            dm.clearGoals();
290        }
291        double bestQuality = 99999 * ts.length;
292        double[][] qualityMap = new double[dungeon.length][dungeon[0].length];
293        for (int x = 0; x < qualityMap.length; x++) {
294            for (int y = 0; y < qualityMap[x].length; y++) {
295                qualityMap[x][y] = 0.0;
296                long bits = 0;
297                for (int i = 0; i < ts.length; ++i) {
298                    qualityMap[x][y] += compositeMap[i][x][y];
299                    if(compositeMap[i][x][y] < 99999.0 && i < 63)
300                        bits |= 1 << i;
301                }
302                if(qualityMap[x][y] < bestQuality)
303                {
304                    ArrayList<Coord> ap = new ArrayList<>();
305
306                    for (int i = 0; i < ts.length && i < 63; ++i) {
307                        if((bits & (1 << i)) != 0)
308                            ap.add(ts[i]);
309                    }
310
311                    if(ap.size() > 0) {
312                        bestQuality = qualityMap[x][y];
313                        bestPoints.clear();
314                        bestPoints.put(Coord.get(x, y), ap);
315                    }
316                }
317                else if(qualityMap[x][y] == bestQuality)
318                {
319                    ArrayList<Coord> ap = new ArrayList<>();
320
321                    for (int i = 0; i < ts.length && i < 63; ++i) {
322                        if((bits & (1 << i)) != 0)
323                            ap.add(ts[i]);
324                    }
325
326                    if (ap.size() > 0) {
327                        bestPoints.put(Coord.get(x, y), ap);
328                    }
329                }
330            }
331        }
332
333        return bestPoints;
334    }
335
336    @Override
337    public LinkedHashMap<Coord, ArrayList<Coord>> idealLocations(Set<Coord> priorityTargets, Set<Coord> lesserTargets, Set<Coord> requiredExclusions) {
338        if(priorityTargets == null)
339            return idealLocations(lesserTargets, requiredExclusions);
340        if(requiredExclusions == null) requiredExclusions = new LinkedHashSet<>();
341
342        //requiredExclusions.remove(origin);
343
344        int totalTargets = priorityTargets.size() + lesserTargets.size();
345        LinkedHashMap<Coord, ArrayList<Coord>> bestPoints = new LinkedHashMap<>(totalTargets * 8);
346
347        if(totalTargets == 0 || volume <= 0)
348            return bestPoints;
349
350        if(volume == 1)
351        {
352            for(Coord p : priorityTargets)
353            {
354                ArrayList<Coord> ap = new ArrayList<>();
355                ap.add(p);
356                bestPoints.put(p, ap);
357            }
358            return bestPoints;
359        }
360        Coord[] pts = priorityTargets.toArray(new Coord[priorityTargets.size()]);
361        Coord[] lts = lesserTargets.toArray(new Coord[lesserTargets.size()]);
362        Coord[] exs = requiredExclusions.toArray(new Coord[requiredExclusions.size()]);
363        Coord t = exs[0];
364
365        double[][][] compositeMap = new double[totalTargets][dungeon.length][dungeon[0].length];
366        Spill sp;
367
368        char[][] dungeonCopy = new char[dungeon.length][dungeon[0].length],
369                dungeonPriorities = new char[dungeon.length][dungeon[0].length];
370        for (int i = 0; i < dungeon.length; i++) {
371            System.arraycopy(dungeon[i], 0, dungeonCopy[i], 0, dungeon[i].length);
372            Arrays.fill(dungeonPriorities[i], '#');
373        }
374        Coord tempPt = Coord.get(0, 0);
375        for (int i = 0; i < exs.length; ++i) {
376            t = exs[i];
377            sp = new Spill(dungeon, spill.measurement);
378            sp.lrng.setState(seed);
379
380            sp.start(t, volume, null);
381            for (int x = 0; x < dungeon.length; x++) {
382                for (int y = 0; y < dungeon[x].length; y++) {
383                    tempPt = Coord.get(x, y);
384                    dungeonCopy[x][y] = (sp.spillMap[x][y] || !AreaUtils.verifyReach(reach, origin, tempPt)) ? '!' : dungeonCopy[x][y];
385                }
386            }
387        }
388
389        t = pts[0];
390
391        DijkstraMap.Measurement dmm = DijkstraMap.Measurement.MANHATTAN;
392        if(spill.measurement == Spill.Measurement.CHEBYSHEV) dmm = DijkstraMap.Measurement.CHEBYSHEV;
393        else if(spill.measurement == Spill.Measurement.EUCLIDEAN) dmm = DijkstraMap.Measurement.EUCLIDEAN;
394
395        double radius = Math.sqrt(volume) * 0.75;
396
397        for (int i = 0; i < pts.length; ++i) {
398            DijkstraMap dm = new DijkstraMap(dungeon, dmm);
399
400            t = pts[i];
401            sp = new Spill(dungeon, spill.measurement);
402            sp.lrng.setState(seed);
403
404            sp.start(t, volume, null);
405
406
407
408            double dist = 0.0;
409            for (int x = 0; x < dungeon.length; x++) {
410                for (int y = 0; y < dungeon[x].length; y++) {
411                    if (sp.spillMap[x][y]){
412                        dist = reach.metric.radius(origin.x, origin.y, x, y);
413                        if(dist <= reach.maxDistance + radius && dist >= reach.minDistance - radius) {
414                            compositeMap[i][x][y] = dm.physicalMap[x][y];
415                            dungeonPriorities[x][y] = dungeon[x][y];
416                        }
417                        else
418                            compositeMap[i][x][y] = DijkstraMap.WALL;
419                    }
420                    else compositeMap[i][x][y] = DijkstraMap.WALL;
421                }
422            }
423            if(compositeMap[i][pts[i].x][pts[i].y] > DijkstraMap.FLOOR)
424            {
425                for (int x = 0; x < dungeon.length; x++) {
426                    Arrays.fill(compositeMap[i][x], 399999.0);
427                }
428                continue;
429            }
430
431
432            dm.initialize(compositeMap[i]);
433            dm.setGoal(t);
434            dm.scan(null);
435            for (int x = 0; x < dungeon.length; x++) {
436                for (int y = 0; y < dungeon[x].length; y++) {
437                    compositeMap[i][x][y] = (dm.gradientMap[x][y] < DijkstraMap.FLOOR  && dungeonCopy[x][y] != '!') ? dm.gradientMap[x][y] : 399999.0;
438                }
439            }
440            dm.resetMap();
441            dm.clearGoals();
442        }
443
444        t = lts[0];
445
446        for (int i = pts.length; i < totalTargets; ++i) {
447            DijkstraMap dm = new DijkstraMap(dungeon, dmm);
448
449            t = lts[i - pts.length];
450            sp = new Spill(dungeon, spill.measurement);
451            sp.lrng.setState(seed);
452
453            sp.start(t, volume, null);
454
455
456            double dist = 0.0;
457            for (int x = 0; x < dungeon.length; x++) {
458                for (int y = 0; y < dungeon[x].length; y++) {
459                    if (sp.spillMap[x][y]){
460                        dist = reach.metric.radius(origin.x, origin.y, x, y);
461                        if(dist <= reach.maxDistance + radius && dist >= reach.minDistance - radius)
462                            compositeMap[i][x][y] = dm.physicalMap[x][y];
463                        else
464                            compositeMap[i][x][y] = DijkstraMap.WALL;
465                    }
466                    else compositeMap[i][x][y] = DijkstraMap.WALL;
467                }
468            }
469            if(compositeMap[i][lts[i - pts.length].x][lts[i - pts.length].y] > DijkstraMap.FLOOR)
470            {
471                for (int x = 0; x < dungeon.length; x++)
472                {
473                    Arrays.fill(compositeMap[i][x], 99999.0);
474                }
475                continue;
476            }
477
478
479            dm.initialize(compositeMap[i]);
480            dm.setGoal(t);
481            dm.scan(null);
482            for (int x = 0; x < dungeon.length; x++) {
483                for (int y = 0; y < dungeon[x].length; y++) {
484                    compositeMap[i][x][y] = (dm.gradientMap[x][y] < DijkstraMap.FLOOR  && dungeonCopy[x][y] != '!' && dungeonPriorities[x][y] != '#') ? dm.gradientMap[x][y] : 99999.0;
485                }
486            }
487            dm.resetMap();
488            dm.clearGoals();
489        }
490        double bestQuality = 99999 * lts.length + 399999 * pts.length;
491        double[][] qualityMap = new double[dungeon.length][dungeon[0].length];
492        for (int x = 0; x < qualityMap.length; x++) {
493            for (int y = 0; y < qualityMap[x].length; y++) {
494                qualityMap[x][y] = 0.0;
495                long pbits = 0, lbits = 0;
496                for (int i = 0; i < pts.length; ++i) {
497                    qualityMap[x][y] += compositeMap[i][x][y];
498                    if(compositeMap[i][x][y] < 399999.0 && i < 63)
499                        pbits |= 1 << i;
500                }
501                for (int i = pts.length; i < totalTargets; ++i) {
502                    qualityMap[x][y] += compositeMap[i][x][y];
503                    if(compositeMap[i][x][y] < 99999.0 && i < 63)
504                        lbits |= 1 << i;
505                }
506                if(qualityMap[x][y] < bestQuality)
507                {
508                    ArrayList<Coord> ap = new ArrayList<>();
509
510                    for (int i = 0; i < pts.length && i < 63; ++i) {
511                        if((pbits & (1 << i)) != 0)
512                            ap.add(pts[i]);
513                    }
514                    for (int i = pts.length; i < totalTargets && i < 63; ++i) {
515                        if((lbits & (1 << i)) != 0)
516                            ap.add(lts[i - pts.length]);
517                    }
518
519                    if(ap.size() > 0) {
520                        bestQuality = qualityMap[x][y];
521                        bestPoints.clear();
522                        bestPoints.put(Coord.get(x, y), ap);
523                    }
524                }
525                else if(qualityMap[x][y] == bestQuality)
526                {
527                    ArrayList<Coord> ap = new ArrayList<>();
528
529                    for (int i = 0; i < pts.length && i < 63; ++i) {
530                        if((pbits & (1 << i)) != 0)
531                            ap.add(pts[i]);
532                    }
533                    for (int i = pts.length; i < totalTargets && i < 63; ++i) {
534                        if ((pbits & (1 << i)) != 0) {
535                            ap.add(pts[i]);
536                            ap.add(pts[i]);
537                            ap.add(pts[i]);
538                            ap.add(pts[i]);
539                        }
540                    }
541
542                    if (ap.size() > 0) {
543                        bestPoints.put(Coord.get(x, y), ap);
544                    }
545                }
546            }
547        }
548
549        return bestPoints;
550    }
551
552
553/*
554    @Override
555    public ArrayList<ArrayList<Coord>> idealLocations(Set<Coord> targets, Set<Coord> requiredExclusions) {
556        int totalTargets = targets.size() + 1;
557        int radius = Math.max(1, (int) (Math.sqrt(volume) * 1.5));
558        ArrayList<ArrayList<Coord>> locs = new ArrayList<ArrayList<Coord>>(totalTargets);
559
560        for(int i = 0; i < totalTargets; i++)
561        {
562            locs.add(new ArrayList<Coord>(volume));
563        }
564        if(totalTargets == 1)
565            return locs;
566        double ctr = 0;
567        if(radius < 1)
568        {
569            locs.get(totalTargets - 2).addAll(targets);
570            return locs;
571        }
572        double tempRad;
573        boolean[][] tested = new boolean[dungeon.length][dungeon[0].length];
574        for (int x = 1; x < dungeon.length - 1; x += radius) {
575            BY_POINT:
576            for (int y = 1; y < dungeon[x].length - 1; y += radius) {
577                for(Coord ex : requiredExclusions)
578                {
579                    if(rt.radius(x, y, ex.x, ex.y) <= radius * 0.75)
580                        continue BY_POINT;
581                }
582                ctr = 0;
583                for(Coord tgt : targets)
584                {
585                    tempRad = rt.radius(x, y, tgt.x, tgt.y);
586                    if(tempRad < radius)
587                        ctr += 1.0 - (tempRad / radius) * 0.5;
588                }
589                if(ctr >= 1)
590                    locs.get((int)(totalTargets - ctr)).add(Coord.get(x, y));
591            }
592        }
593        Coord it;
594        for(int t = 0; t < totalTargets - 1; t++)
595        {
596            if(locs.get(t).size() > 0) {
597                int numPoints = locs.get(t).size();
598                for (int i = 0; i < numPoints; i++) {
599                    it = locs.get(t).get(i);
600                    for (int x = Math.max(1, it.x - radius / 2); x < it.x + (radius + 1) / 2 && x < dungeon.length - 1; x++) {
601                        BY_POINT:
602                        for (int y = Math.max(1, it.y - radius / 2); y <= it.y + (radius - 1) / 2 && y < dungeon[0].length - 1; y++)
603                        {
604                            if(tested[x][y])
605                                continue;
606                            tested[x][y] = true;
607
608                            for(Coord ex : requiredExclusions)
609                            {
610                                if(rt.radius(x, y, ex.x, ex.y) <= radius * 0.75)
611                                    continue BY_POINT;
612                            }
613
614                            ctr = 0;
615                            for(Coord tgt : targets)
616                            {
617                                tempRad = rt.radius(x, y, tgt.x, tgt.y);
618                                if(tempRad < radius)
619                                    ctr += 1.0 - (tempRad / radius) * 0.5;
620                            }
621                            if(ctr >= 1)
622                                locs.get((int)(totalTargets - ctr)).add(Coord.get(x, y));
623                        }
624                    }
625                }
626            }
627        }
628        return locs;
629    }
630*/
631
632    @Override
633    public void setMap(char[][] map) {
634        spill.initialize(map);
635        dungeon = map;
636    }
637
638    @Override
639    public LinkedHashMap<Coord, Double> findArea() {
640        spill.start(center, volume, null);
641        LinkedHashMap<Coord, Double> r = AreaUtils.arrayToHashMap(spill.spillMap);
642        if(!expanding)
643        {
644            spill.reset();
645            spill.lrng.setState(seed);
646        }
647        return r;
648    }
649
650    @Override
651    public Coord getOrigin() {
652        return origin;
653    }
654
655    @Override
656    public void setOrigin(Coord origin) {
657        this.origin = origin;
658
659    }
660
661    @Override
662    public AimLimit getLimitType() {
663        return reach.limit;
664    }
665
666    @Override
667    public int getMinRange() {
668        return reach.minDistance;
669    }
670
671    @Override
672    public int getMaxRange() {
673        return reach.maxDistance;
674    }
675
676    @Override
677    public Radius getMetric() {
678        return reach.metric;
679    }
680
681    /**
682     * Gets the same values returned by getLimitType(), getMinRange(), getMaxRange(), and getMetric() bundled into one
683     * Reach object.
684     *
685     * @return a non-null Reach object.
686     */
687    @Override
688    public Reach getReach() {
689        return reach;
690    }
691
692    @Override
693    public void setLimitType(AimLimit limitType) {
694        reach.limit = limitType;
695
696    }
697
698    @Override
699    public void setMinRange(int minRange) {
700        reach.minDistance = minRange;
701    }
702
703    @Override
704    public void setMaxRange(int maxRange) {
705        reach.maxDistance = maxRange;
706
707    }
708
709    @Override
710    public void setMetric(Radius metric) {
711        reach.metric = metric;
712    }
713
714    /**
715     * Sets the same values as setLimitType(), setMinRange(), setMaxRange(), and setMetric() using one Reach object.
716     *
717     * @param reach a non-null Reach object.
718     */
719    @Override
720    public void setReach(Reach reach) {
721        if(reach != null)
722            this.reach = reach;
723    }
724
725    public boolean isExpanding() {
726        return expanding;
727    }
728
729    public void setExpanding(boolean expanding) {
730        this.expanding = expanding;
731    }
732
733    /**
734     * If you use FOVCache to pre-compute FOV maps for a level, you can share the speedup from using the cache with
735     * some AOE implementations that rely on FOV. Not all implementations need to actually make use of the cache, but
736     * those that use FOV for calculations should benefit. The cache parameter this receives should have completed its
737     * calculations, which can be confirmed by calling awaitCache(). Ideally, the FOVCache will have done its initial
738     * calculations in another thread while the previous level or menu was being displayed, and awaitCache() will only
739     * be a formality.
740     *
741     * @param cache The FOVCache for the current level; can be null to stop using the cache
742     */
743    @GwtIncompatible
744    @Override
745    public void setCache(FOVCache cache) {
746
747    }
748}