001package com.fs.starfarer.api.impl.campaign.intel.events;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.Comparator;
006import java.util.Iterator;
007import java.util.LinkedHashMap;
008import java.util.List;
009import java.util.Map;
010import java.util.Set;
011
012import java.awt.Color;
013
014import com.fs.starfarer.api.Global;
015import com.fs.starfarer.api.campaign.BattleAPI;
016import com.fs.starfarer.api.campaign.CampaignEventListener.FleetDespawnReason;
017import com.fs.starfarer.api.campaign.CampaignFleetAPI;
018import com.fs.starfarer.api.campaign.StarSystemAPI;
019import com.fs.starfarer.api.campaign.StoryPointActionDelegate;
020import com.fs.starfarer.api.campaign.econ.EconomyAPI.EconomyUpdateListener;
021import com.fs.starfarer.api.campaign.econ.MarketAPI;
022import com.fs.starfarer.api.campaign.listeners.FleetEventListener;
023import com.fs.starfarer.api.campaign.listeners.ListenerUtil;
024import com.fs.starfarer.api.combat.MutableStatWithTempMods;
025import com.fs.starfarer.api.fleet.FleetMemberAPI;
026import com.fs.starfarer.api.impl.campaign.command.WarSimScript.LocationDanger;
027import com.fs.starfarer.api.impl.campaign.ids.Conditions;
028import com.fs.starfarer.api.impl.campaign.ids.Factions;
029import com.fs.starfarer.api.impl.campaign.ids.Sounds;
030import com.fs.starfarer.api.impl.campaign.ids.Tags;
031import com.fs.starfarer.api.impl.campaign.intel.bases.LuddicPathBaseIntel;
032import com.fs.starfarer.api.impl.campaign.intel.bases.PirateBaseIntel;
033import com.fs.starfarer.api.impl.campaign.rulecmd.HA_CMD;
034import com.fs.starfarer.api.impl.campaign.rulecmd.SetStoryOption.BaseOptionStoryPointActionDelegate;
035import com.fs.starfarer.api.impl.campaign.rulecmd.SetStoryOption.StoryOptionParams;
036import com.fs.starfarer.api.ui.Alignment;
037import com.fs.starfarer.api.ui.ButtonAPI;
038import com.fs.starfarer.api.ui.IntelUIAPI;
039import com.fs.starfarer.api.ui.LabelAPI;
040import com.fs.starfarer.api.ui.SectorMapAPI;
041import com.fs.starfarer.api.ui.TooltipMakerAPI;
042import com.fs.starfarer.api.ui.TooltipMakerAPI.TooltipCreator;
043import com.fs.starfarer.api.ui.TooltipMakerAPI.TooltipLocation;
044import com.fs.starfarer.api.util.Misc;
045import com.fs.starfarer.api.util.WeightedRandomPicker;
046
047public class HostileActivityEventIntel extends BaseEventIntel implements EconomyUpdateListener, FleetEventListener {
048
049        public static enum Stage {
050                START,
051                MINOR_EVENT,
052                HA_EVENT,
053                
054                // unused, left in for save compatibility between 0.96a and 0.96.1a, can remove for 0.97a
055                @Deprecated HA_1,
056                @Deprecated HA_2,
057                @Deprecated INCREASED_DEFENSES,
058                @Deprecated HA_3,
059                @Deprecated HA_4,
060        }
061        public static String KEY = "$hae_ref";
062        
063        public static String BUTTON_ESCALATE = "button_escalate";
064        
065        public static float FP_PER_POINT = Global.getSettings().getFloat("HA_fleetPointsPerPoint");
066        
067        public static int MAX_PROGRESS = 600;
068        public static int ESCALATE_PROGRESS = 550;
069        
070        public static int RESET_MIN = 0;
071        public static int RESET_MAX = 400;
072        
073        
074        public static class HAERandomEventData {
075                public HostileActivityFactor factor;
076                public EventStageData stage;
077                public boolean isReset = false;
078                public Object custom;
079                public HAERandomEventData(HostileActivityFactor factor, EventStageData stage) {
080                        this.factor = factor;
081                        this.stage = stage;
082                }
083                
084        }
085        
086        public static class HAEFactorDangerData {
087                public HostileActivityFactor factor;
088                public float mag;
089        }
090        public static class HAEStarSystemDangerData {
091                public StarSystemAPI system;
092                public float maxMag;
093                public float totalMag;
094                public float sortMag;
095                public List<HAEFactorDangerData> factorData = new ArrayList<HAEFactorDangerData>();
096        }
097        
098        
099        public static HostileActivityEventIntel get() {
100                return (HostileActivityEventIntel) Global.getSector().getMemoryWithoutUpdate().get(KEY);
101        }
102        
103        protected int blowback;
104        protected Map<String, MutableStatWithTempMods> systemSpawnMults = new LinkedHashMap<String, MutableStatWithTempMods>();
105        
106        public HostileActivityEventIntel() {
107                super();
108                
109                //Global.getSector().getEconomy().addUpdateListener(this);
110                
111                Global.getSector().getMemoryWithoutUpdate().set(KEY, this);
112                
113                setup();
114                
115                // now that the event is fully constructed, add it and send notification
116                Global.getSector().getIntelManager().addIntel(this);
117        }
118        
119        protected void setup() {
120                
121                boolean minorCompleted = false;
122                EventStageData minor = getDataFor(Stage.MINOR_EVENT);
123                if (minor != null) minorCompleted = minor.wasEverReached;
124                
125                factors.clear();
126                stages.clear();
127                
128                setMaxProgress(MAX_PROGRESS);
129                addStage(Stage.START, 0);
130                addStage(Stage.MINOR_EVENT, 300, StageIconSize.MEDIUM);
131                addStage(Stage.HA_EVENT, 600, true, StageIconSize.LARGE);
132                
133                setRandomized(Stage.MINOR_EVENT, RandomizedStageType.BAD, 200, 250, false, false);
134                setRandomized(Stage.HA_EVENT, RandomizedStageType.BAD, 425, 500, false);
135                
136                minor = getDataFor(Stage.MINOR_EVENT);
137                if (minor != null) {
138                        minor.wasEverReached = minorCompleted;
139                }
140                
141                Global.getSector().getListenerManager().removeListenerOfClass(PirateHostileActivityFactor.class);
142                Global.getSector().getListenerManager().removeListenerOfClass(LuddicPathHostileActivityFactor.class);
143                Global.getSector().getListenerManager().removeListenerOfClass(PerseanLeagueHostileActivityFactor.class);
144                Global.getSector().getListenerManager().removeListenerOfClass(TriTachyonHostileActivityFactor.class);
145                Global.getSector().getListenerManager().removeListenerOfClass(LuddicChurchHostileActivityFactor.class);
146                Global.getSector().getListenerManager().removeListenerOfClass(SindrianDiktatHostileActivityFactor.class);
147                Global.getSector().getListenerManager().removeListenerOfClass(HegemonyHostileActivityFactor.class);
148                Global.getSector().getListenerManager().removeListenerOfClass(RemnantHostileActivityFactor.class);
149                
150                
151                addFactor(new HAColonyDefensesFactor());
152                addFactor(new HAShipsDestroyedFactorHint());
153                
154                addFactor(new HABlowbackFactor());
155                
156                PirateHostileActivityFactor pirate = new PirateHostileActivityFactor(this);
157                addActivity(pirate, new KantasProtectionPirateActivityCause2(this));
158                addActivity(pirate, new StandardPirateActivityCause2(this));
159                addActivity(pirate, new PirateBasePirateActivityCause2(this));
160                addActivity(pirate, new KantasWrathPirateActivityCause2(this));
161                
162                LuddicPathHostileActivityFactor path = new LuddicPathHostileActivityFactor(this);
163                addActivity(path, new LuddicPathAgreementHostileActivityCause2(this));
164                addActivity(path, new StandardLuddicPathActivityCause2(this));
165                
166                addActivity(new PerseanLeagueHostileActivityFactor(this), new StandardPerseanLeagueActivityCause(this));
167                addActivity(new TriTachyonHostileActivityFactor(this), new TriTachyonStandardActivityCause(this));
168                addActivity(new LuddicChurchHostileActivityFactor(this), new LuddicChurchStandardActivityCause(this));
169                addActivity(new SindrianDiktatHostileActivityFactor(this), new SindrianDiktatStandardActivityCause(this));
170                addActivity(new HegemonyHostileActivityFactor(this), new HegemonyAICoresActivityCause(this));
171                addActivity(new RemnantHostileActivityFactor(this), new RemnantNexusActivityCause(this));
172                
173                ListenerUtil.finishedAddingCrisisFactors(this);
174        }
175        
176        
177        protected Object readResolve() {
178                if (systemSpawnMults == null) {
179                        systemSpawnMults = new LinkedHashMap<String, MutableStatWithTempMods>();
180                }
181                return this;
182        }
183        
184        public void redoSetupIfNeeded() {
185                if (getDataFor(Stage.INCREASED_DEFENSES) != null || getMaxProgress() == 500) {// || Global.getSettings().isDevMode()) {
186                        setup();
187                }
188        }
189        
190        
191        @Override
192        protected void notifyEnding() {
193                super.notifyEnding();
194                Global.getSector().getEconomy().removeUpdateListener(this);
195                cleanUpHostileActivityConditions();
196        }
197
198        @Override
199        protected void notifyEnded() {
200                super.notifyEnded();
201                Global.getSector().getMemoryWithoutUpdate().unset(KEY);
202        }
203        
204        
205        protected void addBulletPoints(TooltipMakerAPI info, ListInfoMode mode, boolean isUpdate, 
206                                                                        Color tc, float initPad) {
207
208                if (addEventFactorBulletPoints(info, mode, isUpdate, tc, initPad)) {
209                        return;
210                }
211                
212                if (isUpdate && getListInfoParam() instanceof HAERandomEventData) {
213                        HAERandomEventData data = (HAERandomEventData) getListInfoParam();
214                        if (data.isReset) {
215                                data.factor.addBulletPointForEventReset(this, data.stage, info, mode, isUpdate, tc, initPad);
216                        } else {
217                                data.factor.addBulletPointForEvent(this, data.stage, info, mode, isUpdate, tc, initPad);
218                        }
219                        return;
220                }
221                
222                for (EventStageData stage : stages) {
223                        if (stage.rollData instanceof HAERandomEventData) {
224                                HAERandomEventData data = (HAERandomEventData) stage.rollData;
225                                data.factor.addBulletPointForEvent(this, stage, info, mode, isUpdate, tc, initPad);
226                                return;
227                        }
228                }
229                
230//              EventStageData esd = getLastActiveStage(false);
231//              if (esd != null && EnumSet.of(Stage.START, Stage.HA_1, Stage.HA_2, Stage.HA_3, Stage.HA_4).contains(esd.id)) {
232//                      Pair<String, Color> p = getImpactDisplayData((Stage) esd.id);
233//                      String impact = p.one;
234//                      Color impactColor = p.two;
235////                    if (p.one.equals("extreme")) {
236////                            System.out.println("wefwefwe");
237////                    }
238//                      info.addPara("Colony impact: %s", initPad, tc, impactColor, impact);
239//                      return;
240//              }
241                
242                //super.addBulletPoints(info, mode, isUpdate, tc, initPad);
243        }
244        
245        public HAERandomEventData getRollDataForEvent() {
246                EventStageData stage = getDataFor(Stage.HA_EVENT);
247                if (stage == null) return null;;
248                
249                if (stage.rollData instanceof HAERandomEventData) {
250                        HAERandomEventData data = (HAERandomEventData) stage.rollData;
251                        return data; 
252                }
253                return null;
254        }
255
256        @Override
257        public void addStageDescriptionText(TooltipMakerAPI info, float width, Object stageId) {
258                float opad = 10f;
259                float small = 0f;
260                Color h = Misc.getHighlightColor();
261                
262                //setProgress(0);
263                //setProgress(210);
264                //setProgress(600);
265                //setProgress(899);
266//              setProgress(424);
267//              setProgress(480);
268//              setProgress(230);
269//              random = new Random();
270//              setProgress(260);
271//              random = new Random();
272//              setProgress(499);
273                
274                List<HAEStarSystemDangerData> systemData = computePlayerSystemDangerData();
275                
276                EventStageData stage = getDataFor(stageId);
277                if (stage == null) return;
278                
279                if (stage.rollData instanceof HAERandomEventData) {
280                        HAERandomEventData data = (HAERandomEventData) stage.rollData;
281                        data.factor.addStageDescriptionForEvent(this, stage, info);
282                        return;
283                }
284                
285                if (isStageActiveAndLast(stageId)) {
286                        if (stageId == Stage.START) {
287                                float delta = getMonthlyProgress();
288                                
289                                if (delta <= 0) {
290                                        info.addPara("Low-level pirate activity continues, but aside from that, there are "
291                                                        + "no major crises on the horizon.", small);
292                                } else {
293                                        info.addPara("A crisis is virtually inevitable at some point, "
294                                                        + "and hostile fleets continually probe your defenses, but where there is "
295                                                        + "danger, there is often opportunity.", small);
296                                }
297//                              info.addPara("A crisis is virtually inevitable at some point, "
298//                                              + "and hostile fleets continually probe your defenses, but where there is "
299//                                              + "danger, there is often opportunity. A crisis may be averted or delayed by defeating "
300//                                              + "hostile fleets and taking other actions to address the various contributing factors, "
301//                                              + "but another crisis will always be just beyond the horizon.", small);
302                        }
303                        
304                        float systemW = 230f;
305                        float threatW = 300f;
306                        info.beginTable(getFactionForUIColors(), 20f, 
307                                        "Star system", systemW,
308                                        "Danger", 100f,
309                                        "Primary threats", threatW
310                                        );
311                        info.makeTableItemsClickable();
312                        
313                        int maxSystemsToList = 4;
314                        int numListed = 0;
315                        info.addTableHeaderTooltip(0, "Star system with hostile activity, and the name (or number) of your colonies found there.\n\nUp to four of the hardest-hit systems are listed here.");
316                        info.addTableHeaderTooltip(1, "Danger level of the stronger fleets likely to be found in the system. "
317                                        + "Approximate, there may be exceptions. Does not include hostile fleets that may be present there for other reasons.");
318                        info.addTableHeaderTooltip(2, "The most dangerous types of threats likely to be found in the system. "
319                                        + "Not comprehensive, and does not include hostile fleets that may be present there for other reasons.");
320                        
321                        //List<HAEStarSystemDangerData> systemData = computePlayerSystemDangerData();
322
323                        for (final HAEStarSystemDangerData sys : systemData) {
324                                if (sys.sortMag <= 0) continue;
325                                
326                                float mag = sys.sortMag;
327                                String danger = getDangerString(mag);
328                                
329                                int maxThreats = 3;
330                                int count = 0;
331                                List<String> threats = new ArrayList<String>();
332                                List<Color> colors = new ArrayList<Color>();
333                                for (HAEFactorDangerData data : sys.factorData) {
334                                        if (data.mag <= 0) continue;
335                                        threats.add(data.factor.getNameForThreatList(count == 0));
336                                        colors.add(data.factor.getNameColorForThreatList());
337                                        count++;
338                                        if (count >= maxThreats) {
339                                                break;
340                                        }
341                                }
342                                String threatStr = Misc.getJoined("", threats);
343
344                                LabelAPI label = info.createLabel(threatStr, Misc.getTextColor(), threatW);
345                                label.setHighlightColors(colors.toArray(new Color[0]));
346                                label.setHighlight(threats.toArray(new String[0]));
347                                
348                                String systemName = sys.system.getNameWithNoType();
349                                //String systemName = sys.system.getNameWithLowercaseTypeShort();
350                                List<MarketAPI> colonies = Misc.getMarketsInLocation(sys.system, Factions.PLAYER);
351                                String colStr = "";
352                                if (colonies.size() == 1) {
353                                        colStr = colonies.get(0).getName();
354                                } else {
355                                        colStr = "" + colonies.size() + " colonies";
356                                }
357                                systemName += " - " + colStr + "";
358                                
359                                info.addRowWithGlow(Alignment.LMID, Misc.getBasePlayerColor(), systemName,
360                                                                        Alignment.MID, getDangerColor(mag), danger,
361                                                                        Alignment.MID, null, label);
362                                info.addTooltipToAddedRow(new BaseFactorTooltip() {
363                                        @Override
364                                        public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) {
365                                                float w = tooltip.getWidthSoFar();
366                                                float h = Math.round(w / 1.6f);
367                                                tooltip.addSectorMap(w, h, sys.system, 0f);
368                                                tooltip.addPara("Click to open map", Misc.getGrayColor(), 5f);
369                                        }
370                                        
371                                }, TooltipLocation.LEFT, false);
372                                
373                                info.setIdForAddedRow(sys);
374                                
375                                numListed++;
376                                if (numListed >= maxSystemsToList) {
377                                        break;
378                                }
379                        }
380                        info.addTable("None", -1, opad);
381                        info.addSpacer(3f);
382                }
383        }
384        
385        
386        
387        @Override
388        public void afterStageDescriptions(TooltipMakerAPI main) {
389                int progress = getProgress();
390                if (progress < ESCALATE_PROGRESS) {
391                        float width = getBarWidth();
392                        Color color = Misc.getStoryOptionColor();
393                        Color dark = Misc.getStoryDarkColor();
394                        float bw = 300f;
395                        ButtonAPI button = addGenericButton(main, bw, color, dark, "Escalate crisis", BUTTON_ESCALATE);
396                        float inset = width - bw;
397                        //inset = 0f;
398                        button.getPosition().setXAlignOffset(inset);
399                        main.addSpacer(0f).getPosition().setXAlignOffset(-inset);
400                        if (progress >= ESCALATE_PROGRESS) {
401                                button.setEnabled(false);
402                                main.addTooltipTo(new TooltipCreator() {
403                                        @Override
404                                        public boolean isTooltipExpandable(Object tooltipParam) {
405                                                return false;
406                                        }
407                                        @Override
408                                        public float getTooltipWidth(Object tooltipParam) {
409                                                return 450;
410                                        }
411                                        @Override
412                                        public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) {
413                                                tooltip.addPara("Only available when event progress is below %s points.", 0f,
414                                                                Misc.getHighlightColor(), "" + (int) ESCALATE_PROGRESS);
415                                        }
416                                }, button, TooltipLocation.BELOW);
417                        }
418                }
419        }
420
421        public void tableRowClicked(IntelUIAPI ui, TableRowClickData data) {
422                if (data.rowId instanceof HAEStarSystemDangerData) {
423                        HAEStarSystemDangerData d = (HAEStarSystemDangerData) data.rowId;
424                        List<MarketAPI> m = Misc.getMarketsInLocation(d.system, Factions.PLAYER);
425                        if (m.size() == 1) {
426                                ui.showOnMap(m.get(0).getPrimaryEntity());
427                        } else {
428                                ui.showOnMap(d.system.getHyperspaceAnchor());
429                        }
430                }
431        }
432        
433        
434        public TooltipCreator getStageTooltipImpl(Object stageId) {
435                final EventStageData esd = getDataFor(stageId);
436                
437                if (esd != null && esd.rollData instanceof HAERandomEventData) {
438                        HAERandomEventData data = (HAERandomEventData) esd.rollData;
439                        return data.factor.getStageTooltipImpl(this, esd);
440                }
441
442                return null;
443        }
444
445
446
447        @Override
448        public String getIcon() {
449                return Global.getSettings().getSpriteName("events", "hostile_activity");
450        }
451
452        
453
454        @Override
455        protected String getStageIcon(Object stageId) {
456                EventStageData esd = getDataFor(stageId);
457                if (esd != null && esd.id == Stage.HA_EVENT && esd.rollData != null && RANDOM_EVENT_NONE.equals(esd.rollData)) {
458                        return Global.getSettings().getSpriteName("events", "stage_unknown_neutral");
459                }
460                return super.getStageIcon(stageId);
461        }
462        
463        @Override
464        public TooltipCreator getStageTooltip(Object stageId) {
465                final EventStageData esd = getDataFor(stageId);
466                if (esd != null && esd.id == Stage.HA_EVENT && esd.rollData != null && RANDOM_EVENT_NONE.equals(esd.rollData)) {
467                        return new TooltipCreator() {
468                                public boolean isTooltipExpandable(Object tooltipParam) {
469                                        return false;
470                                }
471                                public float getTooltipWidth(Object tooltipParam) {
472                                        return BaseEventFactor.TOOLTIP_WIDTH;
473                                }
474                                
475                                public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) {
476                                        Color h = Misc.getHighlightColor();
477                                        tooltip.addPara("There's no crisis on the horizon right now. When this stage is reached, "
478                                                        + "event progress will be reset to a lower value.",
479                                                        0f);
480                                }
481                        };
482                }
483                return super.getStageTooltip(stageId);
484        }
485        
486
487        protected String getStageIconImpl(Object stageId) {
488                EventStageData esd = getDataFor(stageId);
489                if (esd == null) return null;
490                
491//              if (esd.id == Stage.MINOR_EVENT) {
492//                      System.out.println("ewfwfew");
493//              }
494                
495                //setProgress(48);
496                //setProgress(74);
497                
498                if (esd.rollData instanceof HAERandomEventData) {
499                        HAERandomEventData data = (HAERandomEventData) esd.rollData;
500                        return data.factor.getEventStageIcon(this, esd);
501                }
502//              if (esd.id == Stage.HA_EVENT) {
503//                      System.out.println("wefwefwe");
504//              }
505                //if (stageId == Stage.START) return null;
506        
507                if (esd.id == Stage.START) {
508                        return Global.getSettings().getSpriteName("events", "hostile_activity_" + ((Stage)esd.id).name());
509                }
510                // should not happen - the above cases should handle all possibilities - but just in case
511                return Global.getSettings().getSpriteName("events", "hostile_activity");
512        }
513        
514        
515        @Override
516        public Color getBarColor() {
517                Color color = Misc.getNegativeHighlightColor();
518                //color = Misc.getBasePlayerColor();
519                color = Misc.interpolateColor(color, Color.black, 0.25f);
520                return color;
521        }
522        
523        @Override
524        public Color getBarProgressIndicatorColor() {
525                return super.getBarProgressIndicatorColor();
526        }
527
528
529        @Override
530        protected int getStageImportance(Object stageId) {
531//              if (stageId == Stage.HA_EVENT) {
532//                      return 1;
533//              }
534//              if (stageId == Stage.MINOR_EVENT) {
535//                      return 1;
536//              }
537                return super.getStageImportance(stageId);
538        }
539
540
541
542        @Override
543        protected String getName() {
544                //return "Hostile Activity";
545                return "Colony Crises";
546        }
547        
548
549        public boolean isEventProgressANegativeThingForThePlayer() {
550                return true;
551        }
552        
553        
554        public void addActivity(BaseHostileActivityFactor factor, HostileActivityCause2 cause) {
555                BaseHostileActivityFactor curr = getActivityOfClass(factor.getClass());
556                if (curr == null) {
557                        addFactor(factor);
558                        curr = factor;
559                }
560                curr.addCause(cause);
561        }
562        
563
564        public void removeActivityCause(Class activityClass, Class causeClass) {
565                BaseHostileActivityFactor curr = getActivityOfClass(activityClass);
566                if (curr == null) return;
567                
568                HostileActivityCause2 cause = curr.getCauseOfClass(causeClass);
569                if (cause == null) return;
570                
571                curr.getCauses().remove(cause);
572                
573                if (curr.getCauses().isEmpty()) {
574                        removeActivity(curr);
575                }
576        }
577        
578        public HostileActivityCause2 getActivityCause(Class activityClass, Class causeClass) {
579                BaseHostileActivityFactor curr = getActivityOfClass(activityClass);
580                if (curr == null) return null;
581                
582                HostileActivityCause2 cause = curr.getCauseOfClass(causeClass);
583                if (cause == null) return null;
584                
585                return cause;
586        }
587        
588        
589        public void removeActivity(BaseHostileActivityFactor plugin) {
590                factors.remove(plugin);
591        }
592        public void removeActivityOfClass(Class c) {
593                Iterator<EventFactor> iter = factors.iterator();
594                while (iter.hasNext()) {
595                        EventFactor curr = iter.next();
596                        if (curr.getClass() == c) {
597                                iter.remove();
598                        }
599                }
600        }
601        
602        public BaseHostileActivityFactor getActivityOfClass(Class c) {
603                Iterator<EventFactor> iter = factors.iterator();
604                while (iter.hasNext()) {
605                        EventFactor curr = iter.next();
606                        if (curr.getClass() == c) {
607                                return (BaseHostileActivityFactor) curr;
608                        }
609                }
610                return null;
611        }
612        
613        public List<HAEStarSystemDangerData> computePlayerSystemDangerData() {
614                List<HAEStarSystemDangerData> systemData = new ArrayList<HAEStarSystemDangerData>();
615                for (StarSystemAPI system : Misc.getPlayerSystems(false)) {
616                        HAEStarSystemDangerData data = computeDangerData(system);
617                        systemData.add(data);
618                }
619                Collections.sort(systemData, new Comparator<HAEStarSystemDangerData>() {
620                        public int compare(HAEStarSystemDangerData o1, HAEStarSystemDangerData o2) {
621                                int result = (int) Math.signum(o2.sortMag - o1.sortMag);
622                                if (result == 0) {
623                                        result = (int) Math.signum(o2.totalMag - o1.totalMag);
624                                }
625                                if (result == 0) {
626                                        result = (int) Math.signum(o2.system.getId().hashCode() - o1.system.getId().hashCode());
627                                }
628                                return result;
629                        }
630                });
631                return systemData;
632        }
633        
634        public HAEStarSystemDangerData computeDangerData(StarSystemAPI system) {
635                HAEStarSystemDangerData data = new HAEStarSystemDangerData();
636                data.system = system;
637                
638                float maxMag = 0f;
639                float total = 0f;
640                for (EventFactor factor : factors) {
641                        if (factor instanceof BaseHostileActivityFactor) {
642                                HAEFactorDangerData curr = new HAEFactorDangerData();
643                                curr.factor = (HostileActivityFactor) factor;
644                                curr.mag = ((BaseHostileActivityFactor) factor).getEffectMagnitude(system);
645                                data.factorData.add(curr);
646                                maxMag = Math.max(maxMag, curr.mag);
647                                total += curr.mag;
648                        }
649                }
650                data.maxMag = maxMag;
651                data.totalMag = total;
652                data.sortMag = data.maxMag * 0.75f + data.totalMag * 0.25f;
653                
654                Collections.sort(data.factorData, new Comparator<HAEFactorDangerData>() {
655                        public int compare(HAEFactorDangerData o1, HAEFactorDangerData o2) {
656                                return (int) Math.signum(o2.mag - o1.mag);
657                        }
658                });
659                
660                return data;
661        }
662        
663        public LocationDanger getDanger(float mag) {
664                if (mag <= 0f) return LocationDanger.NONE;
665                if (mag < 0.25f) return LocationDanger.MINIMAL;
666                if (mag < 0.5f) return LocationDanger.LOW;
667                if (mag < 0.75f) return LocationDanger.MEDIUM;
668                if (mag < 1f) return LocationDanger.HIGH;
669                return LocationDanger.EXTREME;
670        }
671        
672        public String getDangerString(float mag) {
673                return getDangerString(getDanger(mag));
674        }
675        public String getDangerString(LocationDanger d) {
676                switch (d) {
677                case EXTREME: return "Extreme";
678                case HIGH: return "High";
679                case MEDIUM: return "Medium";
680                case LOW: return "Low";
681                case MINIMAL: return "Minimal";
682                case NONE: return "None";
683                }
684                return "Unknown";
685        }
686        
687        public Color getDangerColor(float mag) {
688                LocationDanger d = getDanger(mag);
689                if (d == LocationDanger.NONE || d == LocationDanger.MINIMAL) {
690                        return Misc.getPositiveHighlightColor();
691                }
692                if (d == LocationDanger.EXTREME || d == LocationDanger.HIGH) {
693                        return Misc.getNegativeHighlightColor();
694                }
695                return Misc.getHighlightColor();
696        }
697        
698        public float getVeryApproximateFPStrength(StarSystemAPI system) {
699                float mag = getTotalActivityMagnitude(system, true);
700                //mag *= getProgressFraction();
701                mag *= 0.2f + getMarketPresenceFactor(system) * 0.8f;
702                return mag * 1000f;
703        }
704        
705        
706        /**
707         * From 0 (at one size-3 market) to 1 (maxSize + count >= 7). Also capped based on largest market.
708         * @param system
709         * @return
710         */
711        public float getMarketPresenceFactor(StarSystemAPI system) {
712                float maxSize = 0;
713                float count = 0;
714                for (MarketAPI market : Misc.getMarketsInLocation(system, Factions.PLAYER)) {
715                        maxSize = Math.max(market.getSize(), maxSize);
716                        count++;
717                }
718                
719                float f = (maxSize - 3f + count - 1f) / 3f;
720                
721                float cap = 0.35f;
722                if (maxSize <= 4f) cap = 0.55f;
723                else if (maxSize <= 5f) cap = 0.75f;
724                else cap = 1f;
725                
726                if (f < 0f) f = 0f;
727                if (f > cap) f = cap;
728                
729                return f;
730        }
731        
732        public float getTotalActivityMagnitude(StarSystemAPI system) {
733                return getTotalActivityMagnitude(system, true);
734        }
735        public float getTotalActivityMagnitude(StarSystemAPI system, boolean capped) {
736                //if (true) return 0.1f;
737                float total = 0f;
738                for (EventFactor factor : factors) {
739                        if (factor instanceof BaseHostileActivityFactor) {
740                                total += ((BaseHostileActivityFactor) factor).getEffectMagnitude(system);
741                        }
742                }
743                
744                if (capped && total > 1f) total = 1f;
745                
746                total = Math.round(total * 100f) / 100f;
747                
748                return total;
749        }
750        
751        @Override
752        protected void advanceImpl(float amount) {
753                super.advanceImpl(amount);
754                //blowback = 55;
755                if (systemSpawnMults != null) {
756                        float days = Misc.getDays(amount);
757                        for (MutableStatWithTempMods stat : systemSpawnMults.values()) {
758                                stat.advance(days);
759                        }
760                }
761        }
762
763        public MutableStatWithTempMods getNumFleetsStat(StarSystemAPI system) {
764                if (system == null) {
765                        return new MutableStatWithTempMods(1f);
766                }
767                if (systemSpawnMults == null) {
768                        systemSpawnMults = new LinkedHashMap<String, MutableStatWithTempMods>();
769                }
770                
771                String id = system.getId();
772                MutableStatWithTempMods stat = systemSpawnMults.get(id);
773                if (stat == null) {
774                        stat = new MutableStatWithTempMods(1f);
775                        systemSpawnMults.put(id, stat);
776                }
777                return stat;
778        }
779        
780        public float getNumFleetsMultiplier(StarSystemAPI system) {
781                return getNumFleetsStat(system).getModifiedValue();
782        }
783
784        public void economyUpdated() {
785                cleanUpHostileActivityConditions();
786        }
787        
788        
789        public void cleanUpHostileActivityConditions() {
790                for (MarketAPI curr : Misc.getPlayerMarkets(false)) {
791                        if (curr.hasCondition(Conditions.HOSTILE_ACTIVITY)) {
792                                curr.removeCondition(Conditions.HOSTILE_ACTIVITY);
793                        }
794                }
795        }
796        
797        public boolean isEconomyListenerExpired() {
798                return isEnding() || isEnded();
799        }
800        
801        public void commodityUpdated(String commodityId) {
802        }
803
804        
805        public void reportFleetDespawnedToListener(CampaignFleetAPI fleet, FleetDespawnReason reason, Object param) {
806        }
807
808        public void reportBattleOccurred(CampaignFleetAPI fleet, CampaignFleetAPI primaryWinner, BattleAPI battle) {
809                if (isEnded() || isEnding()) return;
810                
811                if (!battle.isPlayerInvolved()) return;
812                
813                if (Global.getSector().getCurrentLocation() instanceof StarSystemAPI &&
814                                battle.getPlayerSide().contains(primaryWinner)) {
815                        StarSystemAPI system = (StarSystemAPI) Global.getSector().getCurrentLocation(); 
816                        for (CampaignFleetAPI otherFleet : battle.getNonPlayerSideSnapshot()) {
817                                if (otherFleet.isStationMode() && otherFleet.getFleetData().getMembersListCopy().isEmpty()) {
818                                        {
819                                                PirateBaseIntel intel = PirateBaseIntel.getIntelFor(system);
820                                                if (intel != null && Misc.getStationFleet(intel.getMarket()) == otherFleet && 
821                                                                HA_CMD.baseInvolved(system, intel)) {
822                                                        int tier = intel.getTier().ordinal();
823                                                        if (tier < 0) tier = 0;
824                                                        if (tier > 4) tier = 4;
825                                                        int points = -1 * Global.getSettings().getIntFromArray("HA_pirateBase", tier);
826                                                        HAPirateBaseDestroyedFactor factor = new HAPirateBaseDestroyedFactor(points);
827                                                        addFactor(factor);
828                                                        return;
829                                                }
830                                        }
831                                        {
832                                                LuddicPathBaseIntel intel = LuddicPathBaseIntel.getIntelFor(system);
833                                                if (intel != null && Misc.getStationFleet(intel.getMarket()) == otherFleet) {
834                                                        float totalInterest = 0f;
835                                                        float activeCells = 0f;
836                                                        for (StarSystemAPI curr : Misc.getPlayerSystems(false)) {
837                                                                totalInterest += StandardLuddicPathActivityCause2.getPatherInterest(curr, 0f, 0f, 1f);
838                                                                activeCells += StandardLuddicPathActivityCause2.getPatherInterest(curr, 0f, 0f, 1f, true);
839                                                        }
840                                                        
841                                                        if (totalInterest > 0) {
842                                                                int flat = Global.getSettings().getInt("HA_patherBaseFlat");
843                                                                int perCell = Global.getSettings().getInt("HA_patherBasePerActiveCell");
844                                                                int max = Global.getSettings().getInt("HA_patherBaseMax");
845
846                                                                int points = -1 * Math.min(max, (flat + perCell * (int) Math.round(activeCells)));
847                                                                HAPatherBaseDestroyedFactor factor = new HAPatherBaseDestroyedFactor(points);
848                                                                addFactor(factor);
849                                                        }
850                                                        return;
851                                                }
852                                        }
853                                }
854                        }
855                        
856                }
857                
858                boolean nearAny = false;
859                for (StarSystemAPI system : Misc.getPlayerSystems(false)) {
860                        nearAny |= Misc.isNear(primaryWinner, system.getLocation());
861                        if (nearAny) break;
862                }
863                if (!nearAny) return;
864                
865                float fpDestroyed = 0;
866                CampaignFleetAPI first = null;
867                for (CampaignFleetAPI otherFleet : battle.getNonPlayerSideSnapshot()) {
868                        //if (!Global.getSector().getPlayerFaction().isHostileTo(otherFleet.getFaction())) continue;
869                        for (FleetMemberAPI loss : Misc.getSnapshotMembersLost(otherFleet)) {
870                                fpDestroyed += loss.getFleetPointCost();
871                                if (first == null) {
872                                        first = otherFleet;
873                                }
874                        }
875                }
876        
877                int points = computeProgressPoints(fpDestroyed);
878                if (points > 0) {
879                        //points = 700;
880                        HAShipsDestroyedFactor factor = new HAShipsDestroyedFactor(-1 * points);
881                        //sendUpdateIfPlayerHasIntel(factor, false); // addFactor now sends update
882                        addFactor(factor);
883                }
884        }
885        
886        public static int computeProgressPoints(float fleetPointsDestroyed) {
887                if (fleetPointsDestroyed <= 0) return 0;
888                
889                int points = Math.round(fleetPointsDestroyed / FP_PER_POINT);
890                if (points < 1) points = 1;
891                return points;
892        }
893
894        
895        @Override
896        protected void notifyStageReached(EventStageData stage) {
897                if (stage.rollData instanceof HAERandomEventData) {
898                        HAERandomEventData data = (HAERandomEventData) stage.rollData;
899                        boolean fired = data.factor.fireEvent(this, stage);
900                        stage.rollData = null;
901                        
902                        if (stage.id == Stage.HA_EVENT) {
903                                int resetProgress = getResetProgress(fired);
904                                setProgress(resetProgress);
905                        }
906                } else if (stage.id == Stage.HA_EVENT &&
907                                (stage.rollData == null || RANDOM_EVENT_NONE.equals(stage.rollData))) {
908                        stage.rollData = null;
909                        int resetProgress = getResetProgress(false);
910                        setProgress(resetProgress);
911                }
912        }
913        
914        protected int getResetProgress(boolean fired) {
915                if (!HABlowbackFactor.ENABLED) {
916                        blowback = 0;
917                }
918                int min = RESET_MIN;
919                if (!fired) min = RESET_MAX - 200;
920                
921                int resetAdd = random.nextInt(RESET_MAX - min + 1);
922                resetAdd = Math.min(resetAdd, random.nextInt(RESET_MAX - min + 1));
923                int resetProgress = min + resetAdd;
924                
925                int add = Math.min(blowback, (int)((RESET_MAX - resetProgress) * 0.5f));
926                if (add > 0) {
927                        resetProgress += add;
928                        blowback -= add;
929                }
930                return resetProgress;
931        }
932        
933        public void resetHA_EVENT() {
934                EventStageData stage = getDataFor(Stage.HA_EVENT);
935                //int resetProgress = stage.progressToRollAt - getRandom().nextInt(100);
936                int resetProgress = getResetProgress(false);
937                resetRandomizedStage(stage);
938                setProgress(resetProgress);
939        }
940        
941        public void resetHA_EVENTIfFromFactor(HostileActivityFactor factor) {
942                EventStageData stage = getDataFor(Stage.HA_EVENT);
943                if (stage != null && stage.rollData instanceof HAERandomEventData && 
944                                ((HAERandomEventData)stage.rollData).factor == factor) {
945                        resetHA_EVENT();
946                }
947        }
948
949        @Override
950        public void resetRandomizedStage(EventStageData stage) {
951                if (stage.rollData instanceof HAERandomEventData) {
952                        HAERandomEventData data = (HAERandomEventData) stage.rollData;
953                        data.isReset = true;
954                        data.factor.resetEvent(this, stage);
955                }
956                super.resetRandomizedStage(stage);
957        }
958
959        protected HostileActivityFactor prevMajorEventPick = null;
960        
961        @Override
962        public void rollRandomizedStage(EventStageData stage) {
963                if (stage.id == Stage.HA_EVENT || stage.id == Stage.MINOR_EVENT) {
964                        float total = 0f;
965                        for (EventFactor factor : factors) {
966                                if (factor instanceof BaseHostileActivityFactor) {
967                                        total += factor.getProgress(this);
968                                }
969                        }
970                        if (total < 1f) total = 1f;
971                        //System.out.println("Random: " + random.nextLong());
972                        WeightedRandomPicker<HostileActivityFactor> picker = new WeightedRandomPicker<HostileActivityFactor>(random);
973                        for (EventFactor factor : factors) {
974                                if (factor instanceof BaseHostileActivityFactor) {
975                                        BaseHostileActivityFactor curr = (BaseHostileActivityFactor) factor;
976                                        curr.setRandomizedStageSeed(random.nextLong()); // seed will be reused by .rollEvent()
977                                        float f = curr.getEventFrequency(this, stage);
978                                        float w = factor.getProgress(this) / total;
979                                        if (w > 0) {
980                                                w = 0.1f + 0.9f * w;
981                                        }
982                                        picker.add(curr, f * w);
983                                }
984                        }
985                        
986                        HostileActivityFactor pick = picker.pickAndRemove();
987                        if (stage.id == Stage.HA_EVENT) {
988                                if (prevMajorEventPick == pick && !picker.isEmpty()) {
989                                        pick = picker.pickAndRemove();
990                                }
991                                prevMajorEventPick = pick;
992                        }
993                        
994                        if (pick == null) return;
995                        
996                        stage.rollData = null;
997                        pick.rollEvent(this, stage);
998                }
999        }
1000        
1001        @Override
1002        public Set<String> getIntelTags(SectorMapAPI map) {
1003                Set<String> tags = super.getIntelTags(map);
1004                tags.add(Tags.INTEL_COLONIES);
1005                return tags;
1006        }
1007
1008        
1009        
1010        @Override
1011        public void addFactor(EventFactor factor) {
1012                if (factor.isOneTime()) {
1013                        int points = factor.getProgress(this);
1014                        if (points < 0) {
1015                                int p = Math.round(-1f * points * HABlowbackFactor.FRACTION);
1016                                p = Math.min(p, getProgress());
1017                                if (p > 0) {
1018                                        addBlowback(p);
1019                                }
1020                        }
1021                }
1022                super.addFactor(factor);
1023        }
1024        
1025        @Override
1026        public void reportEconomyMonthEnd() {
1027                super.reportEconomyMonthEnd();
1028                
1029                if (blowback > 0) {
1030                        int amt = Math.round(blowback * HABlowbackFactor.PER_MONTH);
1031                        
1032                        float mult = 1f;
1033                        for (EventFactor factor : factors) {
1034                                if (factor.isOneTime()) continue;
1035                                mult *= factor.getAllProgressMult(this);
1036                        }
1037                        
1038                        amt = Math.round(amt * mult);
1039                        
1040                        if (amt < 1) amt = 1;
1041                        blowback -= amt;
1042                        if (blowback < 0) blowback = 0;
1043                }
1044        }
1045
1046        public void addBlowback(int points) {
1047                if (!HABlowbackFactor.ENABLED) return;
1048                blowback += points;
1049        }
1050        public int getBlowback() {
1051                if (!HABlowbackFactor.ENABLED) {
1052                        blowback = 0;
1053                }
1054                return blowback;
1055        }
1056
1057        public void setBlowback(int blowback) {
1058                if (!HABlowbackFactor.ENABLED) return;
1059                this.blowback = blowback;
1060        }
1061        
1062        protected String getSoundForOtherUpdate(Object param) {
1063                if (param instanceof HAERandomEventData) {
1064                        HAERandomEventData data = (HAERandomEventData) param;
1065                        if (data.isReset) return null;
1066                        if (data.factor == null) return null;
1067                        return data.factor.getEventStageSound(data);
1068                }
1069                return null;
1070        }
1071
1072        
1073        @Override
1074        public int getMaxMonthlyProgress() {
1075                if (Misc.isEasy()) {
1076                        return Global.getSettings().getInt("ha_maxMonthlyProgressEasy");
1077                }
1078                return Global.getSettings().getInt("ha_maxMonthlyProgress");
1079        }
1080        
1081        public void storyActionConfirmed(Object buttonId, IntelUIAPI ui) {
1082                if (buttonId == BUTTON_ESCALATE) {
1083                        ui.recreateIntelUI();
1084                }
1085        }
1086        
1087        public StoryPointActionDelegate getButtonStoryPointActionDelegate(Object buttonId) {
1088                if (buttonId == BUTTON_ESCALATE) {
1089                        StoryOptionParams params = new StoryOptionParams(null, 1, "escalateCrisis", 
1090                                                                                        Sounds.STORY_POINT_SPEND_INDUSTRY, 
1091                                                                                        "Escalated colony crisis");
1092                        return new BaseOptionStoryPointActionDelegate(null, params) {
1093                                @Override
1094                                public void confirm() {
1095                                        setProgress(ESCALATE_PROGRESS);
1096                                }
1097                                
1098                                @Override
1099                                public String getTitle() {
1100                                        return null;
1101                                }
1102
1103                                @Override
1104                                public void createDescription(TooltipMakerAPI info) {
1105                                        info.setParaInsigniaLarge();
1106                                        info.addPara("Take certain actions to precipitate a crisis more quickly. Sets event progress "
1107                                                        + "to %s points.", -10f, Misc.getHighlightColor(), "" + (int) ESCALATE_PROGRESS);
1108                                        info.addSpacer(20f);
1109                                        super.createDescription(info);
1110                                }
1111                        };
1112                }
1113                return null;
1114        }
1115        
1116}
1117
1118
1119
1120
1121
1122
1123
1124