001package com.fs.starfarer.api.impl.campaign.fleets;
002
003import java.util.ArrayList;
004import java.util.Iterator;
005import java.util.LinkedHashMap;
006import java.util.List;
007import java.util.Map;
008import java.util.Random;
009
010import org.lwjgl.util.vector.Vector2f;
011
012import com.fs.starfarer.api.Global;
013import com.fs.starfarer.api.campaign.BattleAPI;
014import com.fs.starfarer.api.campaign.CampaignEventListener.FleetDespawnReason;
015import com.fs.starfarer.api.campaign.CampaignFleetAPI;
016import com.fs.starfarer.api.campaign.LocationAPI;
017import com.fs.starfarer.api.campaign.SectorEntityToken;
018import com.fs.starfarer.api.campaign.SectorEntityToken.VisibilityLevel;
019import com.fs.starfarer.api.campaign.StarSystemAPI;
020import com.fs.starfarer.api.campaign.econ.MarketAPI;
021import com.fs.starfarer.api.campaign.listeners.FleetEventListener;
022import com.fs.starfarer.api.util.Misc;
023
024public class RouteManager implements FleetEventListener {
025        public static final String KEY = "$core_routeManager";
026        
027        public static float IN_OUT_PHASE_DAYS = 3f;
028        public static float IN_OUT_PHASE_FRACTION = 0.2f;
029        
030//      public static float DAYS_SINCE_SEEN_BEFORE_DESPAWN = 60f;
031//      public static float DESPAWN_DIST_LY = 4.6f;
032//      public static float SPAWN_DIST_LY = 3.5f;
033        
034        public static float DAYS_SINCE_SEEN_BEFORE_DESPAWN_IF_FAR = 30f;
035        public static float DAYS_SINCE_SEEN_BEFORE_DESPAWN_IF_CLOSE = 60f;
036        public static float DESPAWN_DIST_LY_FAR = 4f;
037        public static float DESPAWN_DIST_LY_CLOSE = 3f;
038        public static float SPAWN_DIST_LY = 1.6f;
039        
040        
041        public static class OptionalFleetData {
042                public Float strength; // extremely approximate, in fleet points but modified by quality and doctrine
043                public Float quality;
044                public Float fp; // actual fleet points
045                public String factionId;
046                public String fleetType;
047                public Float damage; // 0 to 1, how much damage the fleet has sustained. spawner should use that info.
048                
049                public OptionalFleetData() {
050                }
051                public OptionalFleetData(MarketAPI market) {
052                        this(market, market.getFactionId());
053                }
054                public OptionalFleetData(MarketAPI market, String factionId) {
055                        quality = Misc.getShipQuality(market, factionId);
056                        this.factionId = factionId;
057                }
058                
059                public float getStrengthModifiedByDamage() {
060                        if (strength == null) return 0f;
061                        float str = strength;
062                        if (damage != null) {
063                                str *= Math.max(0, 1f - damage);
064                        }
065                        return str;
066                }
067        }
068        
069        public static class RouteSegment {
070                public Integer id; // generally ought to be unique within the route, but doesn't have to be
071                public float elapsed = 0f;
072                public float daysMax;
073                public SectorEntityToken from;
074                public SectorEntityToken to;
075                public Object custom;
076                
077                public RouteSegment(Integer id, float daysMax, SectorEntityToken from) {
078                        this(id, daysMax, from, null, null);
079                }
080                public RouteSegment(float daysMax, SectorEntityToken from) {
081                        this(null, daysMax, from, null, null);
082                }
083                public RouteSegment(Integer id, float daysMax, SectorEntityToken from, Object custom) {
084                        this(id, daysMax, from, null, custom);
085                }
086                public RouteSegment(float daysMax, SectorEntityToken from, Object custom) {
087                        this(null, daysMax, from, null, custom);
088                }
089                public RouteSegment(Integer id, float daysMax, SectorEntityToken from, SectorEntityToken to) {
090                        this(id, daysMax, from, to, null);
091                }
092                public RouteSegment(Integer id, SectorEntityToken from, SectorEntityToken to) {
093                        this(id, 0f, from, to, null);
094                        if (from.getContainingLocation() != to.getContainingLocation()) {
095                                float dist = Misc.getDistance(from.getLocationInHyperspace(), to.getLocationInHyperspace());
096                                daysMax = dist / 1500f + IN_OUT_PHASE_DAYS * 2f; 
097                        } else {
098                                float dist = Misc.getDistance(from.getLocation(), to.getLocation());
099                                daysMax = dist / 1500f;
100                        }
101                }
102                public RouteSegment(float daysMax, SectorEntityToken from, SectorEntityToken to) {
103                        this(null, daysMax, from, to, null);
104                }
105                public RouteSegment(Integer id, float daysMax, SectorEntityToken from, SectorEntityToken to, Object custom) {
106                        this.id = id;
107                        this.daysMax = daysMax;
108                        this.from = from;
109                        this.to = to;
110                        this.custom = custom;
111                }
112                
113                public LocationAPI getCurrentContainingLocation() {
114                        LocationAPI loc = null;
115                        if (from != null && to == null) {
116                                loc = from.getContainingLocation();
117                        } else if (from == null && to != null) {
118                                loc = to.getContainingLocation();
119                        } else {
120                                if (from.getContainingLocation() == to.getContainingLocation()) {
121                                        loc = from.getContainingLocation();
122                                } else {
123                                        if (getLeaveProgress() < 1) {
124                                                loc = from.getContainingLocation();
125                                        } else if (getTransitProgress() < 1) {
126                                                loc = Global.getSector().getHyperspace();
127                                        } else {
128                                                loc = to.getContainingLocation();
129                                        }
130                                }
131                        }
132                        return loc;
133                }
134                
135                public LocationAPI getContainingLocationFrom() {
136                        if (from == null) return null;
137                        return from.getContainingLocation();
138
139                }
140                public LocationAPI getContainingLocationTo() {
141                        if (to == null) return null;
142                        return to.getContainingLocation();
143                }
144                
145                public boolean isTravel() {
146                        return to != null;
147                }
148                
149                public boolean isInSystem() {
150                        if (to == null && from != null && from.getContainingLocation() != null && !from.getContainingLocation().isHyperspace()) return true;
151                        
152                        if (from != null && !from.getContainingLocation().isHyperspace() && 
153                                        from.getContainingLocation() == to.getContainingLocation()) {
154                                return true;
155                        }
156                        return false;
157                }
158
159                public boolean isFromSystemCenter() {
160                        return from != null && from.getContainingLocation() instanceof StarSystemAPI &&
161                                                ((StarSystemAPI)from.getContainingLocation()).getCenter() == from;
162                }
163                
164                public boolean isToSystemCenter() {
165                        return to != null && to.getContainingLocation() instanceof StarSystemAPI &&
166                                                ((StarSystemAPI)to.getContainingLocation()).getCenter() == to;
167                }
168                
169                public boolean hasLeaveSystemPhase() {
170                        if (isInSystem()) return false;
171                        if (isFromSystemCenter()) return false;
172                        if (from != null && from.getContainingLocation().isHyperspace()) return false;
173                        return true;
174                }
175                public boolean hasEnterSystemPhase() {
176                        if (isInSystem()) return false;
177                        if (isToSystemCenter()) return false;
178                        if (to != null && to.getContainingLocation().isHyperspace()) return false;
179                        return true;
180                }
181                
182                public float getProgress() {
183                        if (daysMax <= 0f) return 1f;
184                        return elapsed / daysMax;
185                }
186                
187                public float getEnterProgress() {
188                        float dur = Math.min(daysMax * IN_OUT_PHASE_FRACTION, IN_OUT_PHASE_DAYS);
189                        float f = 1f - Math.max(0, daysMax - elapsed) / dur;
190                        if (f > 1) f = 1;
191                        if (f < 0) f = 0;
192                        return f;
193                }
194                public float getLeaveProgress() {
195                        float dur = Math.min(daysMax * IN_OUT_PHASE_FRACTION, IN_OUT_PHASE_DAYS);
196                        float f = elapsed / dur;
197                        if (f > 1) f = 1;
198                        if (f < 0) f = 0;
199                        return f;
200                }
201                public float getTransitProgress() {
202                        float dur = Math.min(daysMax * IN_OUT_PHASE_FRACTION, IN_OUT_PHASE_DAYS);
203                        float max = daysMax;
204                        float e = elapsed;
205                        if (hasEnterSystemPhase()) {
206                                max -= dur;
207                        }
208                        if (hasLeaveSystemPhase()) {
209                                max -= dur;
210                                e -= dur;
211                        }
212                        float f = e / max;
213                        if (f > 1) f = 1;
214                        if (f < 0) f = 0;
215                        return f;
216                }
217                
218                public SectorEntityToken getDestination() {
219                        if (to != null) return to;
220                        return from;
221                }
222                
223                public SectorEntityToken getFrom() {
224                        return from;
225                }
226                public int getId() {
227                        if (id == null) return 0;
228                        return id;
229                }
230                
231                
232        }
233        
234        
235        public static interface RouteFleetSpawner {
236                CampaignFleetAPI spawnFleet(RouteData route);
237                boolean shouldCancelRouteAfterDelayCheck(RouteData route);
238                boolean shouldRepeat(RouteData route);
239                void reportAboutToBeDespawnedByRouteManager(RouteData route);
240        }
241        
242        public static class RouteData {
243                protected OptionalFleetData extra = null;
244                protected float delay = 0f;
245                protected String source;
246                protected MarketAPI market;
247                protected Long seed;
248                protected long timestamp;
249                protected List<RouteSegment> segments = new ArrayList<RouteSegment>();
250                protected CampaignFleetAPI activeFleet = null;
251                protected float daysSinceSeenByPlayer = 1000f;
252                protected float elapsed = 0f;
253                protected Object custom;
254                protected RouteSegment current;
255                protected RouteFleetSpawner spawner;
256                //protected Boolean suspended = null;
257                /**
258                 * "source" is a unique string ID for a given set of fleets. Useful to, for example,
259                 * limit the number of fleets of a particular type being spawned.
260                 * Use RouteManager.getNumRoutesFor(String source) to check number of routes with a given
261                 * source that already exist. 
262                 * @param source
263                 * @param market
264                 * @param seed
265                 */
266                public RouteData(String source, MarketAPI market, Long seed, OptionalFleetData extra) {
267                        this.source = source;
268                        this.market = market;
269                        this.seed = seed;
270                        this.extra = extra;
271                }
272                public OptionalFleetData getExtra() {
273                        return extra;
274                }
275                public void setExtra(OptionalFleetData extra) {
276                        this.extra = extra;
277                }
278                public MarketAPI getMarket() {
279                        return market;
280                }
281                public void goToAtLeastNext(RouteSegment from) {
282                        int index = segments.indexOf(current);
283                        int indexFrom = segments.indexOf(from);
284                        if (indexFrom < 0) return;
285                        if (indexFrom < index) return;
286                        
287                        if (indexFrom < segments.size() - 1) {
288                                current = segments.get(indexFrom + 1);
289                        } else {
290                                current.elapsed = current.daysMax;
291                        }
292                }
293                
294                public void expire() {
295                        if (!segments.isEmpty()) {
296                                current = segments.get(segments.size() - 1);
297                        }
298                        if (current != null) {
299                                current.elapsed = current.daysMax;
300                        }
301                }
302                
303//              public boolean isSuspended() {
304//                      return suspended != null && suspended == false;
305//              }
306//              /**
307//               * Set to true to prevent route points from changing the "current" assignment.
308//               * Useful when fleet is active and so route point advancement should be handled directly based on
309//               * what the fleet does.
310//               * @param suspended
311//               */
312//              public void setSuspended(boolean suspended) {
313//                      if (!suspended) {
314//                              this.suspended = null;
315//                      } else {
316//                              this.suspended = suspended;
317//                      }
318//              }
319                public Random getRandom(int level) {
320                        if (seed == null) return new Random();
321                        return Misc.getRandom(seed, level);
322                }
323                public String getFactionId() {
324                        if (extra == null || extra.factionId == null) {
325                                if (market != null) return market.getFactionId();
326                                return null;
327                        }
328                        return extra.factionId;
329                }
330                public Float getQualityOverride() {
331                        if (extra == null) return null;
332                        return extra.quality;
333                }
334                public long getTimestamp() {
335                        return timestamp;
336                }
337                public void setTimestamp(long timestamp) {
338                        this.timestamp = timestamp;
339                }
340                public Random getRandom() {
341                        Random random = new Random();
342                        if (getSeed() != null) {
343                                random = new Random(getSeed());
344                        }
345                        return random;
346                }
347                
348                public Long getSeed() {
349                        return seed;
350                }
351                public RouteSegment addSegment(RouteSegment segment) {
352                        if (segments.isEmpty()) current = segment;
353                        segments.add(segment);
354                        return segment;
355                }
356                public List<RouteSegment> getSegments() {
357                        return segments;
358                }
359                public void setCurrent(RouteSegment current) {
360                        this.current = current;
361                }
362                public CampaignFleetAPI getActiveFleet() {
363                        return activeFleet;
364                }
365                public float getDaysSinceSeenByPlayer() {
366                        return daysSinceSeenByPlayer;
367                }
368                public float getElapsed() {
369                        return elapsed;
370                }
371                public Object getCustom() {
372                        return custom;
373                }
374                public RouteSegment getCurrent() {
375                        return current;
376                }
377                public Integer getCurrentSegmentId() {
378                        if (current == null) return 0;
379                        return current.getId();
380                }
381                public int getCurrentIndex() {
382                        return segments.indexOf(current);
383                }
384                public RouteFleetSpawner getSpawner() {
385                        return spawner;
386                }
387                public String getSource() {
388                        return source;
389                }
390                
391                public Vector2f getInterpolatedHyperLocation() {
392                        //if (true) return new Vector2f();
393                        if (current == null) return new Vector2f(100000000f, 0);
394                        
395                        if (current.isInSystem() || current.getContainingLocationTo() == null || current.to == null) {
396                                return current.from.getLocationInHyperspace();
397                        }
398                        
399                        //float p = current.elapsed / current.daysMax;
400                        float p = current.getTransitProgress();
401                        
402                        
403                        Vector2f from = current.getContainingLocationFrom().getLocation();
404                        if (current.getContainingLocationFrom().isHyperspace()) {
405                                from = current.getFrom().getLocation();
406                        }
407                        Vector2f to = current.getContainingLocationTo().getLocation();
408                        if (current.getContainingLocationTo().isHyperspace()) {
409                                to = current.to.getLocation();
410                        }
411                        
412//                      Vector2f interpLoc = Misc.interpolateVector(current.getContainingLocationFrom().getLocation(),
413//                                                                                                              current.getContainingLocationTo().getLocation(),
414//                                                                                                              p);
415                        Vector2f interpLoc = Misc.interpolateVector(from, to, p);
416                        return interpLoc;
417                }
418                
419                
420                public boolean isExpired() {
421                        if (segments.indexOf(current) == segments.size() - 1 && current.elapsed >= current.daysMax) {
422                                return true;
423                        }
424                        return false;
425                }
426                public void setCustom(Object custom) {
427                        this.custom = custom;
428                }
429                public float getDelay() {
430                        return delay;
431                }
432                public void setDelay(float delay) {
433                        this.delay = delay;
434                }
435                
436        }
437        
438        public static RouteManager getInstance() {
439                Object test = Global.getSector().getMemoryWithoutUpdate().get(KEY);
440                if (test instanceof RouteManager) {
441                        return (RouteManager) test; 
442                }
443                
444                RouteManager manager = new RouteManager();
445                Global.getSector().getMemoryWithoutUpdate().set(KEY, manager);
446                return manager;
447        }
448        
449        protected List<RouteData> routes = new ArrayList<RouteData>();
450        protected transient Map<String, List<RouteData>> sourceToRoute = new LinkedHashMap<String, List<RouteData>>();
451        
452        
453        Object readResolve() {
454                sourceToRoute = new LinkedHashMap<String, List<RouteData>>();
455                for (RouteData route : routes) {
456                        addToMap(route);
457                }
458                return this;
459        }
460        protected transient Map<LocationAPI, List<RouteData>> routesByLocation = null;
461        
462        public List<RouteData> getRoutesInLocation(LocationAPI location) {
463                if (routesByLocation == null) {
464                        routesByLocation = new LinkedHashMap<LocationAPI, List<RouteData>>();
465                        for (RouteData route : routes) {
466//                              if (route.getSource().startsWith("FGI")) {
467//                                      System.out.println("fewefwefw");
468//                              }
469                                RouteSegment current = route.current;
470                                if (current == null) continue;
471                                
472                                LocationAPI loc = null;
473                                if (current.from != null && current.to == null) {
474                                        loc = current.from.getContainingLocation();
475                                } else if (current.from == null && current.to != null) {
476                                        loc = current.to.getContainingLocation();
477                                } else {
478                                        if (current.from.getContainingLocation() == current.to.getContainingLocation()) {
479                                                loc = current.from.getContainingLocation();
480                                        } else {
481                                                if (current.getLeaveProgress() < 1) {
482                                                        loc = current.from.getContainingLocation();
483                                                } else if (current.getTransitProgress() < 1) {
484                                                        loc = Global.getSector().getHyperspace();
485                                                } else {
486                                                        loc = current.to.getContainingLocation();
487                                                }
488                                        }
489                                }
490                                
491                                if (loc != null) {
492                                        List<RouteData> list = routesByLocation.get(loc);
493                                        if (list == null) {
494                                                list = new ArrayList<RouteData>();
495                                                routesByLocation.put(loc, list);
496                                        }
497                                        list.add(route);
498                                }
499                        }
500                }
501                
502                List<RouteData> list = routesByLocation.get(location);
503                if (list == null) list = new ArrayList<RouteData>();
504                return list;
505        }
506        
507        
508        public RouteData addRoute(String source, MarketAPI market, Long seed, OptionalFleetData extra, RouteFleetSpawner spawner) {
509                return addRoute(source, market, seed, extra, spawner, null);
510        }
511        
512        @Deprecated public void removeRote(RouteData route) {
513                removeRoute(route);
514        }
515        public void removeRoute(RouteData route) {
516                routes.remove(route);
517                removeFromMap(route);
518        }
519        
520        public RouteData addRoute(String source, MarketAPI market, Long seed, OptionalFleetData extra, RouteFleetSpawner spawner, Object custom) {
521                routesByLocation = null;
522                
523                RouteData route = new RouteData(source, market, seed, extra);
524                route.spawner = spawner;
525                route.custom = custom;
526                route.timestamp = Global.getSector().getClock().getTimestamp();
527                routes.add(route);
528                addToMap(route);
529                //routes.clear();
530                return route;
531        }
532        
533        public int getNumRoutesFor(String source) {
534                if (!sourceToRoute.containsKey(source)) return 0;
535                return sourceToRoute.get(source).size();
536        }
537        
538        public void addToMap(RouteData route) {
539                if (route.source == null) return;
540                
541                List<RouteData> forSource = getRoutesForSource(route.source);
542                forSource.add(route);
543        }
544        
545        public void removeFromMap(RouteData route) {
546                routesByLocation = null;
547                
548                if (route.source == null) return;
549                
550                List<RouteData> forSource = getRoutesForSource(route.source);
551                forSource.remove(route);
552                if (forSource.isEmpty()) {
553                        sourceToRoute.remove(route.source);
554                }
555        }
556        
557        public List<RouteData> getRoutesForSource(String source) {
558                List<RouteData> forSource = sourceToRoute.get(source);
559                if (forSource == null) {
560                        forSource = new ArrayList<RouteData>();
561                        sourceToRoute.put(source, forSource);
562                }
563                return forSource;
564        }
565        
566        public RouteData getRoute(String source, CampaignFleetAPI fleet) {
567                List<RouteData> forSource = sourceToRoute.get(source);
568                if (forSource == null) {
569                        forSource = new ArrayList<RouteData>();
570                        sourceToRoute.put(source, forSource);
571                }
572                for (RouteData data : forSource) {
573                        if (data.activeFleet == fleet) return data;
574                }
575                return null;
576        }
577        
578        
579        public void advance(float amount) {
580                float days = Global.getSector().getClock().convertToDays(amount);
581//              sourceToRoute.get("salvage_aegea")
582//              int empty = 0;
583//              int total = 0;
584//              for (RouteData curr : routes) {
585//                      total++;
586//                      //if (curr.activeFleet != null && curr.activeFleet.getFleetData().getMembersListCopy().isEmpty()) {
587//                      if (curr.activeFleet != null && !curr.activeFleet.isAlive()) {
588//                      //if (curr.activeFleet != null) {
589//                              empty++;
590//                      }
591//              }
592//              System.out.println("Empty: " + empty + ", total: " + total);
593//              System.out.println("Routes: " + routes.size());
594                
595                routesByLocation = null;
596                
597                advanceRoutes(days);
598                spawnAndDespawn();
599        }
600        
601        protected void spawnAndDespawn() {
602                CampaignFleetAPI player = Global.getSector().getPlayerFleet();
603                if (player == null) return;
604                
605                //System.out.println("Num routes: " + routes.size());
606                int add = 0;
607                int sub = 0;
608                for (RouteData data : new ArrayList<RouteData>(routes)) {
609                        if (data.activeFleet != null && data.activeFleet.getContainingLocation() == player.getContainingLocation()) {
610                                VisibilityLevel level = data.activeFleet.getVisibilityLevelToPlayerFleet();
611                                if ((level == VisibilityLevel.COMPOSITION_AND_FACTION_DETAILS ||
612                                                level == VisibilityLevel.COMPOSITION_DETAILS) && 
613                                                data.activeFleet.wasMousedOverByPlayer()) {
614                                        data.daysSinceSeenByPlayer = 0f;
615                                }
616                        }
617                        
618//                      if (shouldDespawn(data)) {
619//                              System.out.println("wfwefwe");
620//                      }
621                        if (shouldDespawn(data)) {
622                                //shouldDespawn(data);
623                                data.spawner.reportAboutToBeDespawnedByRouteManager(data);
624                                data.activeFleet.despawn(FleetDespawnReason.PLAYER_FAR_AWAY, null);
625                                if (data.activeFleet.getContainingLocation() != null) {
626                                        data.activeFleet.getContainingLocation().removeEntity(data.activeFleet);
627                                }
628                                data.activeFleet = null;
629                                sub++;
630                                
631                                //System.out.println("Despawn index: " + routes.indexOf(data));
632                                continue;
633                        }
634                        
635//                      if (shouldSpawn(data)) {
636//                              System.out.println("wefwef23f23");
637//                      }
638                        if (shouldSpawn(data)) {
639                                //shouldSpawn(data);
640                                data.activeFleet = data.spawner.spawnFleet(data);
641                                if (data.activeFleet != null) {
642                                        data.activeFleet.addEventListener(this);
643                                        add++;
644                                } else {
645                                        data.expire();
646                                }
647                                
648//                              Vector2f interpLoc = data.getInterpolatedHyperLocation();
649//                              float distLY = Misc.getDistanceLY(interpLoc, player.getLocationInHyperspace());
650//                              float distLYActual = Misc.getDistanceLY(data.activeFleet.getLocation(), player.getLocationInHyperspace());
651//                              System.out.println("Dist: " + distLY + ", actual: " + distLYActual);
652                                //System.out.println("Spawn index: " + routes.indexOf(data));
653                                continue;
654                        }
655                        
656                }
657                
658//              System.out.println("Add: " + add + ", sub: " + sub);
659//              System.out.println("Total: " + Global.getSector().getHyperspace().getFleets().size());
660        }
661        
662        protected boolean shouldSpawn(RouteData data) {
663                if (data.delay > 0) return false;
664                if (data.activeFleet != null) return false;
665                //if (true) return true;
666                
667//              int index = data.getSegments().indexOf(data.getCurrent());
668//              if (index <= 0 || data.getCurrent().elapsed < 8f) return false;
669                //if (index <= 3 || data.getCurrent().elapsed < 1f) return false;
670//              if (index < 5) return false;
671                
672                Vector2f interpLoc = data.getInterpolatedHyperLocation();
673                
674                CampaignFleetAPI player = Global.getSector().getPlayerFleet();
675                float distLY = Misc.getDistanceLY(interpLoc, player.getLocationInHyperspace());
676                if (distLY < SPAWN_DIST_LY) return true;
677                
678                return false;
679        }
680        
681        public static boolean isPlayerInSpawnRange(SectorEntityToken from) {
682                float distLY = Misc.getDistanceLY(from.getLocationInHyperspace(), Global.getSector().getPlayerFleet().getLocationInHyperspace());
683                if (distLY < SPAWN_DIST_LY) return true;
684                return false;
685        }
686        
687        
688        protected boolean shouldDespawn(RouteData data) {
689                if (data.activeFleet == null) return false;
690                if (data.daysSinceSeenByPlayer < DAYS_SINCE_SEEN_BEFORE_DESPAWN_IF_FAR) return false;
691                if (data.activeFleet.getBattle() != null) return false;
692                if (data.activeFleet.isNoAutoDespawn()) return false;
693                
694                //if (true) return false;
695                
696                CampaignFleetAPI player = Global.getSector().getPlayerFleet();
697                float distLY = Misc.getDistanceLY(data.activeFleet.getLocationInHyperspace(), player.getLocationInHyperspace());
698                
699                if (distLY > DESPAWN_DIST_LY_FAR) return true;
700                if (distLY > DESPAWN_DIST_LY_CLOSE &&
701                                data.daysSinceSeenByPlayer > DAYS_SINCE_SEEN_BEFORE_DESPAWN_IF_CLOSE) return true;
702                
703                return false;
704        }
705        
706        protected void advanceRoutes(float days) {
707                
708//              int count = 0;
709//              for (RouteData curr : routes) {
710//                      if (curr.activeFleet != null) {
711//                              count++;
712//                      }
713//              }
714//              System.out.println("Active: " + count);
715                
716                Iterator<RouteData> iter = routes.iterator();
717                while (iter.hasNext()) {
718                        RouteData route = iter.next();
719                        //if (route.isSuspended()) continue;
720                        boolean delay = route.delay > 0;
721                        if (delay) {
722                                route.delay -= days;
723                                if (route.delay < 0) route.delay = 0;
724                                if (route.delay > 0) continue;
725                                
726                                if (route.spawner.shouldCancelRouteAfterDelayCheck(route)) {
727                                        iter.remove();
728                                        removeFromMap(route);
729                                        continue;
730                                }
731                                
732                                route.timestamp = Global.getSector().getClock().getTimestamp();
733                        }
734                        
735                        if (route.current == null && route.segments.isEmpty()) {
736                                iter.remove();
737                                removeFromMap(route);
738                                continue;
739                        }
740                        
741                        if (route.current == null) route.current = route.segments.get(0);
742                        
743                        route.current.elapsed += days;
744                        route.daysSinceSeenByPlayer += days;
745                        route.elapsed += days;
746                        
747                        if (route.getActiveFleet() != null) continue;
748                        
749                        //if (route.isSuspended()) continue;
750                        
751                        if (route.current.elapsed >= route.current.daysMax) {
752                                int index = route.segments.indexOf(route.current);
753                                if (index < route.segments.size() - 1) {
754                                        route.current = route.segments.get(index + 1);
755                                } else {
756                                        if (route.spawner.shouldRepeat(route)) {
757                                                route.current = null;
758                                                for (RouteSegment segment : route.segments) {
759                                                        segment.elapsed = 0f;
760                                                }
761                                        } else {
762                                                removeFromMap(route);
763                                                iter.remove();
764                                        }
765                                }
766                        }
767                }
768        }
769        
770        
771        
772        public void reportFleetDespawnedToListener(CampaignFleetAPI fleet, FleetDespawnReason reason, Object param) {
773                if (reason == FleetDespawnReason.PLAYER_FAR_AWAY) {
774                        return;
775                }
776                //((CampaignFleetAPI)fleet).getName()
777                boolean found = false;
778                for (RouteData curr : routes) {
779                        if (curr.activeFleet != null && curr.activeFleet == fleet) {
780                                if (reason == FleetDespawnReason.PLAYER_FAR_AWAY) {
781                                        //curr.setSuspended(false);
782                                } else {
783                                        routes.remove(curr);
784                                        removeFromMap(curr);
785                                        found = true;
786                                }
787                                break;
788                        }
789                }
790
791// this can happen when route expires while fleet is active
792//              if (!found) {
793//                      System.out.println("NOT FOUND FLEET BEING REMOVED");
794//              }
795
796        }
797        
798        public void reportBattleOccurred(CampaignFleetAPI fleet, CampaignFleetAPI primaryWinner, BattleAPI battle) {
799                
800        }
801
802}
803
804