001package com.fs.starfarer.api.impl.campaign.intel.group;
002
003import java.util.ArrayList;
004import java.util.LinkedHashSet;
005import java.util.List;
006import java.util.Set;
007
008import org.lwjgl.util.vector.Vector2f;
009
010import com.fs.starfarer.api.Global;
011import com.fs.starfarer.api.campaign.CampaignFleetAPI;
012import com.fs.starfarer.api.campaign.FleetAssignment;
013import com.fs.starfarer.api.campaign.JumpPointAPI;
014import com.fs.starfarer.api.campaign.JumpPointAPI.JumpDestination;
015import com.fs.starfarer.api.campaign.LocationAPI;
016import com.fs.starfarer.api.campaign.SectorEntityToken;
017import com.fs.starfarer.api.campaign.StarSystemAPI;
018import com.fs.starfarer.api.impl.campaign.fleets.RouteLocationCalculator;
019import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteData;
020import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteSegment;
021import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
022import com.fs.starfarer.api.util.IntervalUtil;
023import com.fs.starfarer.api.util.Misc;
024
025public class FGTravelAction extends BaseFGAction {
026
027        protected SectorEntityToken from, to;
028        protected IntervalUtil interval = new IntervalUtil(0.1f, 0.3f);
029        
030        protected String travelText;
031        protected String waitingAtDestinationText;
032        protected String rendezvousTravelText;
033        protected String waitingRendezvousText;
034        protected boolean doNotGetSidetracked = true;
035        
036        public FGTravelAction(SectorEntityToken from, SectorEntityToken to) {
037                this.from = from;
038                this.to = to;
039                
040                travelText = "traveling to " + to.getName();
041                waitingAtDestinationText = "orbiting " + to.getName();
042                waitingRendezvousText = "waiting at rendezvous point";
043                rendezvousTravelText = "traveling to rendezvous point";
044                
045                if (to.getContainingLocation() instanceof StarSystemAPI) {
046                        StarSystemAPI system = (StarSystemAPI) to.getContainingLocation();
047                        if (to == system.getCenter()) {
048                                travelText = "traveling to the " + system.getNameWithLowercaseTypeShort();
049                        }
050                }
051                
052                interval.forceIntervalElapsed();
053        }
054
055        @Override
056        public void addRouteSegment(RouteData route) {
057                float travelDays = RouteLocationCalculator.getTravelDays(from, to);
058                travelDays *= 1.5f;
059                RouteSegment segment = new RouteSegment(null, travelDays, from, to, null);
060                route.addSegment(segment);
061        }
062        
063        public static float computeETADays(CampaignFleetAPI fleet, SectorEntityToken dest) {
064                boolean sameLoc = fleet.getContainingLocation() == dest.getContainingLocation();
065                float dist = 0f;
066                boolean destIsSystem = dest.getStarSystem() != null && dest.getStarSystem().getCenter() == dest;
067                if (sameLoc) {
068                        dist = Misc.getDistance(fleet, dest);
069                        if (destIsSystem) dist = 0f;
070                } else {
071                        dist = Misc.getDistance(fleet.getLocationInHyperspace(), dest.getLocationInHyperspace());
072
073                        if (!destIsSystem) {
074                                dist += 5000f; // fudge factor for traveling in-system
075                        }
076                        
077                        if (fleet.getContainingLocation() != null && 
078                                        dest.getContainingLocation() != null && 
079                                        !fleet.getContainingLocation().isHyperspace() && 
080                                        !dest.getContainingLocation().isHyperspace()) {
081                                dist += 5000f; // two legs of travel are in-system
082                        }
083                }
084                
085                return dist / 1000f;
086        }
087
088        public float getEstimatedDaysToComplete() {
089                if (intel.isSpawnedFleets()) {
090                        float totalETA = 0f;
091                        float totalStr = 0f;
092                        for (CampaignFleetAPI fleet : intel.getFleets()) {
093                                float eta = computeETADays(fleet, to); 
094                                float w = fleet.getEffectiveStrength();
095                                totalETA += eta * w;
096                                totalStr += w;
097                        }
098                        
099                        float eta = totalETA / Math.max(1f, totalStr);
100                        return eta;
101                } else {
102                        RouteSegment segment = intel.getSegmentForAction(this);
103                        if (segment == null) return 0f;
104                        return Math.max(0f, segment.daysMax - segment.elapsed);
105                }
106        }
107        
108        
109        @Override
110        public void notifySegmentFinished(RouteSegment segment) {
111                super.notifySegmentFinished(segment);
112                
113                if (FleetGroupIntel.DEBUG) {
114                        System.out.println("FGTravelAction.notifySegmentFinished() " + segment.getTransitProgress() + 
115                                        " [" + from.getName() + " -> " + to.getName() + "]");
116                }
117        }
118
119        @Override
120        public void notifyFleetsSpawnedMidSegment(RouteSegment segment) {
121                super.notifyFleetsSpawnedMidSegment(segment);
122                
123                if (FleetGroupIntel.DEBUG) {
124                        System.out.println("FGTravelAction.notifyFleetsSpawnedMidSegment() " + segment.getTransitProgress() + 
125                                        " [" + from.getName() + " -> " + to.getName() + "]");
126                }
127        }
128
129        @Override
130        public void directFleets(float amount) {
131                List<CampaignFleetAPI> fleets = intel.getFleets();
132                if (fleets.isEmpty()) {
133                        setActionFinished(true);
134                        return;
135                }
136                
137                float days = Global.getSector().getClock().convertToDays(amount);
138                interval.advance(days);
139                
140                if (!interval.intervalElapsed()) return;
141                
142                
143                
144                //JumpPointAPI jp = RouteLocationCalculator.findJumpPointToUse(fleet, current.from);
145
146                Set<LocationAPI> locations = new LinkedHashSet<LocationAPI>();
147                Set<LocationAPI> locationsWithBattles = new LinkedHashSet<LocationAPI>();
148                int inFrom = 0;
149                int inTo = 0;
150                int inHyper = 0;
151                for (CampaignFleetAPI fleet : fleets) {
152                        LocationAPI conLoc = fleet.getContainingLocation();
153                        locations.add(conLoc);
154                        if (fleet.getBattle() != null) {
155                                locationsWithBattles.add(conLoc);
156                        }
157                        
158                        if (to.getContainingLocation() == conLoc) {
159                                inTo++;
160                        } else if (from.getContainingLocation() == conLoc && !conLoc.isHyperspace()) {
161                                inFrom++;
162                        } else {
163                                inHyper++;
164                        }
165                        
166                        //fleet.getMemoryWithoutUpdate().set(MemFlags.FLEET_IGNORES_OTHER_FLEETS, true, 0.4f);
167                }
168                        
169                for (CampaignFleetAPI fleet : fleets) {                 
170                        if (doNotGetSidetracked && !locationsWithBattles.contains(fleet.getContainingLocation())) {
171                                fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_FLEET_DO_NOT_GET_SIDETRACKED, true, 0.4f);
172                        }
173                }
174                
175                boolean allInSameLocation = locations.size() == 1;
176                
177                if (allInSameLocation && inTo > 0 && to.getContainingLocation() instanceof StarSystemAPI) {
178                        // was the travel order just to the system, rather than a specific entity in it?
179                        StarSystemAPI system = (StarSystemAPI) to.getContainingLocation();
180                        if (to == system.getCenter()) {
181                                setActionFinished(true);
182                                return;
183                        }
184                }
185                
186                
187                if (allInSameLocation) {
188                        boolean allNear = true;
189                        
190                        Vector2f com = new Vector2f();
191                        float weight = 0f;
192                        String key = "$FGTravelAction_ignoreFleetForCenterOfMass";
193                        for (CampaignFleetAPI fleet : fleets) {
194                                boolean near = fleet.getContainingLocation() == to.getContainingLocation() &&
195                                                                        Misc.getDistance(fleet, to) < to.getRadius() + 500f;
196                                allNear &= near;
197                                
198                                if (Misc.isSlowMoving(fleet)) {
199                                        fleet.getMemoryWithoutUpdate().set(key, true, 2f);
200                                }
201                                if (fleet.getMemoryWithoutUpdate().getBoolean(key)) {
202                                        continue;
203                                }
204                                
205                                float w = fleet.getFleetPoints();
206                                Vector2f loc = new Vector2f(fleet.getLocation());
207                                loc.scale(w);
208                                Vector2f.add(com, loc, com);
209                                weight += w;
210                        }
211                        
212                        if (weight < 1f) {
213                                weight = 1f;
214                                if (!fleets.isEmpty()) {
215                                        com.set(fleets.get(0).getLocation());
216                                }
217                        }
218                        com.scale(1f / weight);
219                        
220                        Vector2f dest = null;
221                        if (inFrom > 0) {
222                                JumpPointAPI jp = RouteLocationCalculator.findJumpPointToUse(fleets.get(0), from);
223                                dest = jp.getLocation();
224                        } else if (inHyper > 0) {
225                                JumpPointAPI jp = RouteLocationCalculator.findJumpPointToUse(fleets.get(0), to);
226                                SectorEntityToken jumpExit = null;
227                                for (JumpDestination jd : jp.getDestinations()) {
228                                        if (jd.getDestination() != null && jd.getDestination().isInHyperspace()) {
229                                                jumpExit = jd.getDestination();
230                                                break;
231                                        }
232                                }
233                                if (jumpExit != null) {
234                                        dest = jumpExit.getLocation();
235                                } else {
236                                        dest = to.getLocationInHyperspace();
237                                }
238                        } else {
239                                dest = to.getLocation();
240                        }
241                        
242                        if (dest == null) {
243                                setActionFinished(true);
244                                return;
245                        }
246                        
247                        float angle = Misc.getAngleInDegrees(com, dest);
248                        float distComToDest = Misc.getDistance(com, dest);
249                        float offset = Math.min(distComToDest, 5000f);
250
251                        Vector2f dir = Misc.getUnitVectorAtDegreeAngle(angle);
252                        dir.scale(offset);
253                        dest = new Vector2f(dest);
254                        Vector2f.add(com, dir, dest);
255                        
256                        SectorEntityToken movementToken = fleets.get(0).getContainingLocation().createToken(dest);
257                        //SectorEntityToken comToken = fleets.get(0).getContainingLocation().createToken(com);
258                        
259                        float comLeashRange = 750f;
260                        int numFleets = fleets.size();
261                        comLeashRange += Math.min(Math.max(numFleets - 5, 0) * 100f, 500f);
262                                
263                        for (CampaignFleetAPI fleet : fleets) {                         
264                                fleet.clearAssignments();
265//                              if (fleet.getName().contains("Operations")) {
266//                                      System.out.println("efwefwe");
267//                              }
268                                
269//                              if (from.isInCurrentLocation() && from.getName().contains("Tactistar")) {
270//                                      System.out.println("efwfe");
271//                              }
272                                
273                                //fleet.getMemoryWithoutUpdate().set("$preferJumpPointAt", dest, 2f);
274                                
275                                float toCom = Misc.getDistance(fleet.getLocation(), com);
276                                float toDest = Misc.getDistance(fleet.getLocation(), dest);
277                                
278                                if (inTo > 0 && distComToDest < 500f + to.getRadius() && toCom < 500f) {
279                                        fleet.addAssignment(FleetAssignment.ORBIT_PASSIVE, to, 3f, waitingAtDestinationText);
280                                } else if (inTo <= 0 && toCom < 1000 && (toDest < 750 || distComToDest < 500f)) {
281                                        // close to the jump-point; take it by having the move order use to rather than movementToken as the target
282                                        fleet.addAssignment(FleetAssignment.GO_TO_LOCATION, to, 3f, travelText);
283                                        //fleet.getMemoryWithoutUpdate().set(MemFlags.FLEET_IGNORES_OTHER_FLEETS, true, 0.4f);
284                                } else if (toCom > comLeashRange) {
285                                        angle = Misc.getAngleInDegrees(fleet.getLocation(), com);
286                                        dir = Misc.getUnitVectorAtDegreeAngle(angle);
287                                        // need to overshoot to make sure Sustained Burn is used
288                                        dir.scale(5000f);
289                                        Vector2f overshootCom = Vector2f.add(com, dir, new Vector2f());
290                                        SectorEntityToken comToken = fleets.get(0).getContainingLocation().createToken(overshootCom);
291                                        fleet.addAssignment(FleetAssignment.ORBIT_PASSIVE, comToken, 3f, travelText);
292                                } else {
293                                        fleet.addAssignment(FleetAssignment.GO_TO_LOCATION, movementToken, 3f, travelText);
294                                }
295                        }
296                        
297                        if (allNear) {
298                                setActionFinished(true);
299                                return;
300                        }
301                        
302                } else {
303                        SectorEntityToken rendezvous = null;
304                        if (inTo <= 0) {
305                                JumpPointAPI jp = RouteLocationCalculator.findJumpPointToUse(fleets.get(0), from);
306//                              JumpPointAPI jp = null;
307//                              for (CampaignFleetAPI fleet : fleets) {
308//                                      if (fleet.getContainingLocation() == from.getContainingLocation()) {
309//                                              jp = Misc.findNearestJumpPointTo(fleet);
310//                                              break;
311//                                      }
312//                              }
313                                SectorEntityToken jumpExit = null;
314                                for (JumpDestination jd : jp.getDestinations()) {
315                                        if (jd.getDestination() != null && jd.getDestination().isInHyperspace()) {
316                                                jumpExit = jd.getDestination();
317                                                break;
318                                        }
319                                }
320                                if (jumpExit != null) {
321                                        rendezvous = jumpExit;
322                                }
323                        } else { 
324                                //rendezvous = RouteLocationCalculator.findJumpPointToUse(fleets.get(0), to);
325                                List<SectorEntityToken> potential = new ArrayList<SectorEntityToken>();
326                                for (CampaignFleetAPI fleet : fleets) {
327                                        if (fleet.getContainingLocation() == to.getContainingLocation()) {
328                                                SectorEntityToken test = Misc.findNearestJumpPointTo(fleet);
329                                                if (test != null) {
330                                                        potential.add(test);
331                                                }
332//                                              rendezvous = Misc.findNearestJumpPointTo(fleet);
333//                                              break;
334                                        }
335                                }
336                                float bestScore = Float.MAX_VALUE; // want a low score
337                                for (SectorEntityToken curr : potential) {
338                                        float score = 0f;
339                                        for (CampaignFleetAPI fleet : fleets) {
340                                                if (fleet.getContainingLocation() == to.getContainingLocation()) {
341                                                        float dist = Misc.getDistance(curr, fleet);
342                                                        score += dist * fleet.getFleetPoints();
343                                                }
344                                        }
345                                        if (score < bestScore) {
346                                                bestScore = score;
347                                                rendezvous = curr;
348                                        }
349                                }
350                        }
351                        
352                        if (rendezvous == null) {
353                                // something is majorly wrong, i.e. a system not connected to hyperspace
354                                setActionFinished(true);
355                                return;
356                        }
357
358                        for (CampaignFleetAPI fleet : fleets) {
359                                fleet.clearAssignments();
360
361                                if (fleet.getContainingLocation() != rendezvous.getContainingLocation()) {
362                                        // catching up to other fleets
363                                        fleet.addAssignment(FleetAssignment.GO_TO_LOCATION, to, 3f, travelText);
364                                } else {
365                                        float dist = Misc.getDistance(rendezvous, fleet);
366                                        if (dist < 500f) {
367                                                fleet.addAssignment(FleetAssignment.ORBIT_PASSIVE, rendezvous, 3f, waitingRendezvousText);
368                                        } else {
369                                                fleet.addAssignment(FleetAssignment.GO_TO_LOCATION, rendezvous, 3f, rendezvousTravelText);
370                                        }
371                                }
372                        }
373                }
374        }
375
376        public String getTravelText() {
377                return travelText;
378        }
379
380        public void setTravelText(String travelText) {
381                this.travelText = travelText;
382        }
383
384        public String getWaitingAtDestinationText() {
385                return waitingAtDestinationText;
386        }
387
388        public void setWaitingAtDestinationText(String waitingAtDestinationText) {
389                this.waitingAtDestinationText = waitingAtDestinationText;
390        }
391
392        public String getRendezvousTravelText() {
393                return rendezvousTravelText;
394        }
395
396        public void setRendezvousTravelText(String rendezvousTravelText) {
397                this.rendezvousTravelText = rendezvousTravelText;
398        }
399
400        public String getWaitingRendezvousText() {
401                return waitingRendezvousText;
402        }
403
404        public void setWaitingRendezvousText(String waitingRendezvousText) {
405                this.waitingRendezvousText = waitingRendezvousText;
406        }
407
408        public SectorEntityToken getFrom() {
409                return from;
410        }
411
412        public SectorEntityToken getTo() {
413                return to;
414        }
415
416        public void setFrom(SectorEntityToken from) {
417                this.from = from;
418        }
419
420        public void setTo(SectorEntityToken to) {
421                this.to = to;
422        }
423
424        public boolean isDoNotGetSidetracked() {
425                return doNotGetSidetracked;
426        }
427
428        public void setDoNotGetSidetracked(boolean doNotGetSidetracked) {
429                this.doNotGetSidetracked = doNotGetSidetracked;
430        }
431        
432}
433
434
435
436