001package com.fs.starfarer.api.impl.campaign.fleets;
002
003import java.util.List;
004
005import org.lwjgl.util.vector.Vector2f;
006
007import com.fs.starfarer.api.Global;
008import com.fs.starfarer.api.campaign.CampaignFleetAPI;
009import com.fs.starfarer.api.campaign.FactionAPI;
010import com.fs.starfarer.api.campaign.JumpPointAPI;
011import com.fs.starfarer.api.campaign.LocationAPI;
012import com.fs.starfarer.api.campaign.SectorEntityToken;
013import com.fs.starfarer.api.campaign.StarSystemAPI;
014import com.fs.starfarer.api.campaign.econ.MarketAPI;
015import com.fs.starfarer.api.impl.campaign.JumpPointInteractionDialogPluginImpl;
016import com.fs.starfarer.api.impl.campaign.shared.WormholeManager;
017import com.fs.starfarer.api.util.Misc;
018
019public class RouteLocationCalculator {
020        
021        
022        public static enum IntervalType {
023                DAYS,
024                TRAVEL_TIME,
025                FRACTION_OF_REMAINING
026        }
027        
028        public static class TaskInterval {
029                public IntervalType type;
030                public float value;
031                
032                public TaskInterval(IntervalType type, float value) {
033                        this.type = type;
034                        this.value = value;
035                }
036
037                public TaskInterval(IntervalType type) {
038                        super();
039                        this.type = type;
040                }
041
042
043                public static TaskInterval days(float days) {
044                        return new TaskInterval(IntervalType.DAYS, days);
045                }
046                public static TaskInterval travel() {
047                        return new TaskInterval(IntervalType.TRAVEL_TIME, 0f);
048                }
049                public static TaskInterval remaining(float fraction) {
050                        return new TaskInterval(IntervalType.FRACTION_OF_REMAINING, fraction);
051                }
052        }
053        
054        public static float getTravelDays(SectorEntityToken from, SectorEntityToken to) {
055                float dist = 0f;
056                if (from.getContainingLocation() != to.getContainingLocation()) {
057                        dist = Misc.getDistance(from.getLocationInHyperspace(), to.getLocationInHyperspace());
058                        if (!from.isInHyperspace()) {
059                                dist += 5000; // random guess at distance to/from jump-point
060                        }
061                        if (!to.isInHyperspace()) {
062                                dist += 5000; // random guess at distance to/from jump-point
063                        }
064                } else {
065                        dist = Misc.getDistance(from, to);
066                        if (from.isSystemCenter() || to.isSystemCenter()) dist = 0f;
067                }
068                
069                
070                float travelTime = dist / 1500f;
071                return travelTime;
072        }
073        
074        public static void computeIntervalsAndSetLocation(CampaignFleetAPI fleet, float daysElapsed, float maxDays,
075                                                                                        boolean onlyComputeIntervals,
076                                                                                        TaskInterval [] intervals,
077                                                                                        SectorEntityToken ... sequence) {
078                
079                float totalDays = 0f;
080                float totalFraction = 0f;
081                for (int i = 0; i < intervals.length; i++) {
082                        TaskInterval t = intervals[i];
083                        if (t.type == IntervalType.TRAVEL_TIME) {
084                                SectorEntityToken from = sequence[i];
085                                SectorEntityToken to = sequence[i + 1];
086                                
087                                float travelTime = getTravelDays(from, to);
088                                t.value = travelTime;
089                        }
090                        
091                        if (t.type == IntervalType.FRACTION_OF_REMAINING) {
092                                totalFraction += t.value;
093                        } else {
094                                totalDays += t.value;
095                        }
096                }
097                
098                if (totalFraction > 0) {
099                        float remaining = maxDays - totalDays;
100                        for (TaskInterval t : intervals) {
101                                if (t.type == IntervalType.FRACTION_OF_REMAINING) {
102                                        t.value = Math.max(0.1f, t.value / totalFraction * remaining);
103                                }
104                        }
105                }
106                
107                totalDays = 0f;
108                for (TaskInterval t : intervals) {
109                        totalDays += t.value;
110                }
111                
112                if (totalDays > maxDays) {
113                        for (TaskInterval t : intervals) {
114                                t.value *= maxDays / totalDays;
115                        }
116                }
117                
118                float soFar = 0f;
119                float progress = 0f;
120                int index = 0;
121                SectorEntityToken from = null;
122                SectorEntityToken to = null;
123                //for (float curr : intervals) {
124                for (int i = 0; i < intervals.length; i++) {
125                        float curr = intervals[i].value;
126                        if (curr < 0) curr = 0;
127                        
128                        if (soFar + curr > daysElapsed && curr > 0) {
129                                progress = (daysElapsed - soFar) / curr;
130                                if (progress < 0) progress = 0;
131                                if (progress > 1) progress = 1;
132                                
133                                from = sequence[index];
134                                to = sequence[index + 1];
135                                intervals[i].value *= (1f - progress);
136                                break;
137                        }
138                        soFar += curr;
139                        intervals[i].value = 0;
140                        index++;
141                }
142                if (from == null) {
143                        index = intervals.length - 1;
144                        from = sequence[sequence.length - 2];
145                        to = sequence[sequence.length - 1];
146                        progress = 1f;
147                }
148                
149                if (onlyComputeIntervals) {
150                        return;
151                }
152                
153                setLocation(fleet, progress, from, to);
154        }
155        
156
157        /**
158         * Used to assign a reasonable location to a fleet that was just spawned by RouteManager.
159         * 
160         * Will normalize the intervals array to not exceed maxDays total, and will then set
161         * its values to the days remaining for each section.
162         * 
163         * 
164         * @param fleet
165         * @param daysElapsed
166         * @param intervals
167         * @param sequence Must have length = intervals.length + 1.
168         */
169        public static int setLocation(CampaignFleetAPI fleet,
170                                                                   float daysElapsed, float maxDays, int overflowIndex, 
171                                                                   boolean onlyAdjustIntervals,
172                                                                   float [] intervals, SectorEntityToken ... sequence) {
173                
174                float total = 0f;
175                for (float curr : intervals) {
176                        total += curr;
177                }
178                
179                // either add/subtract any extra days to the overflow interval, or scale all if there isn't one
180                // e.g. treat intervals as weights if there is no overflow interval specified
181                if (total != maxDays) {
182                        if (overflowIndex >= 0) {
183                                float extra = total - maxDays;
184                                intervals[overflowIndex] -= extra;
185                                if (intervals[overflowIndex] <= 0) {
186                                        total = maxDays - intervals[overflowIndex];
187                                        intervals[overflowIndex] = 0;
188                                        for (int i = 0; i < intervals.length; i++) {
189                                                intervals[i] *= maxDays / total;
190                                        }
191                                }
192                        } else {
193                                for (int i = 0; i < intervals.length; i++) {
194                                        intervals[i] *= maxDays / total;
195                                }               
196                        }
197                }
198                
199                
200                float soFar = 0f;
201                float progress = 0f;
202                int index = 0;
203                SectorEntityToken from = null;
204                SectorEntityToken to = null;
205                //for (float curr : intervals) {
206                for (int i = 0; i < intervals.length; i++) {
207                        float curr = intervals[i];
208                        if (curr < 0) curr = 0;
209                        
210                        if (soFar + curr > daysElapsed && curr > 0) {
211                                progress = (daysElapsed - soFar) / curr;
212                                if (progress < 0) progress = 0;
213                                if (progress > 1) progress = 1;
214                                
215                                from = sequence[index];
216                                to = sequence[index + 1];
217                                intervals[i] *= (1f - progress);
218                                break;
219                        }
220                        soFar += curr;
221                        intervals[i] = 0;
222                        index++;
223                }
224                if (from == null) {
225                        index = intervals.length - 1;
226                        from = sequence[sequence.length - 2];
227                        to = sequence[sequence.length - 1];
228                        progress = 1f;
229                }
230                
231                
232                if (onlyAdjustIntervals) {
233                        return index;
234                }
235                
236                setLocation(fleet, progress, from, to);
237                
238                return index;
239        }
240        
241        public static void setLocation(CampaignFleetAPI fleet,
242                                                                   float progress, SectorEntityToken from, SectorEntityToken to) {
243//              Cases to handle:
244//              from/to are same
245//              hyper - hyper
246//              system - system
247//              system - hyper
248//              hyper - system
249//              system - other_system
250                
251//              Subcase: 
252//              one end is system center - spawn "somewhere in system" in that case
253                
254                if (progress < 0) progress = 0;
255                if (progress > 1) progress = 1;
256                
257                if (to == null) to = from;
258                if (from == null) from = to;
259                
260                if (from == null && to == null) return;
261                
262                float varianceMult = getVarianceMult(progress);
263                
264                SectorEntityToken forSystemCenterCheck = null;
265                LocationAPI conLoc = null;
266                Vector2f loc = null;
267                if (from == to) {
268                        conLoc = from.getContainingLocation();
269                        forSystemCenterCheck = from;
270
271                        if (!conLoc.isHyperspace()) {
272                                if (progress > 0.03f) {
273                                        // at a typical orbiting radius
274                                        loc = Misc.getPointAtRadius(from.getLocation(), from.getRadius() + 100f + (float) Math.random() * 100f);
275                                } else {
276                                        loc = new Vector2f(from.getLocation());
277                                }
278                        } else {
279                                loc = Misc.getPointWithinRadius(from.getLocation(), 100f + 900f * varianceMult);
280                        }
281                }
282                // hyper to hyper
283                else if (from.isInHyperspace() && to.isInHyperspace()) {
284                        conLoc = from.getContainingLocation();
285                        loc = Misc.interpolateVector(from.getLocation(),
286                                                                                 to.getLocation(),
287                                                                                 progress);
288                        loc = Misc.getPointWithinRadius(loc, 100f + 900f * varianceMult);
289                }
290                // different locations in same system (not hyper)
291                else if (from.getContainingLocation() == to.getContainingLocation()) {
292                        conLoc = from.getContainingLocation();
293                        if (from.isSystemCenter()) forSystemCenterCheck = from;
294                        if (to.isSystemCenter()) forSystemCenterCheck = to;
295                        
296                        loc = Misc.interpolateVector(from.getLocation(),
297                                                                                to.getLocation(),
298                                                                                progress);
299                        if (conLoc instanceof StarSystemAPI && 
300                                        Misc.getDistance(loc, new Vector2f()) < 2000) { // quick hack to avoid primary star
301                                loc = Misc.getPointAtRadius(new Vector2f(), 2000f);
302                        } else {
303                                loc = Misc.getPointWithinRadius(loc, 100f + 900f * varianceMult);
304                        }
305                }
306                // one in hyper, one isn't
307                else if (from.isInHyperspace() != to.isInHyperspace()) {
308                        SectorEntityToken inSystem = from;
309                        SectorEntityToken inHyper = to;
310                        float p = progress;
311                        if (from.isInHyperspace()) {
312                                inSystem = to;
313                                inHyper = from;
314                                p = 1f - progress;
315                        }
316                        
317                        JumpPointAPI jp = findJumpPointToUse(fleet, inSystem);
318                        if (jp == null) return;
319                        float d1 = Misc.getDistance(inSystem, jp);
320                        float d2 = Misc.getDistance(jp.getLocationInHyperspace(), inHyper.getLocation());
321                        if (d1 < 1) d1 = 1;
322                        if (d2 < 1) d2 = 1;
323                        
324                        float t = d1 / (d1 + d2);
325                        if (p < t) { // in system on way to jump-point
326                                conLoc = inSystem.getContainingLocation();
327                                forSystemCenterCheck = inSystem;
328                                
329                                loc = Misc.interpolateVector(inSystem.getLocation(),
330                                                                                        jp.getLocation(),
331                                                                                        p / t);
332                                varianceMult = getVarianceMult(p / t);
333                        } else { // in hyper on way from jump-point to location
334                                conLoc = inHyper.getContainingLocation();
335                                loc = Misc.interpolateVector(Misc.getSystemJumpPointHyperExitLocation(jp),
336                                                                                        inHyper.getLocation(),
337                                                                                        (p - t) / (1f - t));
338                                varianceMult = getVarianceMult((p - t) / (1f - t));
339                        }
340                        loc = Misc.getPointWithinRadius(loc, 100f + 900f * varianceMult);
341                }
342                // from one system to a different system
343                else if (from.getContainingLocation() != to.getContainingLocation()) {
344//                      JumpPointAPI jp1 = Misc.findNearestJumpPointTo(from);
345//                      JumpPointAPI jp2 = Misc.findNearestJumpPointTo(to);
346                        JumpPointAPI jp1 = findJumpPointToUse(fleet, from);
347                        JumpPointAPI jp2 = findJumpPointToUse(fleet, to);
348                        if (jp1 == null || jp2 == null) return;
349                        float d1 = Misc.getDistance(from, jp1);
350                        float d2 = Misc.getDistance(Misc.getSystemJumpPointHyperExitLocation(jp1),
351                                                                                Misc.getSystemJumpPointHyperExitLocation(jp1));
352                        float d3 = Misc.getDistance(jp2, to);
353                        if (d1 < 1) d1 = 1;
354                        if (d2 < 1) d2 = 1;
355                        if (d3 < 1) d3 = 1;
356                        
357                        float t1 = d1 / (d1 + d2 + d3);                 
358                        float t2 = (d1 + d2) / (d1 + d2 + d3);
359                        
360                        if (progress < t1) { // from "from" to jump-point
361                                conLoc = from.getContainingLocation();
362                                forSystemCenterCheck = from;
363                                
364                                loc = Misc.interpolateVector(from.getLocation(),
365                                                                                         jp1.getLocation(),
366                                                                                         progress / t1);
367                                varianceMult = getVarianceMult(progress / t1);
368                        } else if (progress < t2) { // in hyperspace, traveling between systems
369                                conLoc = Global.getSector().getHyperspace();
370                                loc = Misc.interpolateVector(Misc.getSystemJumpPointHyperExitLocation(jp1),
371                                                                                         Misc.getSystemJumpPointHyperExitLocation(jp2),
372                                                                                    (progress - t1) / (t2 - t1));
373                                varianceMult = getVarianceMult((progress - t1) / (t2 - t1));
374                        } else { // in the "to" system, going from jp2 to to
375                                conLoc = to.getContainingLocation();
376                                forSystemCenterCheck = to;
377                                
378                                loc = Misc.interpolateVector(jp2.getLocation(),
379                                                                                         to.getLocation(),
380                                                                                         (progress - t2) / (1f - t2));
381                                varianceMult = getVarianceMult((progress - t2) / (1f - t2));
382                        }
383                        loc = Misc.getPointWithinRadius(loc, 100f + 900f * varianceMult);
384                }
385
386                
387                if (forSystemCenterCheck != null && forSystemCenterCheck.isSystemCenter() &&
388                                conLoc == forSystemCenterCheck.getContainingLocation()) {
389                        loc = Misc.getPointAtRadius(forSystemCenterCheck.getLocation(), forSystemCenterCheck.getRadius() + 3000f + (float) Math.random() * 2000f);
390                }
391                
392//              loc = Misc.getPointWithinRadius(loc, 
393//                              route.getMarket().getPrimaryEntity().getRadius() + 100 + (float) Math.random() * 100f);
394                
395                // failsafes
396                if (conLoc == null) conLoc = from.getContainingLocation();
397                if (loc == null) loc = new Vector2f(from.getLocation());
398                
399                
400                if (fleet.getContainingLocation() != conLoc) {
401                        if (fleet.getContainingLocation() != null) {
402                                fleet.getContainingLocation().removeEntity(fleet);
403                        }
404                        conLoc.addEntity(fleet);
405                }
406                fleet.setLocation(loc.x, loc.y);
407        }
408        
409        
410        public static float getVarianceMult(float p) {
411                float varianceMult = 1f;
412                if (p < 0.1f) {
413                        varianceMult = p / 0.1f;
414                } else if (p > 0.9f) {
415                        varianceMult = (1f - p) / 0.1f;
416                }
417                return varianceMult;
418        }
419        
420        
421        public static JumpPointAPI findJumpPointToUse(CampaignFleetAPI fleet, SectorEntityToken from) {
422                return findJumpPointToUse(fleet.getFaction(), from);
423        }
424        public static JumpPointAPI findJumpPointToUse(FactionAPI faction, SectorEntityToken from) {
425                float min = Float.MAX_VALUE;
426                JumpPointAPI result = null;
427                float fringeMax = 0;
428                JumpPointAPI fringe = null;
429                
430                LocationAPI location = from.getContainingLocation();
431                List<JumpPointAPI> points = location.getEntities(JumpPointAPI.class);
432                
433                
434                for (JumpPointAPI curr : points) {
435                        if (curr.getMemoryWithoutUpdate().getBoolean(JumpPointInteractionDialogPluginImpl.UNSTABLE_KEY)) {
436                                continue;
437                        }
438                        if (curr.getMemoryWithoutUpdate().getBoolean(WormholeManager.WORMHOLE)) {
439                                continue;
440                        }
441                        
442                        float dist = Misc.getDistance(from.getLocation(), curr.getLocation());
443                        if (dist < min) {
444                                min = dist;
445                                result = curr;
446                        }
447                        dist = Misc.getDistance(new Vector2f(), curr.getLocation());
448                        if (dist > fringeMax) {
449                                fringe = curr;
450                                fringeMax = dist;
451                        }
452                }
453                
454                if (from.getContainingLocation() instanceof StarSystemAPI) {
455                        StarSystemAPI system = (StarSystemAPI) from.getContainingLocation();
456                        boolean useFringeOnly = !isInControlOfSystemOrEven(faction, system);
457                        if (useFringeOnly && fringe != null) {
458                                return fringe;
459                        }
460                }
461                
462                return result;
463        }
464        
465        public static boolean isInControlOfSystemOrEven(FactionAPI faction, StarSystemAPI system) {
466                List<MarketAPI> markets = Misc.getMarketsInLocation(system);
467                int hostileMax = 0;
468                int ourMax = 0;
469                for (MarketAPI market : markets) {
470                        if (market.getFaction().isHostileTo(faction)) {
471                                hostileMax = Math.max(hostileMax, market.getSize());
472                        } else if (market.getFaction() == faction) {
473                                ourMax = Math.max(ourMax, market.getSize());
474                        }
475                }
476                boolean inControl = ourMax >= hostileMax;
477                return inControl;
478        }
479}
480
481
482
483
484
485
486
487