001package com.fs.starfarer.api.impl.campaign.command;
002
003import java.util.ArrayList;
004import java.util.HashSet;
005import java.util.LinkedHashSet;
006import java.util.List;
007import java.util.Set;
008
009import com.fs.starfarer.api.EveryFrameScript;
010import com.fs.starfarer.api.Global;
011import com.fs.starfarer.api.campaign.CampaignFleetAPI;
012import com.fs.starfarer.api.campaign.FactionAPI;
013import com.fs.starfarer.api.campaign.SectorEntityToken;
014import com.fs.starfarer.api.campaign.StarSystemAPI;
015import com.fs.starfarer.api.campaign.ai.CampaignFleetAIAPI.ActionType;
016import com.fs.starfarer.api.campaign.econ.MarketAPI;
017import com.fs.starfarer.api.campaign.listeners.ObjectiveEventListener;
018import com.fs.starfarer.api.impl.campaign.MilitaryResponseScript;
019import com.fs.starfarer.api.impl.campaign.MilitaryResponseScript.MilitaryResponseParams;
020import com.fs.starfarer.api.impl.campaign.fleets.EconomyFleetRouteManager;
021import com.fs.starfarer.api.impl.campaign.fleets.RouteManager;
022import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.OptionalFleetData;
023import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteData;
024import com.fs.starfarer.api.impl.campaign.ids.Factions;
025import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
026import com.fs.starfarer.api.impl.campaign.ids.Tags;
027import com.fs.starfarer.api.impl.campaign.intel.events.HostileActivityEventIntel;
028import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.Objectives;
029import com.fs.starfarer.api.impl.campaign.tutorial.TutorialMissionIntel;
030import com.fs.starfarer.api.plugins.BuildObjectiveTypePicker;
031import com.fs.starfarer.api.plugins.BuildObjectiveTypePicker.BuildObjectiveParams;
032import com.fs.starfarer.api.util.CountingMap;
033import com.fs.starfarer.api.util.Misc;
034import com.fs.starfarer.api.util.TimeoutTracker;
035import com.fs.starfarer.api.util.WeightedRandomPicker;
036
037public class WarSimScript implements EveryFrameScript, ObjectiveEventListener {
038
039        public static enum LocationDanger {
040                NONE(0.01f),
041                MINIMAL(0.1f),
042                LOW(0.2f),
043                MEDIUM(0.3f),
044                HIGH(0.5f),
045                EXTREME(0.8f),
046                ;
047                
048                public static LocationDanger [] vals = values();
049                
050                public float enemyStrengthFraction;
051                private LocationDanger(float enemyStrengthFraction) {
052                        this.enemyStrengthFraction = enemyStrengthFraction;
053                }
054                
055                public LocationDanger next() {
056                        int index = this.ordinal() + 1;
057                        if (index >= vals.length) index = vals.length - 1;
058                        return vals[index];
059                }
060                public LocationDanger prev() {
061                        int index = this.ordinal() - 1;
062                        if (index < 0) index = 0;
063                        return vals[index];
064                }
065
066        }
067        
068        
069        
070        
071        
072        public static final String KEY = "$core_warSimScript";
073        
074        public static final float CHECK_DAYS = 10f;
075        public static final float CHECK_PROB = 0.5f;
076        
077        
078        public static WarSimScript getInstance() {
079                Object test = Global.getSector().getMemoryWithoutUpdate().get(KEY);
080                return (WarSimScript) test; 
081        }
082        
083        protected TimeoutTracker<String> timeouts = new TimeoutTracker<String>();
084
085        protected List<StarSystemAPI> queue = new ArrayList<StarSystemAPI>();
086        
087        public WarSimScript() {
088                Global.getSector().getMemoryWithoutUpdate().set(KEY, this);
089                Global.getSector().getListenerManager().addListener(this);
090                
091                for (StarSystemAPI system : Global.getSector().getEconomy().getStarSystemsWithMarkets()) {
092                        String sid = getStarSystemTimeoutId(system);
093                        timeouts.add(sid, 2f + (float) Math.random() * 3f);
094                }
095        }
096        
097        protected Object readResolve() {
098                if (timeouts == null) {
099                        timeouts = new TimeoutTracker<String>();
100                }
101                return this;
102        }
103        
104        public void advance(float amount) {
105                //if (true) return;
106                
107                if (TutorialMissionIntel.isTutorialInProgress()) {
108                        return;
109                }
110                
111                float days = Misc.getDays(amount);
112                
113                timeouts.advance(days);
114                
115                if (queue.isEmpty()) {
116                        queue = Global.getSector().getEconomy().getStarSystemsWithMarkets();
117                }
118                
119                if (!queue.isEmpty()) {
120                        StarSystemAPI curr = queue.remove(0);
121                        processStarSystem(curr);
122                }
123        }
124
125        public void processStarSystem(StarSystemAPI system) {
126                String sid = getStarSystemTimeoutId(system);
127                if (timeouts.contains(sid)) return;
128                timeouts.add(sid, 2f + (float) Math.random() * 3f);
129                
130                CountingMap<FactionAPI> str = getFactionStrengths(system);
131
132                boolean inSpawnRange = RouteManager.isPlayerInSpawnRange(system.getCenter());
133                
134                List<FactionAPI> factions = new ArrayList<FactionAPI>(str.keySet());
135                
136//              if (system.getName().toLowerCase().contains("old milix")) {
137//                      System.out.println("wefwefwe");
138//              }
139                
140//              if (system.isCurrentLocation()) {
141//                      System.out.println("ff23f23f32");
142//              }
143                
144                for (SectorEntityToken obj : system.getEntitiesWithTag(Tags.OBJECTIVE)) {
145                        List<FactionAPI> contenders = new ArrayList<FactionAPI>();
146                        
147                        // figure out if anyone that doesn't own it thinks they should own it
148                        for (FactionAPI faction : factions) {
149                                if (wantsToOwnObjective(faction, str, obj)) {
150                                        contenders.add(faction);
151                                        String id = getControlTimeoutId(obj, faction);
152                                        if (!timeouts.contains(id)) {
153                                                addObjectiveActionResponse(obj, faction, obj.getFaction());
154                                        }
155                                } else if (faction == obj.getFaction()) {
156                                        contenders.add(faction);
157                                }
158                        }
159                        
160                        if (!inSpawnRange) {
161                                String id = getControlSimTimeoutId(obj);
162                                if (timeouts.contains(id)) continue;
163                                
164                                timeouts.add(id, 10f + (float) Math.random() * 30f);
165                                
166                                WeightedRandomPicker<FactionAPI> picker = new WeightedRandomPicker<FactionAPI>();
167                                float max = 0f;
168                                for (FactionAPI faction : contenders) {
169                                        float curr = str.getCount(faction) + getStationStrength(faction, system, obj);
170                                        if (curr > max) {
171                                                max = curr;
172                                        }
173                                }
174                                if (max <= 0) continue;
175                                
176                                for (FactionAPI faction : contenders) {
177                                        float curr = str.getCount(faction) + getStationStrength(faction, system, obj);
178                                        float w = (curr / max) - 0.5f;
179                                        picker.add(faction, w);
180                                }
181                                
182                                FactionAPI winner = picker.pick();
183                                if (winner != null && winner != obj.getFaction()) {
184                                        Objectives o = new Objectives(obj);
185                                        o.control(winner.getId());
186                                }
187                        }
188                }
189                
190                
191                for (SectorEntityToken sLoc : system.getEntitiesWithTag(Tags.STABLE_LOCATION)) {
192                        if (sLoc.hasTag(Tags.NON_CLICKABLE)) continue;
193                        if (sLoc.hasTag(Tags.FADING_OUT_AND_EXPIRING)) continue;
194                        if (!inSpawnRange) {
195                                String id = getBuildSimTimeoutId(sLoc);
196                                if (timeouts.contains(id)) continue;
197                                
198                                timeouts.add(id, 20f + (float) Math.random() * 20f);
199                                
200                                WeightedRandomPicker<FactionAPI> picker = new WeightedRandomPicker<FactionAPI>();
201                                float max = 0f;
202                                for (FactionAPI faction : factions) {
203                                        float curr = str.getCount(faction) + getStationStrength(faction, system, sLoc);
204                                        if (curr > max) {
205                                                max = curr;
206                                        }
207                                }
208                                if (max <= 0) continue;
209                                
210                                for (FactionAPI faction : factions) {
211                                        float curr = str.getCount(faction) + getStationStrength(faction, system, sLoc);
212                                        float w = (curr / max) - 0.5f;
213                                        picker.add(faction, w);
214                                }
215                                
216                                FactionAPI winner = picker.pick();
217                                if (winner != null && winner != sLoc.getFaction()) {
218                                        BuildObjectiveParams params = new BuildObjectiveParams();
219                                        params.faction = winner;
220                                        params.fleet = null;
221                                        params.stableLoc = sLoc;
222                                        BuildObjectiveTypePicker pick = Global.getSector().getGenericPlugins().pickPlugin(BuildObjectiveTypePicker.class, params);
223                                        String type = null;
224                                        if (pick != null) {
225                                                type = pick.pickObjectiveToBuild(params);
226                                        }
227                                        if (type != null) {
228                                                Objectives o = new Objectives(sLoc);
229                                                o.build(type, winner.getId());
230                                        }
231                                }
232                        }
233                }
234        }
235        
236        /**
237         * If it doesn't already own it, it's owned by an enemy, and the faction either
238         * has the closest market to it or is the strongest in-system faction.
239         * 
240         * Or: owned by a non-hostile faction that has no colony presence in the system, and this faction does
241         * @param faction
242         * @param str
243         * @param o
244         * @return
245         */
246        protected boolean wantsToOwnObjective(FactionAPI faction, CountingMap<FactionAPI> str, SectorEntityToken o) {
247                if (o.getFaction() == faction) return false;
248                if (!o.getFaction().isHostileTo(faction) && !o.getFaction().isNeutralFaction()) {
249//                      for (MarketAPI curr : Misc.getMarketsInLocation(o.getContainingLocation())) {
250//                              if (curr.getFaction() == o.getFaction() && 
251//                                              !curr.getFaction().isNeutralFaction() &&
252//                                              !curr.getFaction().isPlayerFaction()) {
253//                                      return false;
254//                              }
255//                      }
256//                      return true;
257                        
258                        boolean ownerHasColonyInSystem = false;
259                        for (MarketAPI curr : Misc.getMarketsInLocation(o.getContainingLocation())) {
260                                if (curr.getFaction() == o.getFaction() && 
261                                                !curr.getFaction().isNeutralFaction()) {
262                                        ownerHasColonyInSystem = true;
263                                        break;
264                                }
265                        }
266                        if (ownerHasColonyInSystem) return false;
267                        return true;
268                }
269                
270                float minDist = Float.MAX_VALUE;
271                MarketAPI closest = null;
272                boolean haveInSystemMarkets = false;
273                for (MarketAPI market : Misc.getMarketsInLocation(o.getContainingLocation())) {
274                        float dist = Misc.getDistance(market.getPrimaryEntity(), o);
275                        if (dist < minDist) {
276                                minDist = dist;
277                                closest = market;
278                        }
279                        if (faction == market.getFaction()) {
280                                haveInSystemMarkets = true;
281                        }
282                }
283                
284                if (closest != null && closest.getFaction() == faction) {
285                        return true;
286                }
287                
288                // pirate-like factions will try to pick up objectives that are far away from any markets
289                if (faction.getCustomBoolean(Factions.CUSTOM_PIRATE_BEHAVIOR)) {
290                        if (minDist > 8000) {
291                                return true;
292                        }
293                }
294                
295                if (!haveInSystemMarkets && closest != null && !closest.getFaction().isHostileTo(faction)) {
296                        return false;
297                }
298                
299                int maxStr = 0;
300                FactionAPI strongest = null;
301                for (FactionAPI curr : str.keySet()) {
302                        int s = str.getCount(curr);
303                        if (s > maxStr) {
304                                maxStr = s;
305                                strongest = curr;
306                        }
307                }
308                
309                return strongest == faction;
310        }
311        
312
313
314        public void reportObjectiveChangedHands(SectorEntityToken objective, FactionAPI from, FactionAPI to) {
315                addObjectiveActionResponse(objective, from, to);
316        }
317
318
319        public void reportObjectiveDestroyed(SectorEntityToken objective, SectorEntityToken stableLocation, FactionAPI enemy) {
320                String id = getBuildSimTimeoutId(stableLocation);
321                timeouts.add(id, 40f + (float) Math.random() * 20f, 100f);
322                
323                addObjectiveActionResponse(objective, objective.getFaction(), null);
324        }
325
326        
327        protected String getStarSystemTimeoutId(StarSystemAPI system) {
328                String id = "starsystem_" + system.getId();
329                return id;
330        }
331        
332        protected String getBuildSimTimeoutId(SectorEntityToken objective) {
333                String id = "sim_build_" + objective.getId();
334                return id;
335        }
336        
337        protected String getControlSimTimeoutId(SectorEntityToken objective) {
338                String id = "sim_changedhands_" + objective.getId();
339                return id;
340        }
341        
342        protected String getControlTimeoutId(SectorEntityToken objective, FactionAPI faction) {
343                String id = faction.getId() + "_" + objective.getId();
344                return id;
345        }
346        
347        protected void addObjectiveActionResponse(SectorEntityToken objective, FactionAPI faction, FactionAPI enemy) {
348                if (faction.isNeutralFaction()) return;
349                if (faction.getCustomBoolean(Factions.CUSTOM_NO_WAR_SIM)) return;
350                
351                if (enemy != null && enemy.isNeutralFaction()) return;
352                if (enemy != null && !faction.isHostileTo(enemy)) return;
353                
354                String id = getControlTimeoutId(objective, faction);
355                if (timeouts.contains(id)) return;
356                
357                if (isAlreadyFightingFor(objective, faction)) { // an MRS from some other source, such as a raid
358                        return;
359                }
360                
361                MilitaryResponseParams params = new MilitaryResponseParams(ActionType.HOSTILE, 
362                                objective.getId(), 
363                                faction,
364                                objective,
365                                0.4f,
366                                20f + (float) Math.random() * 20f);
367                MilitaryResponseScript script = new MilitaryResponseScript(params);
368                objective.getContainingLocation().addScript(script);
369                
370                timeouts.add(id, params.responseDuration * 2f);
371        }
372        
373        
374        
375        
376        public boolean isDone() {
377                return false;
378        }
379
380        public boolean runWhilePaused() {
381                return false;
382        }
383
384        
385        
386        
387        public static CountingMap<FactionAPI> getFactionStrengths(StarSystemAPI system) {
388                CountingMap<FactionAPI> result = new CountingMap<FactionAPI>();
389
390                Set<FactionAPI> factions = new LinkedHashSet<FactionAPI>();
391//              if (system.getName().startsWith("Askonia")) {
392//                      System.out.println("wefewfew");
393//              }
394                
395                for (CampaignFleetAPI fleet : system.getFleets()) {
396                        if (fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_TRADE_FLEET)) continue;
397                        if (fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_SMUGGLER)) continue;
398                        factions.add(fleet.getFaction());
399                }
400                
401                for (RouteData route : RouteManager.getInstance().getRoutesInLocation(system)) {
402                        String id = route.getFactionId();
403                        if (id == null) continue;
404                        FactionAPI faction = Global.getSector().getFaction(id);
405                        factions.add(faction);
406                }
407                
408                for (FactionAPI faction : factions) {
409                        if (faction.getCustomBoolean(Factions.CUSTOM_NO_WAR_SIM)) continue;
410                        
411                        int strength = (int) getFactionStrength(faction, system);
412                        if (strength > 0) {
413                                result.add(faction, strength);
414                        }
415                }
416                return result;
417        }
418        
419        
420        
421        public static float getRelativeEnemyStrength(String factionId, StarSystemAPI system) {
422                float enemyStrength = getEnemyStrength(factionId, system);
423                float factionStrength = getFactionStrength(factionId, system);
424                float f = enemyStrength / Math.max(1f, factionStrength + enemyStrength);
425                return f;
426        }
427        
428        public static float getRelativeFactionStrength(String factionId, StarSystemAPI system) {
429                float enemyStrength = getEnemyStrength(factionId, system);
430                float factionStrength = getFactionStrength(factionId, system);
431                float f = factionStrength / Math.max(1f, factionStrength + enemyStrength);
432                return f;
433        }
434        
435        public static float getEnemyStrength(String factionId, StarSystemAPI system) {
436                return getEnemyStrength(Global.getSector().getFaction(factionId), system, false);
437        }
438        public static float getEnemyStrength(FactionAPI faction, StarSystemAPI system) {
439                return getEnemyStrength(faction, system, false);
440        }
441        public static float getEnemyStrength(String factionId, StarSystemAPI system, boolean assumeHostileToPlayer) {
442                return getEnemyStrength(Global.getSector().getFaction(factionId), system, assumeHostileToPlayer);
443        }
444        public static float getEnemyStrength(FactionAPI faction, StarSystemAPI system, boolean assumeHostileToPlayer) {
445                float enemyStr = 0;
446                Set<String> seen = new HashSet<String>();
447                if (EconomyFleetRouteManager.ENEMY_STRENGTH_CHECK_EXCLUDE_PIRATES) {
448                        seen.add(Factions.PIRATES);
449                }
450                
451                
452                for (MarketAPI target : Misc.getMarketsInLocation(system)) {
453                        if (!(assumeHostileToPlayer && target.getFaction().isPlayerFaction())) {
454                                if (!target.getFaction().isHostileTo(faction)) continue;
455                        }
456                        
457                        if (seen.contains(target.getFactionId())) continue;
458                        seen.add(target.getFactionId());
459                        enemyStr += WarSimScript.getFactionStrength(target.getFaction(), system);
460                }
461                
462                if (faction.isPlayerFaction()) {
463                        HostileActivityEventIntel intel = HostileActivityEventIntel.get();
464                        //HostileActivityIntel intel = HostileActivityIntel.get(system);
465                        if (intel != null) {
466                                enemyStr += intel.getVeryApproximateFPStrength(system);
467                        }
468                }
469                
470                return enemyStr;
471        }
472        
473        public static float getFactionStrength(String factionId, StarSystemAPI system) {
474                return getFactionStrength(Global.getSector().getFaction(factionId), system);
475        }
476        public static float getFactionStrength(FactionAPI faction, StarSystemAPI system) {
477                float strength = 0f;
478                
479//              if (system.getName().toLowerCase().contains("naraka") && Factions.PIRATES.equals(faction.getId())) {
480//                      System.out.println("wefwefwe");
481//              }
482                
483                Set<CampaignFleetAPI> seenFleets = new HashSet<CampaignFleetAPI>();
484                for (CampaignFleetAPI fleet : system.getFleets()) {
485                        if (fleet.getFaction() != faction) continue;
486                        if (fleet.isStationMode()) continue;
487                        if (fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_TRADE_FLEET)) continue;
488                        if (fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_SMUGGLER)) continue;
489                        
490                        if (fleet.isPlayerFleet()) continue;
491                        
492//                      if (EconomyFleetRouteManager.FACTION_STRENGTH_CHECK_EXCLUDE_PIRATES && 
493//                                      fleet.getFaction().getId().equals(Factions.PIRATES)) {
494//                              continue;
495//                      }
496                        
497                        strength += fleet.getEffectiveStrength();
498                        
499                        seenFleets.add(fleet);
500                }
501                
502                for (RouteData route : RouteManager.getInstance().getRoutesInLocation(system)) {
503                        if (route.getActiveFleet() != null && seenFleets.contains(route.getActiveFleet())) continue;
504                
505                        OptionalFleetData data = route.getExtra();
506                        if (data == null) continue;
507                        if (route.getFactionId() == null) continue;
508                        if (!faction.getId().equals(route.getFactionId())) continue;
509                        
510//                      if (EconomyFleetRouteManager.FACTION_STRENGTH_CHECK_EXCLUDE_PIRATES && 
511//                                      route.getFactionId().equals(Factions.PIRATES)) {
512//                              continue;
513//                      }
514                        
515                        strength += data.getStrengthModifiedByDamage();
516                }
517                
518                return strength;
519        }
520        
521        
522        public static float getStationStrength(FactionAPI faction, StarSystemAPI system, SectorEntityToken from) {
523                float strength = 0f;
524                
525                for (CampaignFleetAPI fleet : system.getFleets()) {
526                        if (!fleet.isStationMode()) continue;
527                        if (fleet.getFaction() != faction) continue;
528                        
529                        float maxDist = Misc.getBattleJoinRange() * 3f;
530                        
531                        float dist = Misc.getDistance(from, fleet);
532                        if (dist < maxDist) {
533                                strength += fleet.getEffectiveStrength();
534                        }
535                }
536                
537                return strength;
538        }
539
540        public TimeoutTracker<String> getTimeouts() {
541                return timeouts;
542        }
543        
544        
545        public static void removeFightOrdersFor(SectorEntityToken target, FactionAPI faction) {
546                for (EveryFrameScript s : target.getContainingLocation().getScripts()) {
547                        if (s instanceof MilitaryResponseScript) {
548                                MilitaryResponseScript script = (MilitaryResponseScript) s;
549                                if (script.getParams() != null && script.getParams().target == target &&
550                                                script.getParams().faction == faction) {
551                                        script.forceDone();
552                                }
553                        }
554                }
555        }
556        
557        public static void setNoFightingForObjective(SectorEntityToken objective, FactionAPI faction, float timeout) {
558                removeFightOrdersFor(objective, faction);
559                if (timeout > 0) {
560                        WarSimScript wss = getInstance();
561                        String id = wss.getControlTimeoutId(objective, faction);
562                        wss.timeouts.add(id, timeout);
563                }
564        }
565        
566        public static void removeNoFightingTimeoutForObjective(SectorEntityToken objective, FactionAPI faction) {
567                WarSimScript wss = getInstance();
568                String id = wss.getControlTimeoutId(objective, faction);
569                wss.timeouts.remove(id);
570        }
571        
572        public static boolean isAlreadyFightingFor(SectorEntityToken objective, FactionAPI faction) {
573                for (EveryFrameScript s : objective.getContainingLocation().getScripts()) {
574                        if (s instanceof MilitaryResponseScript) {
575                                MilitaryResponseScript script = (MilitaryResponseScript) s;
576                                if (script.getParams() != null && script.getParams().target == objective &&
577                                                script.getParams().faction == faction) {
578                                        return true;
579                                }
580                        }
581                }
582                return false;
583                
584        }
585        
586        public static LocationDanger getDangerFor(FactionAPI faction, StarSystemAPI system) {
587                if (system == null) return LocationDanger.NONE;
588                return getDangerFor(getFactionStrength(faction, system), getEnemyStrength(faction, system));
589        }
590        public static LocationDanger getDangerFor(String factionId, StarSystemAPI system) {
591                if (system == null) return LocationDanger.NONE;
592                return getDangerFor(getFactionStrength(factionId, system), getEnemyStrength(factionId, system));
593        }
594        public static LocationDanger getDangerFor(float factionStrength, float enemyStrength) {
595                if (enemyStrength < 100) return LocationDanger.NONE;
596                
597                float f = enemyStrength / Math.max(1f, factionStrength + enemyStrength);
598                for (LocationDanger level : LocationDanger.vals) {
599                        float test = level.enemyStrengthFraction + (level.next().enemyStrengthFraction - level.enemyStrengthFraction) * 0.5f;
600                        if (level == LocationDanger.NONE) test = LocationDanger.NONE.enemyStrengthFraction;
601                        if (test >= f) {
602                                return level;
603                        }
604                }
605                return LocationDanger.EXTREME;
606        }
607}
608
609
610
611
612
613