001package com.fs.starfarer.api.impl.campaign.rulecmd.salvage;
002
003import java.awt.Color;
004import java.util.ArrayList;
005import java.util.List;
006import java.util.Map;
007import java.util.Random;
008
009import org.lwjgl.input.Keyboard;
010import org.lwjgl.util.vector.Vector2f;
011
012import com.fs.starfarer.api.EveryFrameScript;
013import com.fs.starfarer.api.Global;
014import com.fs.starfarer.api.campaign.BattleAPI;
015import com.fs.starfarer.api.campaign.BattleAPI.BattleSide;
016import com.fs.starfarer.api.campaign.CampaignFleetAPI;
017import com.fs.starfarer.api.campaign.CargoAPI;
018import com.fs.starfarer.api.campaign.CoreInteractionListener;
019import com.fs.starfarer.api.campaign.FactionAPI;
020import com.fs.starfarer.api.campaign.GroundRaidTargetPickerDelegate;
021import com.fs.starfarer.api.campaign.InteractionDialogAPI;
022import com.fs.starfarer.api.campaign.InteractionDialogPlugin;
023import com.fs.starfarer.api.campaign.OptionPanelAPI;
024import com.fs.starfarer.api.campaign.RepLevel;
025import com.fs.starfarer.api.campaign.RuleBasedDialog;
026import com.fs.starfarer.api.campaign.SectorEntityToken;
027import com.fs.starfarer.api.campaign.TextPanelAPI;
028import com.fs.starfarer.api.campaign.ai.CampaignFleetAIAPI.ActionType;
029import com.fs.starfarer.api.campaign.econ.Industry;
030import com.fs.starfarer.api.campaign.econ.MarketAPI;
031import com.fs.starfarer.api.campaign.econ.MonthlyReport;
032import com.fs.starfarer.api.campaign.listeners.GroundRaidObjectivesListener.RaidResultData;
033import com.fs.starfarer.api.campaign.listeners.ListenerUtil;
034import com.fs.starfarer.api.campaign.rules.MemoryAPI;
035import com.fs.starfarer.api.characters.OfficerDataAPI;
036import com.fs.starfarer.api.characters.PersonAPI;
037import com.fs.starfarer.api.combat.BattleCreationContext;
038import com.fs.starfarer.api.combat.MutableStat;
039import com.fs.starfarer.api.combat.MutableStat.StatMod;
040import com.fs.starfarer.api.combat.StatBonus;
041import com.fs.starfarer.api.fleet.FleetMemberAPI;
042import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.CustomRepImpact;
043import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActionEnvelope;
044import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActions;
045import com.fs.starfarer.api.impl.campaign.DebugFlags;
046import com.fs.starfarer.api.impl.campaign.FleetEncounterContext;
047import com.fs.starfarer.api.impl.campaign.FleetInteractionDialogPluginImpl;
048import com.fs.starfarer.api.impl.campaign.FleetInteractionDialogPluginImpl.BaseFIDDelegate;
049import com.fs.starfarer.api.impl.campaign.FleetInteractionDialogPluginImpl.FIDConfig;
050import com.fs.starfarer.api.impl.campaign.MilitaryResponseScript;
051import com.fs.starfarer.api.impl.campaign.MilitaryResponseScript.MilitaryResponseParams;
052import com.fs.starfarer.api.impl.campaign.RuleBasedInteractionDialogPluginImpl;
053import com.fs.starfarer.api.impl.campaign.econ.RecentUnrest;
054import com.fs.starfarer.api.impl.campaign.econ.impl.PopulationAndInfrastructure;
055import com.fs.starfarer.api.impl.campaign.graid.DisruptIndustryRaidObjectivePluginImpl;
056import com.fs.starfarer.api.impl.campaign.graid.GroundRaidObjectivePlugin;
057import com.fs.starfarer.api.impl.campaign.ids.Commodities;
058import com.fs.starfarer.api.impl.campaign.ids.Conditions;
059import com.fs.starfarer.api.impl.campaign.ids.Factions;
060import com.fs.starfarer.api.impl.campaign.ids.Industries;
061import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
062import com.fs.starfarer.api.impl.campaign.ids.Sounds;
063import com.fs.starfarer.api.impl.campaign.ids.Stats;
064import com.fs.starfarer.api.impl.campaign.ids.Strings;
065import com.fs.starfarer.api.impl.campaign.ids.Tags;
066import com.fs.starfarer.api.impl.campaign.intel.BaseIntelPlugin;
067import com.fs.starfarer.api.impl.campaign.intel.deciv.DecivTracker;
068import com.fs.starfarer.api.impl.campaign.population.CoreImmigrationPluginImpl;
069import com.fs.starfarer.api.impl.campaign.procgen.StarSystemGenerator;
070import com.fs.starfarer.api.impl.campaign.rulecmd.AddRemoveCommodity;
071import com.fs.starfarer.api.impl.campaign.rulecmd.BaseCommandPlugin;
072import com.fs.starfarer.api.impl.campaign.rulecmd.FireAll;
073import com.fs.starfarer.api.impl.campaign.rulecmd.FireBest;
074import com.fs.starfarer.api.impl.campaign.rulecmd.SetStoryOption;
075import com.fs.starfarer.api.impl.campaign.rulecmd.SetStoryOption.BaseOptionStoryPointActionDelegate;
076import com.fs.starfarer.api.impl.campaign.rulecmd.SetStoryOption.StoryOptionParams;
077import com.fs.starfarer.api.impl.campaign.rulecmd.ShowDefaultVisual;
078import com.fs.starfarer.api.impl.campaign.shared.SharedData;
079import com.fs.starfarer.api.impl.campaign.terrain.HyperspaceTerrainPlugin;
080import com.fs.starfarer.api.ui.LabelAPI;
081import com.fs.starfarer.api.ui.TooltipMakerAPI;
082import com.fs.starfarer.api.ui.TooltipMakerAPI.StatModValueGetter;
083import com.fs.starfarer.api.util.Misc;
084import com.fs.starfarer.api.util.Misc.Token;
085
086/**
087 * 
088 */
089public class MarketCMD extends BaseCommandPlugin {
090
091        public static enum RaidType {
092                CUSTOM_ONLY,
093                VALUABLE,
094                DISRUPT,
095        }
096        
097        public static enum BombardType {
098                TACTICAL,
099                SATURATION,
100        }
101        
102        public static enum RaidDangerLevel {
103                NONE("None", "None", Misc.getPositiveHighlightColor(), 0f, 60f, 1),
104                MINIMAL("Minimal", "Minimal", Misc.getPositiveHighlightColor(), 0.02f, 50f, 1),
105                LOW("Low", "Light", Misc.getPositiveHighlightColor(), 0.04f, 40f, 2),
106                MEDIUM("Medium", "Moderate", Misc.getHighlightColor(), 0.08f, 30f, 3),
107                HIGH("High", "Heavy", Misc.getNegativeHighlightColor(), 0.16f, 20f, 5),
108                EXTREME("Extreme", "Extreme", Misc.getNegativeHighlightColor(), 0.32f, 10f, 7);
109                
110                private static RaidDangerLevel [] vals = values();
111                
112                public String name;
113                public String lossesName;
114                public Color color;
115                public float marineLossesMult;
116                public int marineTokens;
117                public float disruptionDays;
118                private RaidDangerLevel(String name, String lossesName, Color color, float marineLossesMult, float disruptionDays, int marineTokens) {
119                        this.name = name;
120                        this.lossesName = lossesName;
121                        this.color = color;
122                        this.marineLossesMult = marineLossesMult;
123                        this.disruptionDays = disruptionDays;
124                        this.marineTokens = marineTokens;
125                }
126                
127                public RaidDangerLevel next() {
128                        int index = this.ordinal() + 1;
129                        if (index >= vals.length) index = vals.length - 1;
130                        return vals[index];
131                }
132                public RaidDangerLevel prev() {
133                        int index = this.ordinal() - 1;
134                        if (index < 0) index = 0;
135                        return vals[index];
136                }
137        }
138        
139        public static class TempData {
140                //public boolean canSurpriseRaid;
141                //public boolean isSurpriseRaid;
142                public boolean canRaid;
143                public boolean canBombard;
144                
145                public int bombardCost;
146                
147                public int marinesLost;
148                
149                //public boolean canFail = false;
150                //public float failProb = 0f;
151                
152                public float raidMult;
153                
154                public float attackerStr;
155                public float defenderStr;
156                
157                public boolean nonMarket = false;
158                public boolean secret = false;
159                
160                public RaidType raidType = null;
161                public BombardType bombardType = null;
162                public CargoAPI raidLoot;
163                public int xpGained;
164                public Industry target = null;
165                public List<FactionAPI> willBecomeHostile = new ArrayList<FactionAPI>();
166                public List<Industry> bombardmentTargets = new ArrayList<Industry>();
167                public List<GroundRaidObjectivePlugin> objectives = new ArrayList<GroundRaidObjectivePlugin>();
168                public String contText;
169                public String raidGoBackTrigger;
170                public String raidContinueTrigger;
171        }
172        
173        public static int HOSTILE_ACTIONS_TIMEOUT_DAYS = 60;
174        public static int TACTICAL_BOMBARD_TIMEOUT_DAYS = 120;
175        public static int SATURATION_BOMBARD_TIMEOUT_DAYS = 365;
176        
177        public static int MIN_MARINE_TOKENS = 1;
178        public static float RE_PER_MARINE_TOKEN = 0.1f;
179        public static int MAX_MARINE_TOKENS = 10;
180        public static float LOSS_REDUCTION_PER_RESERVE_TOKEN = 0.05f;
181        public static float LOSS_INCREASE_PER_RAID = 0.5f;
182        public static float MAX_MARINE_LOSSES = 0.8f;
183
184        public static float MIN_RE_TO_REDUCE_MARINE_LOSSES = 0.5f;
185        public static float MAX_MARINE_LOSS_REDUCTION_MULT = 0.05f;
186        
187        // for causing deficit; higher value means less units need to be raided to cause same deficit   
188        public static float ECON_IMPACT_MULT = 1f;
189        
190        public static float QUANTITY_MULT_NORMAL = 1f; 
191        public static float QUANTITY_MULT_EXCESS = 2f; 
192        public static float QUANTITY_MULT_DEFICIT = -0.5f; 
193        public static float QUANTITY_MULT_OVERALL = 0.1f; 
194        
195        
196        public static String ENGAGE = "mktEngage";
197        
198        public static String RAID = "mktRaid";
199        public static String RAID_NON_MARKET = "mktRaidNonMarket";
200        //public static String RAID_SURPRISE = "mktRaidSurprise";
201        //public static String RAID_RARE = "mktRaidRare";
202        public static String RAID_VALUABLE = "mktRaidValuable";
203        public static String RAID_DISRUPT = "mktRaidDisrupt";
204        public static String RAID_GO_BACK = "mktRaidGoBack";
205        public static String RAID_CONFIRM_CONTINUE = "mktRaidConfirmContinue";
206        
207        public static String RAID_CONFIRM = "mktRaidConfirm";
208        public static String RAID_CONFIRM_STORY = "mktRaidConfirmStory";
209        public static String RAID_NEVER_MIND = "mktRaidNeverMind";
210        public static String RAID_RESULT = "mktRaidResult";
211        
212        public static String INVADE = "mktInvade";
213        public static String GO_BACK = "mktGoBack";
214        
215        public static String BOMBARD = "mktBombard";
216        public static String BOMBARD_TACTICAL = "mktBombardTactical";
217        public static String BOMBARD_SATURATION = "mktBombardSaturation";
218        public static String BOMBARD_CONFIRM = "mktBombardConfirm";
219        public static String BOMBARD_NEVERMIND = "mktBombardNeverMind";
220        public static String BOMBARD_RESULT = "mktBombardResult";
221        
222        public static String DEBT_RESULT_CONTINUE = "marketCmd_checkDebtContinue";
223        
224        
225        
226        
227        public static float DISRUPTION_THRESHOLD = 0.25f;
228        public static float VALUABLES_THRESHOLD = 0.05f;
229        
230        protected CampaignFleetAPI playerFleet;
231        protected SectorEntityToken entity;
232        protected FactionAPI playerFaction;
233        protected FactionAPI entityFaction;
234        protected TextPanelAPI text;
235        protected OptionPanelAPI options;
236        protected CargoAPI playerCargo;
237        protected MemoryAPI memory;
238        protected MarketAPI market;
239        protected InteractionDialogAPI dialog;
240        protected Map<String, MemoryAPI> memoryMap;
241        protected FactionAPI faction;
242
243        protected TempData temp = new TempData();
244        
245        public MarketCMD() {
246                //DebugFlags.MARKET_HOSTILITIES_DEBUG = false;
247        }
248        
249        protected void clearTemp() {
250                if (temp != null) {
251                        //temp.isSurpriseRaid = false;
252                        temp.raidType = null;
253                        temp.bombardType = null;
254                        temp.raidLoot = null;
255                        temp.target = null;
256                        temp.willBecomeHostile.clear();
257                        temp.bombardmentTargets.clear();
258                        temp.objectives.clear();
259                        temp.contText = null;
260                        temp.raidGoBackTrigger = null;
261                        temp.raidContinueTrigger = null;
262                        //temp.canFail = false;
263                        //temp.failProb = 0f;
264                }
265        }
266        
267        public MarketCMD(SectorEntityToken entity) {
268                init(entity);
269        }
270        
271        protected void init(SectorEntityToken entity) {
272                
273                memory = entity.getMemoryWithoutUpdate();
274                this.entity = entity;
275                playerFleet = Global.getSector().getPlayerFleet();
276                playerCargo = playerFleet.getCargo();
277                
278                playerFaction = Global.getSector().getPlayerFaction();
279                entityFaction = entity.getFaction();
280                
281                faction = entity.getFaction();
282                
283                market = entity.getMarket();
284                
285                //DebugFlags.MARKET_HOSTILITIES_DEBUG = false;
286                //market.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_PLAYER_HOSTILE_ACTIVITY_NEAR_MARKET, true, 0.1f);
287                
288                String key = "$MarketCMD_temp";
289                MemoryAPI mem = null;
290                if (market != null) {
291                        mem = market.getMemoryWithoutUpdate();
292                } else {
293                        mem = entity.getMemoryWithoutUpdate();
294                }
295                if (mem.contains(key)) {
296                        temp = (TempData) mem.get(key);
297                } else {
298                        mem.set(key, temp, 0f);
299                }
300        }
301
302        public boolean execute(String ruleId, InteractionDialogAPI dialog, List<Token> params, Map<String, MemoryAPI> memoryMap) {
303                this.dialog = dialog;
304                this.memoryMap = memoryMap;
305                
306                String command = params.get(0).getString(memoryMap);
307                if (command == null) return false;
308                
309                entity = dialog.getInteractionTarget();
310                init(entity);
311                
312                memory = getEntityMemory(memoryMap);
313                
314                text = dialog.getTextPanel();
315                options = dialog.getOptionPanel();
316                
317                if (command.equals("showDefenses")) {
318                        clearTemp();
319                        //new ShowDefaultVisual().execute(null, dialog, Misc.tokenize(""), memoryMap);
320                        showDefenses(true);
321                } else if (command.equals("goBackToDefenses")) {
322                        if (temp.nonMarket) {
323                                String trigger = temp.raidGoBackTrigger;
324                                if (trigger == null || trigger.isEmpty()) trigger = "PopulateOptions";
325                                clearTemp();
326                                FireAll.fire(null, dialog, memoryMap, trigger);
327                                return true;
328                        }
329                        clearTemp();
330                        //new ShowDefaultVisual().execute(null, dialog, Misc.tokenize(""), memoryMap);
331                        showDefenses(true);
332                        //dialog.getVisualPanel().finishFadeFast();
333                } else if (command.equals("engage")) {
334                        engage();
335                } else if (command.equals("raidMenu")) {
336//                      boolean surprise = "mktRaidSurprise".equals(memory.get("$option"));
337//                      temp.isSurpriseRaid = surprise;
338                        raidMenu();
339//              } else if (command.equals("raidRare")) {
340//                      raidRare();
341                } else if (command.equals("raidNonMarket")) {
342                        raidNonMarket();
343                } else if (command.equals("raidValuable")) {
344                        raidValuable();
345                } else if (command.equals("raidDisrupt")) {
346                        raidDisrupt();
347                } else if (command.equals("raidConfirm")) {
348                        raidConfirm(false);
349                } else if (command.equals("raidConfirmContinue")) {
350                        raidConfirmContinue();
351                } else if (command.equals("raidNeverMind")) {
352                        raidNeverMind();
353                } else if (command.equals("addContinueToRaidResultOption")) {
354                        addContinueOption(temp.contText);
355                } else if (command.equals("raidResult")) {
356                        raidResult();
357                } else if (command.equals("bombardMenu")) {
358                        bombardMenu();
359                } else if (command.equals("bombardTactical")) {
360                        bombardTactical();
361                } else if (command.equals("bombardSaturation")) {
362                        bombardSaturation();
363                } else if (command.equals("bombardConfirm")) {
364                        bombardConfirm();
365                } else if (command.equals("bombardNeverMind")) {
366                        bombardNeverMind();
367                } else if (command.equals("bombardResult")) {
368                        bombardResult();
369                } else if (command.equals("checkDebtEffect")) {
370                        return checkDebtEffect();
371                } else if (command.equals("applyDebtEffect")) {
372                        applyDebtEffect();
373                } else if (command.equals("checkMercsLeaving")) {
374                        return checkMercsLeaving();
375                } else if (command.equals("convinceMercToStay")) {
376                        convinceMercToStay();
377                } else if (command.equals("mercLeaves")) {
378                        mercLeaves();
379                } else if (command.equals("assistVolturnInsurgents"))
380                {
381                        MarketAPI volturn = Global.getSector().getEconomy().getMarket("volturn");
382                        RecentUnrest.get(volturn).add(2, "The Luddic insurgency acquired unusually heavy weaponry somehow");
383                }
384                
385                return true;
386        }
387
388        protected void showDefenses(boolean withText) {
389                CampaignFleetAPI primary = getInteractionTargetForFIDPI();
390                CampaignFleetAPI station = getStationFleet();
391                
392                boolean hasNonStation = false;
393                boolean hasOtherButInsignificant = true;
394                boolean hasStation = station != null;
395                boolean otherWantsToFight = false;
396                BattleAPI b = null;
397                FleetEncounterContext context = null;
398                FleetInteractionDialogPluginImpl plugin = null;
399                
400                boolean ongoingBattle = false;
401                
402                boolean playerOnDefenderSide = false;
403                boolean playerCanNotJoin = false;
404
405                String stationType = "station";
406                if (station != null) {
407                        FleetMemberAPI flagship = station.getFlagship();
408                        if (flagship != null && flagship.getVariant() != null) {
409                                String name = flagship.getVariant().getDesignation().toLowerCase();
410                                stationType = name;
411                        }
412                }
413                
414                StationState state = getStationState();
415                
416                if (market != null) {
417                        Global.getSector().getEconomy().tripleStep();
418                }
419                
420                if (primary == null) {
421                        if (state == StationState.NONE) {
422                                text.addPara("The colony has no orbital station or nearby fleets to defend it.");
423                        } else {
424                                printStationState();
425                                text.addPara("There are no nearby fleets to defend the colony.");
426                        }
427                } else {
428                        ongoingBattle = primary.getBattle() != null;
429
430                        CampaignFleetAPI pluginFleet = primary;
431                        if (ongoingBattle) {
432                                BattleSide playerSide = primary.getBattle().pickSide(playerFleet);
433                                CampaignFleetAPI other = primary.getBattle().getPrimary(primary.getBattle().getOtherSide(playerSide));
434                                if (other != null) {
435                                        pluginFleet = other;
436                                }
437                        }
438                        
439                        FIDConfig params = new FIDConfig();
440                        params.justShowFleets = true;
441                        params.showPullInText = withText;
442                        plugin = new FleetInteractionDialogPluginImpl(params);
443                        //dialog.setInteractionTarget(primary);
444                        dialog.setInteractionTarget(pluginFleet);
445                        plugin.init(dialog);
446//                      if (ongoingBattle) {
447//                              plugin.setPlayerFleet(primary.getBattle().getPlayerCombined());
448//                      }
449                        dialog.setInteractionTarget(entity);
450                        
451                        
452                        context = (FleetEncounterContext)plugin.getContext();
453                        b = context.getBattle();
454                        
455                        BattleSide playerSide = b.pickSide(playerFleet);
456                        if (playerSide != BattleSide.NO_JOIN) {
457                                if (b.getOtherSideCombined(playerSide).isEmpty()) {
458                                        playerSide = BattleSide.NO_JOIN;
459                                }
460                        }
461                        playerCanNotJoin = playerSide == BattleSide.NO_JOIN;
462                        if (!playerCanNotJoin) {
463                                playerOnDefenderSide = b.getSide(playerSide) == b.getSideFor(primary);
464                        }
465                        if (!ongoingBattle) {
466                                playerOnDefenderSide = false;
467                        }
468
469                        boolean otherHasStation = false;
470                        if (playerSide != BattleSide.NO_JOIN) {
471                                //for (CampaignFleetAPI fleet : b.getNonPlayerSide()) {
472                                if (station != null) {
473                                        for (CampaignFleetAPI fleet : b.getSideFor(station)) {
474                                                if (!fleet.isStationMode()) {
475                                                        hasNonStation = true;
476                                                        hasOtherButInsignificant &= Misc.isInsignificant(fleet);
477                                                }
478                                        }
479                                } else {
480                                        if (b.getNonPlayerSide() != null) {
481                                                for (CampaignFleetAPI fleet : b.getNonPlayerSide()) {
482                                                        if (!fleet.isStationMode()) {
483                                                                hasNonStation = true;
484                                                                hasOtherButInsignificant &= Misc.isInsignificant(fleet);
485                                                        }
486                                                }
487                                        } else {
488                                                hasNonStation = true;
489                                        }
490                                }
491                                
492                                for (CampaignFleetAPI fleet : b.getOtherSide(playerSide)) {
493                                        if (!fleet.isStationMode()) {
494                                                //hasNonStation = true;
495                                        } else {
496                                                otherHasStation = true;
497                                        }
498                                }
499                        }
500                        
501                        if (!hasNonStation) hasOtherButInsignificant = false;
502                        
503                        //otherWantsToFight = hasStation || plugin.otherFleetWantsToFight(true);
504                        
505                        // inaccurate because it doesn't include the station in the "wants to fight" calculation, but, this is tricky
506                        // and I don't want to break it right now
507                        otherWantsToFight = otherHasStation || plugin.otherFleetWantsToFight(true);
508                        
509                        if (withText) {
510                                if (hasStation) {
511                                        String name = "An orbital station";
512                                        if (station != null) {
513                                                FleetMemberAPI flagship = station.getFlagship();
514                                                if (flagship != null) {
515                                                        name = flagship.getVariant().getDesignation().toLowerCase();
516                                                        stationType = name;
517                                                        name = Misc.ucFirst(station.getFaction().getPersonNamePrefixAOrAn()) + " " + 
518                                                                        station.getFaction().getPersonNamePrefix() + " " + name;
519                                                }
520                                        }
521                                        text.addPara(name + " dominates the orbit and prevents any " +
522                                                                 "hostile action, aside from a quick raid, unless it is dealt with.");
523                                        
524                                        
525                                        if (hasNonStation) {
526                                                if (ongoingBattle) {
527                                                        text.addPara("There are defending ships present, but they are currently involved in a battle, "
528                                                                        + "and you could take advantage of the distraction to launch a raid.");
529                                                } else {
530                                                        if (hasOtherButInsignificant) {
531                                                                text.addPara("Defending ships are present, but not in sufficient strength " +
532                                                                                 "to want to give battle or prevent any hostile action you might take.");
533                                                        } else {
534                                                                text.addPara("The defending ships present are, with the support of the station, sufficient to prevent " +
535                                                                         "raiding as well.");
536                                                        }
537                                                }
538                                        }
539                                } else if (hasNonStation && otherWantsToFight) {
540                                        printStationState();
541                                        text.addPara("Defending ships are present in sufficient strength to prevent any hostile action " +
542                                        "until they are dealt with.");
543                                } else if (hasNonStation && !otherWantsToFight) {
544                                        printStationState();
545                                        text.addPara("Defending ships are present, but not in sufficient strength " +
546                                                                 "to want to give battle or prevent any hostile action you might take.");
547                                }
548                                
549                                plugin.printOngoingBattleInfo();
550                        }
551                }
552
553                if (!hasNonStation) hasOtherButInsignificant = false;
554                
555                options.clearOptions();
556                
557                String engageText = "Engage the defenders";
558                
559                if (playerCanNotJoin) {
560                        engageText = "Engage the defenders";
561                } else if (playerOnDefenderSide) {
562                        if (hasStation && hasNonStation) {
563                                engageText = "Aid the " + stationType + " and its defenders";
564                        } else if (hasStation) {
565                                engageText = "Aid the " + stationType + "";
566                        } else {
567                                engageText = "Aid the defenders";
568                        }
569                } else {
570                        if (ongoingBattle) {
571                                engageText = "Aid the attacking forces";
572                        } else {
573                                if (hasStation && hasNonStation) {
574                                        engageText = "Engage the " + stationType + " and its defenders";
575                                } else if (hasStation) {
576                                        engageText = "Engage the " + stationType + "";
577                                } else {
578                                        engageText = "Engage the defenders";
579                                }
580                        }
581                }
582                
583                
584                options.addOption(engageText, ENGAGE);
585                
586                temp.secret = false;
587                temp.canRaid = ongoingBattle || hasOtherButInsignificant || (hasNonStation && !otherWantsToFight) || !hasNonStation;
588                temp.canBombard = (hasOtherButInsignificant || (hasNonStation && !otherWantsToFight) || !hasNonStation) && !hasStation;
589                //temp.canSurpriseRaid = Misc.getDaysSinceLastRaided(market) < SURPRISE_RAID_TIMEOUT;
590                
591                boolean couldRaidIfNotDebug = temp.canRaid;
592                if (DebugFlags.MARKET_HOSTILITIES_DEBUG) {
593                        if (!temp.canRaid || !temp.canBombard) {
594                                text.addPara("(DEBUG mode: can raid and bombard anyway)");
595                        }
596                        temp.canRaid = true;
597                        temp.canBombard = true;
598                        //temp.canSurpriseRaid = true;
599                }
600                        
601//              options.addOption("Launch a raid against the colony", RAID);
602//              options.addOption("Consider an orbital bombardment", BOMBARD);
603//              options.addOption("Launch a surprise raid against " + market.getName(), RAID_SURPRISE);
604                options.addOption("Launch a raid against " + market.getName() + "", RAID);
605                //dialog.setOptionColor(RAID_SURPRISE, Misc.getStoryOptionColor());
606                options.addOption("Consider an orbital bombardment of " + market.getName() + "", BOMBARD);
607                
608                if (!temp.canRaid) {
609                        options.setEnabled(RAID, false);
610                        options.setTooltip(RAID, "The presence of enemy fleets that are willing to offer battle makes a raid impossible.");
611                } 
612                
613//              if (!temp.canSurpriseRaid) {
614////                    float surpriseRaidDays = (int) (SURPRISE_RAID_TIMEOUT - Misc.getDaysSinceLastRaided(market));
615////                    if (surpriseRaidDays > 0) {
616////                            surpriseRaidDays = (int) Math.round(surpriseRaidDays);
617////                            if (surpriseRaidDays < 1) surpriseRaidDays = 1;
618////                            String days = "days";
619////                            if (surpriseRaidDays == 1) {
620////                                    days = "day";
621////                            }
622////                            //text.addPara("Your ground forces commander estimates that");
623////                    }
624//                      options.setEnabled(RAID_SURPRISE, false);
625//                      options.setTooltip(RAID_SURPRISE, "This colony was raided within the last cycle and its ground defenses are on high alert, making a surprise raid impossible.");
626//              }
627                
628                if (!temp.canBombard) {
629                        options.setEnabled(BOMBARD, false);
630                        options.setTooltip(BOMBARD, "All defenses must be defeated to make a bombardment possible.");
631                }
632                
633                
634                //DEBUG = false;
635                if (temp.canRaid && getRaidCooldown() > 0) {// && couldRaidIfNotDebug) {
636                        if (!DebugFlags.MARKET_HOSTILITIES_DEBUG) {
637                                options.setEnabled(RAID, false);
638                                text.addPara("Your forces will be able to organize another raid within a day or so.");
639                                temp.canRaid = false;
640                        } else {
641                                text.addPara("Your forces will be able to organize another raid within a day or so.");
642                                text.addPara("(DEBUG mode: can do it anyway)");
643                        }
644                        //options.setTooltip(RAID, "Need more time to organize another raid.");
645                }
646                
647                //options.addOption("Launch a raid of the colony", RAID);
648                
649                
650                if (context != null && otherWantsToFight && !playerCanNotJoin) {
651                        boolean knows = context.getBattle() != null && context.getBattle().getNonPlayerSide() != null &&
652                                                        context.getBattle().knowsWhoPlayerIs(context.getBattle().getNonPlayerSide());
653                        boolean lowImpact = context.isLowRepImpact();
654                        FactionAPI nonHostile = plugin.getNonHostileOtherFaction();
655                        //if (!playerFleet.getFaction().isHostileTo(otherFleet.getFaction()) && knows && !context.isEngagedInHostilities()) {
656                        if (nonHostile != null && knows && !lowImpact && !context.isEngagedInHostilities()) {
657                                options.addOptionConfirmation(ENGAGE,
658                                                "The " + nonHostile.getDisplayNameLong() + 
659                                                " " + nonHostile.getDisplayNameIsOrAre() + 
660                                                " not currently hostile, and you have been positively identified. " +
661                                                "Are you sure you want to engage in open hostilities?", "Yes", "Never mind");
662                        }
663                } else if (context == null || playerCanNotJoin || !otherWantsToFight) {
664                        options.setEnabled(ENGAGE, false);
665                        if (!otherWantsToFight) {
666                                if (ongoingBattle && playerOnDefenderSide && !otherWantsToFight) {
667                                        options.setTooltip(ENGAGE, "The attackers are in disarray and not currently attempting to engage the station.");
668                                } else {
669                                        if (playerCanNotJoin) {
670                                                options.setTooltip(ENGAGE, "You're unable to join this battle.");
671                                        } else if (primary == null) {
672                                                options.setTooltip(ENGAGE, "There are no defenders to engage.");
673                                        } else {
674                                                options.setTooltip(ENGAGE, "The defenders are refusing to give battle to defend the colony.");
675                                        }
676                                }
677                        }
678                }
679                
680                options.addOption("Go back", GO_BACK);
681                options.setShortcut(GO_BACK, Keyboard.KEY_ESCAPE, false, false, false, true);
682                
683                
684                if (plugin != null) {
685                        plugin.cleanUpBattle();
686                }
687                
688        }
689        
690        public static float getRaidStr(CampaignFleetAPI fleet) {
691                float attackerStr = fleet.getCargo().getMaxPersonnel() * 0.25f;
692                float support = Misc.getFleetwideTotalMod(fleet, Stats.FLEET_GROUND_SUPPORT, 0f);
693                attackerStr += Math.min(support, attackerStr);
694                
695                StatBonus stat = fleet.getStats().getDynamic().getMod(Stats.PLANETARY_OPERATIONS_MOD);
696                attackerStr = stat.computeEffective(attackerStr);
697                
698                return attackerStr;
699        }
700        
701        public static float MARINES_IN_MARKET_CARGO_DEFENSE_BONUS = 1f;
702        public static float getDefenderStr(MarketAPI market) {
703                return getDefenderStr(market, false);
704        }
705        public static float getDefenderStr(MarketAPI market, boolean forBombard) {
706                StatBonus stat = market.getStats().getDynamic().getMod(Stats.GROUND_DEFENSES_MOD);
707                float defenderStr = (int) Math.round(stat.computeEffective(0f));
708                float added = getDefenderIncreaseValue(market);
709                defenderStr += added;
710                
711                if (market.isPlayerOwned() && !forBombard) {
712                        float marineDefenseValueMult = MARINES_IN_MARKET_CARGO_DEFENSE_BONUS;
713                        CargoAPI cargo = Misc.getStorageCargo(market);
714                        if (cargo != null) {
715                                defenderStr += cargo.getMarines() * marineDefenseValueMult;
716                        }
717                        cargo = Misc.getLocalResourcesCargo(market);
718                        if (cargo != null) {
719                                defenderStr += cargo.getMarines() * marineDefenseValueMult;
720                        }
721                }
722                
723                return defenderStr;
724        }
725        
726        public static float getRaidEffectiveness(MarketAPI market, CampaignFleetAPI fleet) {
727                return getRaidEffectiveness(market, getRaidStr(fleet));
728        }
729        public static float getRaidEffectiveness(MarketAPI market, float attackerStr) {
730                float defenderStr = getDefenderStr(market);
731                return attackerStr / Math.max(1f, (attackerStr + defenderStr));
732        }
733
734        public static int getMarinesFor(MarketAPI market, int tokens) {
735                float defenderStr = getDefenderStr(market);
736                return getMarinesFor((int)defenderStr, tokens);
737        }
738        public static int getMarinesFor(int defenderStrength, int tokens) {
739//              mult = as / (as + ds);
740//              tokens = mult / re_per
741//
742//              t * re_per = as / (as + ds)
743//              t * re_per * (as + ds) = as;
744//              t * re_per * as + t * re_per * ds = as;
745//              t * re_per * ds = as * (1 - t * re_per)
746//              as = t * re_per * ds / (1 - t * re_per)
747                
748                
749                int marines = (int) Math.round((float) tokens * RE_PER_MARINE_TOKEN * 
750                                                        (float) defenderStrength / (1f - (float) tokens * RE_PER_MARINE_TOKEN));
751                
752                return marines;
753        }
754        public static int getDisruptDaysPerToken(MarketAPI market, Industry industry) {
755                DisruptIndustryRaidObjectivePluginImpl obj = new DisruptIndustryRaidObjectivePluginImpl(market, industry);
756                return (int) Math.round(obj.getBaseDisruptDuration(1));
757        }
758        
759        
760        protected void raidNonMarket() {
761                float width = 350;
762                float opad = 10f;
763                float small = 5f;
764                
765                Color h = Misc.getHighlightColor();
766                
767                temp.nonMarket = true;
768                
769                float difficulty = memory.getFloat("$raidDifficulty");
770                temp.raidGoBackTrigger = memory.getString("$raidGoBackTrigger");
771                temp.raidContinueTrigger = memory.getString("$raidContinueTrigger");
772                
773                dialog.getVisualPanel().showImagePortion("illustrations", "raid_prepare", 640, 400, 0, 0, 480, 300);
774                
775                float marines = playerFleet.getCargo().getMarines();
776                float support = Misc.getFleetwideTotalMod(playerFleet, Stats.FLEET_GROUND_SUPPORT, 0f);
777                if (support > marines) support = marines;
778                
779                StatBonus attackerBase = new StatBonus(); 
780                StatBonus defenderBase = new StatBonus();
781                
782                //defenderBase.modifyFlatAlways("base", baseDef, "Base value for a size " + market.getSize() + " colony");
783                
784                attackerBase.modifyFlatAlways("core_marines", marines, "Marines on board");
785                attackerBase.modifyFlatAlways("core_support", support, "Fleet capability for ground support");
786                
787                StatBonus attacker = playerFleet.getStats().getDynamic().getMod(Stats.PLANETARY_OPERATIONS_MOD);
788                StatBonus defender = new StatBonus();
789                if (market != null && difficulty <= 0) defender = market.getStats().getDynamic().getMod(Stats.GROUND_DEFENSES_MOD);
790                
791                defender.modifyFlat("difficulty", difficulty, "Expected resistance");
792                
793                String surpriseKey = "core_surprise";
794//              if (temp.isSurpriseRaid) {
795//                      //defender.modifyMult(surpriseKey, 0.1f, "Surprise raid");
796//                      attacker.modifyMult(surpriseKey, SURPRISE_RAID_STRENGTH_MULT, "Surprise raid");
797//              }
798                
799                String increasedDefensesKey = "core_addedDefStr";
800                float added = 0;
801                if (market != null) added = getDefenderIncreaseValue(market);
802                if (added > 0) {
803                        defender.modifyFlat(increasedDefensesKey, added, "Increased defender preparedness");
804                }
805                
806                float attackerStr = (int) Math.round(attacker.computeEffective(attackerBase.computeEffective(0f)));
807                float defenderStr = (int) Math.round(defender.computeEffective(defenderBase.computeEffective(0f)));
808                
809                temp.attackerStr = attackerStr;
810                temp.defenderStr = defenderStr;
811                
812                TooltipMakerAPI info = text.beginTooltip();
813                
814                info.setParaSmallInsignia();
815                
816                String has = faction.getDisplayNameHasOrHave();
817                String is = faction.getDisplayNameIsOrAre();
818                boolean hostile = faction.isHostileTo(Factions.PLAYER);
819                boolean tOn = playerFleet.isTransponderOn();
820                float initPad = 0f;
821                if (!hostile && !faction.isNeutralFaction()) {
822                        if (tOn) {
823                                info.addPara(Misc.ucFirst(faction.getDisplayNameWithArticle()) + " " + is + 
824                                                " not currently hostile. Your fleet's transponder is on, and carrying out a raid " +
825                                                "will result in open hostilities.",
826                                                initPad, faction.getBaseUIColor(), faction.getDisplayNameWithArticleWithoutArticle());
827                        } else {
828                                info.addPara(Misc.ucFirst(faction.getDisplayNameWithArticle()) + " " + is + 
829                                                " not currently hostile. Your fleet's transponder is off, and carrying out a raid " +
830                                                "will only result in a minor penalty to your standing.",
831                                                initPad, faction.getBaseUIColor(), faction.getDisplayNameWithArticleWithoutArticle());
832                        }
833                        initPad = opad;
834                }
835                
836                float sep = small;
837                sep = 3f;
838                info.addPara("Raid strength: %s", initPad, h, "" + (int)attackerStr);
839                info.addStatModGrid(width, 50, opad, small, attackerBase, true, statPrinter(false));
840                if (!attacker.isUnmodified()) {
841                        info.addStatModGrid(width, 50, opad, sep, attacker, true, statPrinter(true));
842                }
843                
844                
845                info.addPara("Operation difficulty: %s", opad, h, "" + (int)defenderStr);
846                //info.addStatModGrid(width, 50, opad, small, defenderBase, true, statPrinter());
847                //if (!defender.isUnmodified()) {
848                info.addStatModGrid(width, 50, opad, small, defender, true, statPrinter(true));
849                //}
850                
851                defender.unmodifyFlat(increasedDefensesKey);
852                defender.unmodifyMult(surpriseKey);
853                attacker.unmodifyMult(surpriseKey);
854                
855                text.addTooltip();
856                
857                boolean hasForces = true;
858                temp.raidMult = attackerStr / Math.max(1f, (attackerStr + defenderStr));
859                temp.raidMult = Math.round(temp.raidMult * 100f) / 100f;
860                
861                {
862                        Color eColor = h;
863                        if (temp.raidMult < DISRUPTION_THRESHOLD && temp.raidMult < VALUABLES_THRESHOLD) {
864                                eColor = Misc.getNegativeHighlightColor();
865                        }
866                        text.addPara("Projected raid effectiveness: %s",
867                                        eColor,
868                                        "" + (int)(temp.raidMult * 100f) + "%");
869                        //"" + (int)Math.round(temp.raidMult * 100f) + "%");
870                        if (temp.raidMult < VALUABLES_THRESHOLD) {
871                                text.addPara("You do not have the forces to carry out an effective raid.");
872                                hasForces = false;
873                        }
874                }
875                
876                options.clearOptions();
877                
878                options.addOption("Designate raid objectives", RAID_VALUABLE);
879                
880                if (!hasForces) {
881                        options.setEnabled(RAID_VALUABLE, false);
882                }
883                
884                options.addOption("Go back", RAID_GO_BACK);
885                options.setShortcut(RAID_GO_BACK, Keyboard.KEY_ESCAPE, false, false, false, true);
886        }
887        
888        
889//      protected void raidRare() {
890//              
891//      }
892        
893        protected void raidMenu() {
894                float width = 350;
895                float opad = 10f;
896                float small = 5f;
897                
898//              if (true) {
899//                      Global.getSector().getCampaignUI().showCoreUITab(CoreUITabId.CARGO);
900//                      return;
901//              }
902                
903                Color h = Misc.getHighlightColor();
904                
905                temp.nonMarket = false;
906                
907//              dialog.getVisualPanel().showPlanetInfo(market.getPrimaryEntity());
908//              dialog.getVisualPanel().finishFadeFast();
909                dialog.getVisualPanel().showImagePortion("illustrations", "raid_prepare", 640, 400, 0, 0, 480, 300);
910
911                float marines = playerFleet.getCargo().getMarines();
912                float support = Misc.getFleetwideTotalMod(playerFleet, Stats.FLEET_GROUND_SUPPORT, 0f);
913                if (support > marines) support = marines;
914                
915                StatBonus attackerBase = new StatBonus(); 
916                StatBonus defenderBase = new StatBonus(); 
917                
918                //defenderBase.modifyFlatAlways("base", baseDef, "Base value for a size " + market.getSize() + " colony");
919                
920                attackerBase.modifyFlatAlways("core_marines", marines, "Marines on board");
921                attackerBase.modifyFlatAlways("core_support", support, "Fleet capability for ground support");
922                
923                StatBonus attacker = playerFleet.getStats().getDynamic().getMod(Stats.PLANETARY_OPERATIONS_MOD);
924                StatBonus defender = market.getStats().getDynamic().getMod(Stats.GROUND_DEFENSES_MOD);
925                
926                String surpriseKey = "core_surprise";
927//              if (temp.isSurpriseRaid) {
928//                      //defender.modifyMult(surpriseKey, 0.1f, "Surprise raid");
929//                      attacker.modifyMult(surpriseKey, SURPRISE_RAID_STRENGTH_MULT, "Surprise raid");
930//              }
931                
932                String increasedDefensesKey = "core_addedDefStr";
933                float added = getDefenderIncreaseValue(market);
934                if (added > 0) {
935                        defender.modifyFlat(increasedDefensesKey, added, "Increased defender preparedness");
936                }
937                
938                float attackerStr = (int) Math.round(attacker.computeEffective(attackerBase.computeEffective(0f)));
939                float defenderStr = (int) Math.round(defender.computeEffective(defenderBase.computeEffective(0f)));
940                
941                temp.attackerStr = attackerStr;
942                temp.defenderStr = defenderStr;
943                
944                TooltipMakerAPI info = text.beginTooltip();
945                
946                info.setParaSmallInsignia();
947                
948                String has = faction.getDisplayNameHasOrHave();
949                String is = faction.getDisplayNameIsOrAre();
950                boolean hostile = faction.isHostileTo(Factions.PLAYER);
951                boolean tOn = playerFleet.isTransponderOn();
952                float initPad = 0f;
953                if (!hostile) {
954                        if (tOn) {
955                                info.addPara(Misc.ucFirst(faction.getDisplayNameWithArticle()) + " " + is + 
956                                                " not currently hostile. Your fleet's transponder is on, and carrying out a raid " +
957                                                "will result in open hostilities.",
958                                                initPad, faction.getBaseUIColor(), faction.getDisplayNameWithArticleWithoutArticle());
959                        } else {
960                                info.addPara(Misc.ucFirst(faction.getDisplayNameWithArticle()) + " " + is + 
961                                                " not currently hostile. Your fleet's transponder is off, and carrying out a raid " +
962                                                "will only result in a minor penalty to your standing.",
963                                                initPad, faction.getBaseUIColor(), faction.getDisplayNameWithArticleWithoutArticle());
964                        }
965                        initPad = opad;
966                }
967                
968                float sep = small;
969                sep = 3f;
970                info.addPara("Raid strength: %s", initPad, h, "" + (int)attackerStr);
971                info.addStatModGrid(width, 50, opad, small, attackerBase, true, statPrinter(false));
972                if (!attacker.isUnmodified()) {
973                        info.addStatModGrid(width, 50, opad, sep, attacker, true, statPrinter(true));
974                }
975                
976                
977                info.addPara("Ground defense strength: %s", opad, h, "" + (int)defenderStr);
978                //info.addStatModGrid(width, 50, opad, small, defenderBase, true, statPrinter());
979                //if (!defender.isUnmodified()) {
980                        info.addStatModGrid(width, 50, opad, small, defender, true, statPrinter(true));
981                //}
982                        
983                defender.unmodifyFlat(increasedDefensesKey);
984                defender.unmodifyMult(surpriseKey);
985                attacker.unmodifyMult(surpriseKey);
986                
987                text.addTooltip();
988
989                boolean hasForces = true;
990                boolean canDisrupt = true;
991                temp.raidMult = attackerStr / Math.max(1f, (attackerStr + defenderStr));
992                temp.raidMult = Math.round(temp.raidMult * 100f) / 100f;
993                //temp.raidMult = 1f;
994                
995                
996                
997                
998                {
999                        //temp.failProb = 0f;
1000                        Color eColor = h;
1001                        if (temp.raidMult < DISRUPTION_THRESHOLD && temp.raidMult < VALUABLES_THRESHOLD) {
1002                                eColor = Misc.getNegativeHighlightColor();
1003                        }
1004                        if (temp.raidMult < DISRUPTION_THRESHOLD) {
1005                                //eColor = Misc.getNegativeHighlightColor();
1006                                canDisrupt = false;
1007                                //temp.canFail = true;
1008                        } else if (temp.raidMult >= 0.7f) {
1009                                //eColor = Misc.getPositiveHighlightColor();
1010                        }
1011//                      text.addPara("Projected raid effectiveness: %s. " +
1012//                                      "This will determine the outcome of the raid, " +
1013//                                      "as well as the casualties suffered by your forces, if any.",
1014//                                      eColor,
1015//                                      "" + (int)Math.round(temp.raidMult * 100f) + "%");
1016                        text.addPara("Projected raid effectiveness: %s",
1017                                        eColor,
1018                                        "" + (int)(temp.raidMult * 100f) + "%");
1019                                        //"" + (int)Math.round(temp.raidMult * 100f) + "%");
1020                        if (!canDisrupt) {
1021                                text.addPara("The ground defenses are too strong for your forces to be able to cause long-term disruption.");
1022                        }
1023                        if (temp.raidMult < VALUABLES_THRESHOLD) {
1024                                text.addPara("You do not have the forces to carry out an effective raid to acquire valuables or achieve other objectives.");
1025                                hasForces = false;
1026                        }
1027//                      if (canDisrupt) {
1028//                      } else {
1029//                              text.addPara("Projected raid effectiveness: %s. " +
1030//                                              "This will determine the outcome of the raid, " +
1031//                                              "as well as the casualties suffered by your forces, if any.",
1032//                                              eColor,
1033//                                              "" + (int)Math.round(temp.raidMult * 100f) + "%");
1034//                      }
1035                }
1036                
1037                if (DebugFlags.MARKET_HOSTILITIES_DEBUG) {
1038                        canDisrupt = true;
1039                }
1040                
1041                options.clearOptions();
1042                
1043                //options.addOption("Try to acquire rare items, such as blueprints", RAID_RARE);
1044                //options.addOption("Try to acquire valuables, such as commodities, blueprints, and other items", RAID_VALUABLE);
1045                options.addOption("Try to acquire valuables, such as commodities or blueprints, or achieve other objectives", RAID_VALUABLE);
1046                options.addOption("Disrupt the operations of a specific industry or facility", RAID_DISRUPT);
1047                
1048                if (!hasForces) {
1049                        options.setEnabled(RAID_VALUABLE, false);
1050                        //options.setEnabled(RAID_RARE, false);
1051                }
1052                
1053                if (!hasForces || !canDisrupt) {
1054                        options.setEnabled(RAID_DISRUPT, false);
1055                        if (!canDisrupt) {
1056                                String pct = "" + (int)Math.round(DISRUPTION_THRESHOLD * 100f) + "%";
1057                                options.setTooltip(RAID_DISRUPT, "Requires at least " + pct + " raid effectiveness.");
1058                                options.setTooltipHighlights(RAID_DISRUPT, pct);
1059                                options.setTooltipHighlightColors(RAID_DISRUPT, h);
1060                        }
1061                }
1062        
1063                options.addOption("Go back", RAID_GO_BACK);
1064                options.setShortcut(RAID_GO_BACK, Keyboard.KEY_ESCAPE, false, false, false, true);
1065        }
1066        
1067//      protected void raidRare() {
1068//              
1069//      }
1070        
1071        protected void raidValuable() {
1072                temp.raidType = RaidType.VALUABLE;
1073                
1074                List<GroundRaidObjectivePlugin> obj = new ArrayList<GroundRaidObjectivePlugin>();
1075                
1076                // See: StandardGroundRaidObjectivesCreator; it creates the standard objectives with priority 0 below
1077                final RaidType useType = !temp.nonMarket ? temp.raidType : RaidType.CUSTOM_ONLY;
1078                //if (temp.nonMarket) useType = RaidType.CUSTOM_ONLY;
1079                for (int i = 0; i < 10; i++) {
1080                        ListenerUtil.modifyRaidObjectives(market, entity, obj, useType, getNumMarineTokens(), i);
1081                }
1082                
1083                if (obj.isEmpty()) {
1084                        text.addPara("After careful consideration, there do not appear to be any targets " +
1085                                                 "likely to yield anything of value.");
1086                        addNeverMindOption();
1087                        return;
1088                }
1089                
1090                
1091                dialog.showGroundRaidTargetPicker("Select raid objectives", "Select", market, obj, 
1092                                new GroundRaidTargetPickerDelegate() {
1093                        public void pickedGroundRaidTargets(List<GroundRaidObjectivePlugin> data) {
1094                                float value = 0;
1095                                for (GroundRaidObjectivePlugin curr : data) {
1096                                        value += curr.getProjectedCreditsValue();
1097                                }
1098                                Color h = Misc.getHighlightColor();
1099                                List<String> names = new ArrayList<String>();
1100                                for (GroundRaidObjectivePlugin curr : data) {
1101                                        names.add(curr.getNameOverride() != null ? curr.getNameOverride() : curr.getName());
1102                                }
1103                                String list = Misc.getAndJoined(names);
1104                                String item = "objective";
1105                                if (names.size() > 1) {
1106                                        item = "objectives";
1107                                }
1108                                
1109                                String isOrAre = "are";
1110                                String marinesStr = "marines";
1111                                if (playerCargo.getMarines() == 1) {
1112                                        isOrAre = "is";
1113                                        marinesStr = "marine";
1114                                }
1115                                //float losses = getProjectedMarineLossesFloat();
1116                                LabelAPI label = text.addPara("Your marine commander submits a plan for your approval. Losses during this " +
1117                                                "operation are projected to be %s. There " + isOrAre + " a total of %s " +
1118                                                marinesStr + " in your fleet.", 
1119                                                getMarineLossesColor(data), getProjectedMarineLosses(data).toLowerCase(), 
1120                                                Misc.getWithDGS(playerCargo.getMarines()));
1121                                label.setHighlightColors(getMarineLossesColor(data), Misc.getHighlightColor());
1122                                text.addPara(Misc.ucFirst(item) + " targeted: " + list + ".", h,
1123                                                names.toArray(new String[0]));
1124                                if (value > 0) {
1125                                        text.addPara("The estimated value of the items obtained is projected to be around %s.",
1126                                                        h, Misc.getDGSCredits(value));
1127                                }
1128
1129//                              text.addPara("The marines are ready to go, awaiting your final confirmation. There are a total of %s " +
1130//                                              "marines in your fleet.", Misc.getHighlightColor(), Misc.getWithDGS(playerCargo.getMarines()));
1131                                text.addPara("The marines are ready to go, awaiting your final confirmation.");
1132                                temp.objectives = data;
1133                                addConfirmOptions();
1134                        }
1135                        
1136                        public boolean isDisruptIndustryMode() {
1137                                return false;
1138                        }
1139                        
1140                        public boolean isCustomOnlyMode() {
1141                                return useType == RaidType.CUSTOM_ONLY;
1142                        }
1143                        
1144                        public void cancelledGroundRaidTargetPicking() {
1145                                
1146                        }
1147                        
1148                        public int getCargoSpaceNeeded(List<GroundRaidObjectivePlugin> data) {
1149                                float total = 0;
1150                                for (GroundRaidObjectivePlugin curr : data) {
1151                                        total += curr.getCargoSpaceNeeded();
1152                                }
1153                                return (int) total;
1154                        }
1155
1156                        public int getFuelSpaceNeeded(List<GroundRaidObjectivePlugin> data) {
1157                                float total = 0;
1158                                for (GroundRaidObjectivePlugin curr : data) {
1159                                        total += curr.getFuelSpaceNeeded();
1160                                }
1161                                return (int) total;
1162                        }
1163
1164                        public int getProjectedCreditsValue(List<GroundRaidObjectivePlugin> data) {
1165                                float total = 0;
1166                                for (GroundRaidObjectivePlugin curr : data) {
1167                                        total += curr.getProjectedCreditsValue();
1168                                }
1169                                return (int) total;
1170                        }
1171                        
1172                        public int getNumMarineTokens() {
1173                                return MarketCMD.this.getNumMarineTokens();
1174                        }
1175                        
1176                        public MutableStat getMarineLossesStat(List<GroundRaidObjectivePlugin> data) {
1177                                return MarketCMD.this.getMarineLossesStat(data);
1178                        }
1179                        
1180                        public String getProjectedMarineLosses(List<GroundRaidObjectivePlugin> data) {
1181                                //return "" + (int) Math.round(getProjectedMarineLossesFloat());
1182                                float marines = playerFleet.getCargo().getMarines();
1183                                float losses = getAverageMarineLosses(data);
1184                                
1185                                float f = losses / Math.max(1f, marines);
1186                                
1187                                for (RaidDangerLevel level : RaidDangerLevel.values()) {
1188                                        float test = level.marineLossesMult + (level.next().marineLossesMult - level.marineLossesMult) * 0.5f;
1189                                        if (level == RaidDangerLevel.NONE) test = RaidDangerLevel.NONE.marineLossesMult;
1190                                        if (test >= f) {
1191                                                return level.lossesName;
1192                                        }
1193                                }
1194                                return RaidDangerLevel.EXTREME.lossesName;
1195                        }
1196                        
1197                        public float getAverageMarineLosses(List<GroundRaidObjectivePlugin> data) {
1198                                return MarketCMD.this.getAverageMarineLosses(data);
1199                        }
1200                
1201                        public Color getMarineLossesColor(List<GroundRaidObjectivePlugin> data) {
1202                                float marines = playerFleet.getCargo().getMarines();
1203                                float losses = getAverageMarineLosses(data);
1204                                
1205                                        
1206                                float f = losses / Math.max(1f, marines);
1207                                if (f <= 0 && data.isEmpty())  return Misc.getGrayColor();
1208                                
1209                                for (RaidDangerLevel level : RaidDangerLevel.values()) {
1210                                        float test = level.marineLossesMult + (level.next().marineLossesMult - level.marineLossesMult) * 0.5f;
1211                                        if (test >= f) {
1212                                                return level.color;
1213                                        }
1214                                }
1215                                return RaidDangerLevel.EXTREME.color;
1216                        }
1217                        public String getRaidEffectiveness() {
1218                                return "" + (int)(temp.raidMult * 100f) + "%";
1219                        }
1220                });
1221        }
1222        
1223        protected void addBombardConfirmOptions() {
1224                options.clearOptions();
1225                options.addOption("Launch bombardment", BOMBARD_CONFIRM);
1226                options.addOption("Never mind", BOMBARD_NEVERMIND);
1227                options.setShortcut(BOMBARD_NEVERMIND, Keyboard.KEY_ESCAPE, false, false, false, true);
1228                
1229                List<FactionAPI> nonHostile = new ArrayList<FactionAPI>();
1230                for (FactionAPI faction : temp.willBecomeHostile) {
1231                        boolean hostile = faction.isHostileTo(Factions.PLAYER);
1232                        if (!hostile) {
1233                                nonHostile.add(faction);
1234                        }
1235                }
1236                
1237                if (nonHostile.size() == 1) {
1238                        FactionAPI faction = nonHostile.get(0);
1239                        options.addOptionConfirmation(BOMBARD_CONFIRM,
1240                                        "The " + faction.getDisplayNameLong() + 
1241                                        " " + faction.getDisplayNameIsOrAre() + 
1242                                        " not currently hostile, and will become hostile if you carry out the bombardment. " +
1243                                        "Are you sure?", "Yes", "Never mind");
1244                } else if (nonHostile.size() > 1) {
1245                        options.addOptionConfirmation(BOMBARD_CONFIRM,
1246                                        "Multiple factions that are not currently hostile " +
1247                                        "will become hostile if you carry out the bombardment. " +
1248                                        "Are you sure?", "Yes", "Never mind");
1249                }
1250        }
1251        
1252        protected void raidDisrupt() {
1253                temp.raidType = RaidType.DISRUPT;
1254                
1255                // See: StandardGroundRaidObjectivesCreator; it creates the standard objectives with priority 0 below
1256                List<GroundRaidObjectivePlugin> obj = new ArrayList<GroundRaidObjectivePlugin>();
1257                for (int i = 0; i < 10; i++) {
1258                        ListenerUtil.modifyRaidObjectives(market, entity, obj, temp.raidType, getNumMarineTokens(), i);
1259                }
1260                
1261                if (obj.isEmpty()) {
1262                        text.addPara("There are no industries or facilities present that could be disrupted by a raid.");
1263                        addNeverMindOption();
1264                        return;
1265                }
1266                
1267                
1268                dialog.showGroundRaidTargetPicker("Select raid objectives", "Select", market, obj, 
1269                                new GroundRaidTargetPickerDelegate() {
1270                        public void pickedGroundRaidTargets(List<GroundRaidObjectivePlugin> data) {
1271                                float value = 0;
1272                                for (GroundRaidObjectivePlugin curr : data) {
1273                                        value += curr.getProjectedCreditsValue();
1274                                }
1275                                Color h = Misc.getHighlightColor();
1276                                List<String> names = new ArrayList<String>();
1277                                for (GroundRaidObjectivePlugin curr : data) {
1278                                        names.add(curr.getNameOverride() != null ? curr.getNameOverride() : curr.getName());
1279                                }
1280                                String list = Misc.getAndJoined(names);
1281                                String item = "objective";
1282                                if (names.size() > 1) {
1283                                        item = "objectives";
1284                                }
1285                                
1286                                //float losses = getProjectedMarineLossesFloat();
1287
1288                                text.addPara("Your marine commander submits a plan for your approval. Losses during this " +
1289                                                "operation are projected to be %s.", 
1290                                                getMarineLossesColor(data), getProjectedMarineLosses(data).toLowerCase());
1291                                text.addPara(Misc.ucFirst(item) + " targeted: " + list + ".", h,
1292                                                names.toArray(new String[0]));
1293                                
1294                                if (value > 0) {
1295                                        text.addPara("The estimated value of the items obtained is projected to be around %s.",
1296                                                        h, Misc.getDGSCredits(value));
1297                                }
1298
1299                                text.addPara("The marines are ready to go, awaiting your final confirmation. There are a total of %s " +
1300                                                "marines in your fleet.", Misc.getHighlightColor(), Misc.getWithDGS(playerCargo.getMarines()));
1301                                temp.objectives = data;
1302                                addConfirmOptions();
1303                        }
1304                        
1305                        public boolean isDisruptIndustryMode() {
1306                                return true;
1307                        }
1308                        
1309                        public void cancelledGroundRaidTargetPicking() {
1310                                
1311                        }
1312                        
1313                        public int getCargoSpaceNeeded(List<GroundRaidObjectivePlugin> data) {
1314                                float total = 0;
1315                                for (GroundRaidObjectivePlugin curr : data) {
1316                                        total += curr.getCargoSpaceNeeded();
1317                                }
1318                                return (int) total;
1319                        }
1320
1321                        public int getFuelSpaceNeeded(List<GroundRaidObjectivePlugin> data) {
1322                                float total = 0;
1323                                for (GroundRaidObjectivePlugin curr : data) {
1324                                        total += curr.getFuelSpaceNeeded();
1325                                }
1326                                return (int) total;
1327                        }
1328
1329                        public int getProjectedCreditsValue(List<GroundRaidObjectivePlugin> data) {
1330                                float total = 0;
1331                                for (GroundRaidObjectivePlugin curr : data) {
1332                                        total += curr.getProjectedCreditsValue();
1333                                }
1334                                return (int) total;
1335                        }
1336                        
1337                        public int getNumMarineTokens() {
1338                                return MarketCMD.this.getNumMarineTokens();
1339                        }
1340                        
1341                        public MutableStat getMarineLossesStat(List<GroundRaidObjectivePlugin> data) {
1342                                return MarketCMD.this.getMarineLossesStat(data);
1343                        }
1344                        
1345                        public String getProjectedMarineLosses(List<GroundRaidObjectivePlugin> data) {
1346                                //return "" + (int) Math.round(getProjectedMarineLossesFloat());
1347                                float marines = playerFleet.getCargo().getMarines();
1348                                float losses = getAverageMarineLosses(data);
1349                                
1350                                float f = losses / Math.max(1f, marines);
1351                                
1352                                for (RaidDangerLevel level : RaidDangerLevel.values()) {
1353                                        float test = level.marineLossesMult + (level.next().marineLossesMult - level.marineLossesMult) * 0.5f;
1354                                        if (level == RaidDangerLevel.NONE) test = RaidDangerLevel.NONE.marineLossesMult;
1355                                        if (test >= f) {
1356                                                return level.lossesName;
1357                                        }
1358                                }
1359                                return RaidDangerLevel.EXTREME.lossesName;
1360                        }
1361                        
1362                        public float getAverageMarineLosses(List<GroundRaidObjectivePlugin> data) {
1363                                return MarketCMD.this.getAverageMarineLosses(data);
1364                        }
1365                
1366                        public Color getMarineLossesColor(List<GroundRaidObjectivePlugin> data) {
1367                                float marines = playerFleet.getCargo().getMarines();
1368                                float losses = getAverageMarineLosses(data);
1369                                
1370                                        
1371                                float f = losses / Math.max(1f, marines);
1372                                if (f <= 0)  return Misc.getGrayColor();
1373                                
1374                                for (RaidDangerLevel level : RaidDangerLevel.values()) {
1375                                        float test = level.marineLossesMult + (level.next().marineLossesMult - level.marineLossesMult) * 0.5f;
1376                                        if (test >= f) {
1377                                                return level.color;
1378                                        }
1379                                }
1380                                return RaidDangerLevel.EXTREME.color;
1381                        }
1382                        public String getRaidEffectiveness() {
1383                                return "" + (int)(temp.raidMult * 100f) + "%";
1384                        }
1385
1386                        public boolean isCustomOnlyMode() {
1387                                // TODO Auto-generated method stub
1388                                return false;
1389                        }
1390                });             
1391                
1392                
1393//              dialog.showIndustryPicker("Select raid target", "Select", market, targets, new IndustryPickerListener() {
1394//                      public void pickedIndustry(Industry industry) {
1395//                              raidDisruptIndustryPicked(industry);
1396//                      }
1397//                      public void cancelledIndustryPicking() {
1398//                              
1399//                      }
1400//              });
1401        }
1402        
1403        protected float computeBaseDisruptDuration(Industry ind) {
1404                //float dur = getNumMarineTokens() * Global.getSettings().getFloat("raidDisruptDurationPerMarineToken") - ind.getDisruptedDays();
1405                float dur = getNumMarineTokens() * ind.getSpec().getDisruptDanger().disruptionDays - ind.getDisruptedDays();
1406                return (int) dur;
1407        }
1408        
1409        public static int getBombardDestroyThreshold() {
1410                return Global.getSettings().getInt("bombardSaturationDestroySize");
1411                
1412        }
1413        public static int getBombardDisruptDuration() {
1414                float dur = Global.getSettings().getFloat("bombardDisruptDuration");
1415                return (int) dur;
1416        }
1417        
1418        protected void raidDisruptIndustryPicked(Industry target) {
1419                temp.target = target;
1420                text.addParagraph("Target: " + target.getCurrentName(), Global.getSettings().getColor("buttonText"));
1421                
1422                float dur = computeBaseDisruptDuration(target);
1423                
1424                Color h = Misc.getHighlightColor();
1425                
1426                float already = target.getDisruptedDays();
1427                if (already > 0) {
1428                        text.addPara(target.getNameForModifier() + " operations are already disrupted, and a raid will have " +
1429                                        "reduced effect.");
1430                }
1431                
1432                text.addPara("Your ground forces commander estimates that given the relative force strengths, " +
1433                                " the raid should disrupt all " + target.getCurrentName() + " operations for at least %s days.",
1434                                h, "" + (int) Misc.getRounded(dur));
1435                
1436                text.addPara("Your forces are ready to go, awaiting your final confirmation.");
1437                
1438                options.clearOptions();
1439                
1440                addConfirmOptions();
1441        }
1442        
1443        
1444        protected void addNeverMindOption() {
1445                options.clearOptions();
1446                options.addOption("Never mind", RAID_NEVER_MIND);
1447                options.setShortcut(RAID_NEVER_MIND, Keyboard.KEY_ESCAPE, false, false, false, true);
1448        }
1449        
1450        protected void addBombardNeverMindOption() {
1451                options.clearOptions();
1452                options.addOption("Never mind", BOMBARD_NEVERMIND);
1453                options.setShortcut(BOMBARD_NEVERMIND, Keyboard.KEY_ESCAPE, false, false, false, true);
1454        }
1455        
1456        protected void addContinueOption() {
1457                addContinueOption(null);
1458        }
1459        protected void addContinueOption(String text) {
1460                if (text == null) text = "Continue";
1461                options.clearOptions();
1462                options.addOption(text, RAID_RESULT);
1463        }
1464        
1465        
1466        public static final String DEFENDER_INCREASE_KEY = "$core_defenderIncrease";
1467        public static float getDefenderIncreaseRaw(MarketAPI market) {
1468                if (market == null) return 0f;
1469                float e = market.getMemoryWithoutUpdate().getExpire(DEFENDER_INCREASE_KEY);
1470                if (e < 0) e = 0;
1471                return e;
1472        }
1473        
1474        public static void applyDefenderIncreaseFromRaid(MarketAPI market) {
1475                float e = market.getMemoryWithoutUpdate().getExpire(DEFENDER_INCREASE_KEY);
1476                if(e < 0) e = 0;
1477                e += getRaidDefenderIncreasePerRaid();
1478                float max = getRaidDefenderIncreaseMax();
1479                if (e > max) e = max;
1480                
1481                market.getMemoryWithoutUpdate().set(DEFENDER_INCREASE_KEY, true);
1482                market.getMemoryWithoutUpdate().expire(DEFENDER_INCREASE_KEY, e);
1483        }
1484        
1485        public static float getDefenderIncreaseValue(MarketAPI market) {
1486                float e = getDefenderIncreaseRaw(market);
1487                float f = getRaidDefenderIncreaseFraction();
1488                float min = getRaidDefenderIncreaseMin();
1489                
1490                float base = PopulationAndInfrastructure.getBaseGroundDefenses(market.getSize());
1491                float incr = Math.max(base * f, min);
1492                
1493                float per = getRaidDefenderIncreasePerRaid();
1494                
1495                return (int)(incr * e / per);
1496        }
1497        
1498        protected static float getRaidDefenderIncreasePerRaid() {
1499                return Global.getSettings().getFloat("raidDefenderIncreasePerRaid");
1500        }
1501        protected static float getRaidDefenderIncreaseMax() {
1502                return Global.getSettings().getFloat("raidDefenderIncreaseMax");
1503        }
1504        protected static float getRaidDefenderIncreaseFraction() {
1505                return Global.getSettings().getFloat("raidDefenderIncreaseFraction");
1506        }
1507        protected static float getRaidDefenderIncreaseMin() {
1508                return Global.getSettings().getFloat("raidDefenderIncreaseMin");
1509        }
1510        
1511        
1512        protected float getRaidCooldownMax() {
1513                return Global.getSettings().getFloat("raidCooldownDays");
1514        }
1515        
1516        protected void setRaidCooldown(float cooldown) {
1517                String key = "$raid_cooldown";
1518                Global.getSector().getMemoryWithoutUpdate().set(key, true, cooldown);
1519        }
1520        
1521        protected float getRaidCooldown() {
1522                String key = "$raid_cooldown";
1523                return Global.getSector().getMemoryWithoutUpdate().getExpire(key);
1524        }
1525        
1526        protected Random getRandom() {
1527                String key = "$raid_random";
1528                MemoryAPI mem = null;
1529                SectorEntityToken entity = null;
1530                if (market != null) {
1531                        mem = market.getMemoryWithoutUpdate();
1532                        entity = market.getPrimaryEntity();
1533                } else {
1534                        entity = this.entity;
1535                        mem = entity.getMemoryWithoutUpdate();
1536                }
1537                Random random = null;
1538                if (mem.contains(key)) {
1539                        random = (Random) mem.get(key);
1540                } else {
1541                        if (entity != null) {
1542                                long seed = Misc.getSalvageSeed(entity);
1543                                seed /= 321L;
1544                                seed *= (Global.getSector().getClock().getMonth() + 10);
1545                                random = new Random(seed);
1546                        } else {
1547                                random = new Random();
1548                        }
1549                }
1550                mem.set(key, random, 30f);
1551                
1552                return random;
1553        }
1554        
1555        
1556        public int getNumMarineTokens() {
1557                //if (true) return MAX_MARINE_TOKENS;
1558                int num = (int) Math.round(temp.raidMult / RE_PER_MARINE_TOKEN); 
1559                if (num < MIN_MARINE_TOKENS) num = MIN_MARINE_TOKENS;
1560                if (num > MAX_MARINE_TOKENS) num = MAX_MARINE_TOKENS;
1561                return num;
1562        }
1563        
1564        protected MutableStat getMarineLossesStat(List<GroundRaidObjectivePlugin> data) {
1565                MutableStat stat = new MutableStat(1f);
1566                
1567                float total = 0f;
1568                float assignedTokens = 0f;
1569                for (GroundRaidObjectivePlugin curr : data) {
1570                        RaidDangerLevel danger = curr.getDangerLevel();
1571                        total += danger.marineLossesMult * (float) curr.getMarinesAssigned();
1572                        assignedTokens += curr.getMarinesAssigned();
1573                }
1574                
1575                float danger = total / Math.max(1f, assignedTokens);
1576                
1577                float hazard = 1f;
1578                if (market != null) hazard = market.getHazardValue();
1579                
1580                float reMult = 1f;
1581                if (temp.raidMult > MIN_RE_TO_REDUCE_MARINE_LOSSES) {
1582                        float extra = (temp.raidMult - MIN_RE_TO_REDUCE_MARINE_LOSSES) / (1f - MIN_RE_TO_REDUCE_MARINE_LOSSES);
1583//                      extra = (float) Math.sqrt(extra);
1584//                      extra *= 0.5f;
1585                        extra = MAX_MARINE_LOSS_REDUCTION_MULT + (1f - MAX_MARINE_LOSS_REDUCTION_MULT) * (1f - extra);
1586                        reMult = extra;
1587                } else if (temp.raidMult < RE_PER_MARINE_TOKEN) {
1588                        float extra = 1f + (RE_PER_MARINE_TOKEN - temp.raidMult) / RE_PER_MARINE_TOKEN;
1589                        reMult = extra;
1590                }
1591                
1592                if (market != null && reMult < 1f) {
1593                        float minMarinesForAssignedTokens = getMarinesFor(market, (int) Math.round(assignedTokens));
1594                        float actualMarines = Global.getSector().getPlayerFleet().getCargo().getMarines();
1595                        if (actualMarines > minMarinesForAssignedTokens && actualMarines > 0) {
1596                                reMult *= 0.5f + (0.5f * minMarinesForAssignedTokens / actualMarines);
1597                        }
1598                }
1599
1600                float reservesMult = 1f;
1601                float maxTokens = getNumMarineTokens();
1602                if (maxTokens > assignedTokens) {
1603                        reservesMult = 1f - (maxTokens - assignedTokens) * LOSS_REDUCTION_PER_RESERVE_TOKEN;
1604                        reservesMult = Math.max(0.5f, reservesMult);
1605                }
1606                
1607                float e = getDefenderIncreaseRaw(market);
1608                float per = getRaidDefenderIncreasePerRaid();
1609                float prep = e / per * LOSS_INCREASE_PER_RAID;
1610                
1611                stat.modifyMultAlways("danger", danger, "Danger level of objectives");
1612                stat.modifyMult("hazard", hazard, "Colony hazard rating");
1613                if (reMult < 1f) {
1614                        stat.modifyMultAlways("reMult", reMult, "High raid effectiveness");
1615                } else if (reMult > 1f) {
1616                        stat.modifyMultAlways("reMult", reMult, "Low raid effectiveness");
1617                }
1618                
1619                if (reservesMult < 1f && assignedTokens > 0) {
1620                        stat.modifyMultAlways("reservesMult", reservesMult, "Forces held in reserve");
1621                }
1622//              else if (reservesMult >= 1f && assignedTokens > 0) {
1623//                      stat.modifyMultAlways("reservesMult", 1f, "No forces held in reserve");
1624//              }
1625                
1626                stat.modifyMult("prep", 1f + prep, "Increased defender preparedness");
1627                
1628                stat.applyMods(playerFleet.getStats().getDynamic().getStat(Stats.PLANETARY_OPERATIONS_CASUALTIES_MULT));
1629                
1630                ListenerUtil.modifyMarineLossesStatPreRaid(market, data, stat);
1631                
1632                return stat;
1633        }
1634        
1635        
1636
1637        protected float getAverageMarineLosses(List<GroundRaidObjectivePlugin> data) {
1638                MutableStat stat = getMarineLossesStat(data);
1639                float mult = stat.getModifiedValue();
1640                if (mult > MAX_MARINE_LOSSES) {
1641                        mult = MAX_MARINE_LOSSES;
1642                }
1643                
1644                float marines = playerFleet.getCargo().getMarines();
1645                return marines * mult;
1646        }
1647        
1648        protected void addMilitaryResponse() {
1649                if (market == null) return;
1650                
1651                if (!market.getFaction().getCustomBoolean(Factions.CUSTOM_NO_WAR_SIM)) {
1652                        MilitaryResponseParams params = new MilitaryResponseParams(ActionType.HOSTILE, 
1653                                        "player_ground_raid_" + market.getId(), 
1654                                        market.getFaction(),
1655                                        market.getPrimaryEntity(),
1656                                        0.75f,
1657                                        30f);
1658                        market.getContainingLocation().addScript(new MilitaryResponseScript(params));
1659                }
1660                List<CampaignFleetAPI> fleets = market.getContainingLocation().getFleets();
1661                for (CampaignFleetAPI other : fleets) {
1662                        if (other.getFaction() == market.getFaction()) {
1663                                MemoryAPI mem = other.getMemoryWithoutUpdate();
1664                                Misc.setFlagWithReason(mem, MemFlags.MEMORY_KEY_MAKE_HOSTILE_WHILE_TOFF, "raidAlarm", true, 1f);
1665                        }
1666                }
1667        }
1668        
1669        protected void raidConfirm(boolean secret) {
1670                if (temp.raidType == null) {
1671                        raidNeverMind();
1672                        return;
1673                }
1674                
1675                temp.secret = secret;
1676                
1677//              if (temp.raidType == RaidType.VALUABLE) {
1678//                      dialog.getVisualPanel().showImagePortion("illustrations", "raid_valuables_result", 640, 400, 0, 0, 480, 300);
1679//              } else if (temp.raidType == RaidType.DISRUPT) {
1680//                      dialog.getVisualPanel().showImagePortion("illustrations", "raid_disrupt_result", 640, 400, 0, 0, 480, 300);
1681//              }
1682                
1683                Random random = getRandom();
1684                //random = new Random();
1685                
1686                if (!DebugFlags.MARKET_HOSTILITIES_DEBUG) {
1687                        Misc.increaseMarketHostileTimeout(market, HOSTILE_ACTIONS_TIMEOUT_DAYS);
1688                }
1689                
1690                addMilitaryResponse();
1691                
1692                
1693                // if done here, increases marine casualties from this raid - move it down later
1694//              if (market != null) {
1695//                      applyDefenderIncreaseFromRaid(market);
1696//              }
1697                
1698                setRaidCooldown(getRaidCooldownMax());
1699
1700                //RecentUnrest.get(market).add(3, Misc.ucFirst(reason));
1701                int stabilityPenalty = 0;
1702                if (!temp.nonMarket) {
1703                        String reason = "Recently raided";
1704                        if (Misc.isPlayerFactionSetUp()) {
1705                                reason = playerFaction.getDisplayName() + " raid";
1706                        }
1707                        float raidMultForStabilityPenalty = temp.raidMult;
1708                        if (temp.objectives != null) {
1709                                float assignedTokens = 0f;
1710                                for (GroundRaidObjectivePlugin curr : temp.objectives) {
1711                                        assignedTokens += curr.getMarinesAssigned();
1712                                }
1713                                raidMultForStabilityPenalty = assignedTokens * 0.1f; 
1714                        }
1715                        
1716                        stabilityPenalty = applyRaidStabiltyPenalty(market, reason, raidMultForStabilityPenalty);
1717                        Misc.setFlagWithReason(market.getMemoryWithoutUpdate(), MemFlags.RECENTLY_RAIDED, 
1718                                                                   Factions.PLAYER, true, 30f);
1719                        Misc.setRaidedTimestamp(market);
1720                }
1721                
1722                int marines = playerFleet.getCargo().getMarines();
1723                float probOfLosses = 1f;
1724                
1725                int losses = 0;
1726                if (random.nextFloat() < probOfLosses) {
1727                        float averageLosses = getAverageMarineLosses(temp.objectives);
1728                        float variance = averageLosses / 4f;
1729                        
1730                        //float randomizedLosses = averageLosses - variance + variance * 2f * random.nextFloat();
1731                        float randomizedLosses = StarSystemGenerator.getNormalRandom(
1732                                                        random, averageLosses - variance, averageLosses + variance);
1733                        if (randomizedLosses < 1f) {
1734                                randomizedLosses = random.nextFloat() < randomizedLosses ? 1f : 0f;
1735                        }
1736                        randomizedLosses = Math.round(randomizedLosses);
1737                        losses = (int) randomizedLosses;
1738                        
1739                        if (losses < 0) losses = 0;
1740                        if (losses > marines) losses = marines;
1741                }
1742                
1743                //losses = random.nextInt(marines / 2);
1744                
1745                if (losses <= 0) {
1746                        text.addPara("Your forces have not suffered any casualties.");
1747                        temp.marinesLost = 0;
1748                } else {
1749                        text.addPara("You forces have suffered casualties during the raid.", Misc.getHighlightColor(), "" + losses);
1750                        playerFleet.getCargo().removeMarines(losses);
1751                        temp.marinesLost = losses;
1752                        AddRemoveCommodity.addCommodityLossText(Commodities.MARINES, losses, text);
1753                }
1754                
1755                
1756                if (!secret) {
1757                        boolean tOn = playerFleet.isTransponderOn();
1758                        boolean hostile = faction.isHostileTo(Factions.PLAYER);
1759                        CustomRepImpact impact = new CustomRepImpact();
1760                        if (market != null) {
1761                                impact.delta = market.getSize() * -0.01f * 1f;
1762                        } else {
1763                                impact.delta = -0.01f;
1764                        }
1765                        if (!hostile && tOn) {
1766                                impact.ensureAtBest = RepLevel.HOSTILE;
1767                        }
1768                        if (impact.delta != 0 && !faction.isNeutralFaction()) {
1769                                Global.getSector().adjustPlayerReputation(
1770                                                new RepActionEnvelope(RepActions.CUSTOM, 
1771                                                        impact, null, text, true, true),
1772                                                        faction.getId());
1773                        }
1774                }
1775                
1776                if (stabilityPenalty > 0) {
1777                        text.addPara("Stability of " + market.getName() + " reduced by %s.",
1778                                        Misc.getHighlightColor(), "" + stabilityPenalty);
1779                }
1780                
1781//              if (!temp.nonMarket) {
1782//                      if (temp.raidType == RaidType.VALUABLE || true) {
1783//                              text.addPara("The raid was successful in achieving its objectives.");
1784//                      }
1785//              }
1786                
1787                CargoAPI result = performRaid(random, temp.raidMult);
1788                
1789                if (market != null) market.reapplyIndustries();
1790                
1791                result.sort();
1792                result.updateSpaceUsed();
1793                
1794                temp.raidLoot = result;
1795                
1796//              int raidCredits = (int)result.getCredits().get();
1797//              if (raidCredits < 0) raidCredits = 0;
1798//              
1799//              //result.clear();
1800//              if (raidCredits > 0) {
1801//                      AddRemoveCommodity.addCreditsGainText(raidCredits, text);
1802//                      playerFleet.getCargo().getCredits().add(raidCredits);
1803//              }
1804                
1805                if (temp.xpGained > 0) {
1806                        Global.getSector().getPlayerStats().addXP(temp.xpGained, dialog.getTextPanel());
1807                }
1808                if (temp.raidType == RaidType.VALUABLE) {
1809                        if (result.getTotalCrew() + result.getSpaceUsed() + result.getFuel() < 10) {
1810                                dialog.getVisualPanel().showImagePortion("illustrations", "raid_covert_result", 640, 400, 0, 0, 480, 300);
1811                        } else {
1812                                dialog.getVisualPanel().showImagePortion("illustrations", "raid_valuables_result", 640, 400, 0, 0, 480, 300);
1813                        }
1814                } else if (temp.raidType == RaidType.DISRUPT) {
1815                        dialog.getVisualPanel().showImagePortion("illustrations", "raid_disrupt_result", 640, 400, 0, 0, 480, 300);
1816                }
1817                
1818                boolean withContinue = false;
1819                
1820                for (GroundRaidObjectivePlugin curr : temp.objectives) {
1821                        if (curr.withContinueBeforeResult()) {
1822                                withContinue = true;
1823                                break;
1824                        }
1825                }
1826                
1827                if (market != null) {
1828                        applyDefenderIncreaseFromRaid(market);
1829                }
1830                
1831//              if (market.getMemoryWithoutUpdate().getBoolean("$raid_showContinueBeforeResult"))
1832//              withContinue = true;
1833                
1834                if (withContinue) {
1835                        options.clearOptions();
1836                        options.addOption("Continue", RAID_CONFIRM_CONTINUE);
1837                } else {
1838                        raidConfirmContinue();
1839                }
1840        }
1841        
1842        public void raidConfirmContinue() {
1843                //Random random = getRandom();
1844                String contText = null;
1845                if (temp.raidType == RaidType.VALUABLE || true) {
1846                        if (!temp.nonMarket) {
1847                                if (temp.raidType == RaidType.VALUABLE || true) {
1848                                        //text.addPara("The raid was successful in achieving its objectives.");
1849                                }
1850                        }
1851                        
1852//                      CargoAPI result = performRaid(random, temp.raidMult);
1853//                      
1854//                      if (market != null) market.reapplyIndustries();
1855//                      
1856//                      result.sort();
1857//                      
1858//                      temp.raidLoot = result;
1859                        
1860                        int raidCredits = (int)temp.raidLoot.getCredits().get();
1861                        if (raidCredits < 0) raidCredits = 0;
1862                        
1863                        //result.clear();
1864                        if (raidCredits > 0) {
1865                                AddRemoveCommodity.addCreditsGainText(raidCredits, text);
1866                                playerFleet.getCargo().getCredits().add(raidCredits);
1867                        }
1868                        
1869//                      if (temp.xpGained > 0) {
1870//                              Global.getSector().getPlayerStats().addXP(temp.xpGained, dialog.getTextPanel());
1871//                      }
1872                        
1873                        if (!temp.raidLoot.isEmpty()) {
1874                                contText = "Pick through the spoils";
1875                        }
1876                        temp.contText = contText;
1877                        
1878                        float assignedTokens = 0f;
1879                        List<Industry> disrupted = new ArrayList<Industry>();
1880                        for (GroundRaidObjectivePlugin curr : temp.objectives) {
1881                                assignedTokens += curr.getMarinesAssigned();
1882                                if (curr instanceof DisruptIndustryRaidObjectivePluginImpl && curr.getSource() != null) {
1883                                        disrupted.add(curr.getSource());
1884                                }
1885                        }
1886                        
1887                        RaidResultData data = new RaidResultData();
1888                        data.market = market;
1889                        data.entity = entity;
1890                        data.objectives = temp.objectives;
1891                        data.type = temp.raidType;
1892                        data.raidEffectiveness = temp.raidMult;
1893                        data.xpGained = temp.xpGained;
1894                        data.marinesTokensInReserve = (int) Math.round(getNumMarineTokens() - assignedTokens);
1895                        data.marinesTokens = getNumMarineTokens();
1896                        data.marinesLost = temp.marinesLost;
1897                        
1898                        ListenerUtil.reportRaidObjectivesAchieved(data, dialog, memoryMap);
1899                        
1900                        if (temp.raidType == RaidType.VALUABLE) {
1901                                ListenerUtil.reportRaidForValuablesFinishedBeforeCargoShown(dialog, market, temp, temp.raidLoot);
1902                        } else if (temp.raidType == RaidType.DISRUPT) {
1903                                for (Industry curr : disrupted) {
1904                                        ListenerUtil.reportRaidToDisruptFinished(dialog, market, temp, curr);
1905                                }
1906                        }
1907                        
1908                }
1909                
1910                Global.getSoundPlayer().playUISound("ui_raid_finished", 1f, 1f);
1911                
1912                FireBest.fire(null, dialog, memoryMap, "PostGroundRaid");
1913        }
1914        
1915        protected CargoAPI performRaid(Random random, float raidEffectiveness) {
1916                CargoAPI result = Global.getFactory().createCargo(true);
1917                
1918                float leftoverRE = (int)Math.round(raidEffectiveness * 100f) % (int)Math.round(RE_PER_MARINE_TOKEN * 100f);
1919                leftoverRE /= 100f;
1920                if (raidEffectiveness < RE_PER_MARINE_TOKEN) {
1921                        //leftoverRE = leftoverRE - RE_PER_MARINE_TOKEN;
1922                        leftoverRE = 0f;
1923                }
1924                
1925                long baseSeed = random.nextLong();
1926                
1927                int xp = 0;
1928                for (GroundRaidObjectivePlugin plugin : temp.objectives) {
1929                        float lootMult = 1f + leftoverRE / Math.max(RE_PER_MARINE_TOKEN, raidEffectiveness);
1930                        
1931                        Random curr = new Random(Misc.seedUniquifier() ^ (baseSeed * plugin.getClass().getName().hashCode()));
1932                        xp += plugin.performRaid(result, curr, lootMult, dialog.getTextPanel());
1933                }
1934
1935                temp.xpGained = xp;
1936                
1937                return result;
1938        }
1939        
1940        
1941        protected void raidNeverMind() {
1942                if (temp.nonMarket) {
1943                        raidNonMarket();
1944                } else {
1945                        raidMenu();
1946                }
1947        }
1948        
1949        
1950        protected void raidShowLoot() {
1951                dialog.getVisualPanel().showLoot("Spoils", temp.raidLoot, false, true, true, new CoreInteractionListener() {
1952                        public void coreUIDismissed() {
1953                                //dialog.dismiss();
1954                                finishedRaidOrBombard();
1955                        }
1956                });
1957        }
1958        
1959        
1960        protected void printStationState() {
1961                StationState state = getStationState();
1962                if (state == StationState.REPAIRS || state == StationState.UNDER_CONSTRUCTION) {
1963                        CampaignFleetAPI fleet = Misc.getStationBaseFleet(market);
1964                        String name = "orbital station";
1965                        if (fleet != null) {
1966                                FleetMemberAPI flagship = fleet.getFlagship();
1967                                if (flagship != null) {
1968                                        name = flagship.getVariant().getDesignation().toLowerCase();
1969                                }
1970                        }
1971                        if (state == StationState.REPAIRS) {
1972                                text.addPara("The " + name + " has suffered extensive damage and is not currently combat-capable.");
1973                        } else {
1974                                text.addPara("The " + name + " is under construction and is not currently combat-capable.");
1975                        }
1976                }
1977        }
1978
1979        
1980        protected void engage() {
1981                final SectorEntityToken entity = dialog.getInteractionTarget();
1982                final MemoryAPI memory = getEntityMemory(memoryMap);
1983
1984                final CampaignFleetAPI primary = getInteractionTargetForFIDPI();
1985                
1986                dialog.setInteractionTarget(primary);
1987                
1988                final FIDConfig config = new FIDConfig();
1989                config.leaveAlwaysAvailable = true;
1990                config.showCommLinkOption = false;
1991                config.showEngageText = false;
1992                config.showFleetAttitude = false;
1993                config.showTransponderStatus = false;
1994                //config.showWarningDialogWhenNotHostile = false;
1995                config.alwaysAttackVsAttack = true;
1996                config.impactsAllyReputation = true;
1997//              config.impactsEnemyReputation = false;
1998//              config.pullInAllies = false;
1999//              config.pullInEnemies = false;
2000//              config.lootCredits = false;
2001                
2002//              config.firstTimeEngageOptionText = "Engage the automated defenses";
2003//              config.afterFirstTimeEngageOptionText = "Re-engage the automated defenses";
2004                config.noSalvageLeaveOptionText = "Continue";
2005                
2006                config.dismissOnLeave = false;
2007                config.printXPToDialog = true;
2008                
2009                config.straightToEngage = true;
2010                
2011                CampaignFleetAPI station = getStationFleet();
2012                config.playerAttackingStation = station != null;
2013                
2014                final FleetInteractionDialogPluginImpl plugin = new FleetInteractionDialogPluginImpl(config);
2015                
2016                final InteractionDialogPlugin originalPlugin = dialog.getPlugin();
2017                config.delegate = new BaseFIDDelegate() {
2018                        @Override
2019                        public void notifyLeave(InteractionDialogAPI dialog) {
2020                                if (primary.isStationMode()) {
2021                                        primary.getMemoryWithoutUpdate().clear();
2022                                        primary.clearAssignments();
2023                                        //primary.deflate();
2024                                }
2025                                
2026                                dialog.setPlugin(originalPlugin);
2027                                dialog.setInteractionTarget(entity);
2028                                
2029                                boolean quickExit = entity.hasTag(Tags.NON_CLICKABLE);
2030                                
2031                                if (!Global.getSector().getPlayerFleet().isValidPlayerFleet() || quickExit) {
2032                                        dialog.getOptionPanel().clearOptions();
2033                                        dialog.getOptionPanel().addOption("Leave", "marketLeave");
2034                                        dialog.getOptionPanel().setShortcut("marketLeave", Keyboard.KEY_ESCAPE, false, false, false, true);
2035        
2036                                        dialog.showTextPanel();
2037                                        dialog.setPromptText("You decide to...");
2038                                        dialog.getVisualPanel().finishFadeFast();
2039                                        text.updateSize();
2040                                        
2041//                                      dialog.hideVisualPanel();
2042//                                      dialog.getVisualPanel().finishFadeFast();
2043//                                      dialog.hideTextPanel();
2044//                                      dialog.dismiss();
2045                                        return;
2046                                }
2047                                
2048                                if (plugin.getContext() instanceof FleetEncounterContext) {
2049                                        FleetEncounterContext context = (FleetEncounterContext) plugin.getContext();
2050                                        if (context.didPlayerWinMostRecentBattleOfEncounter()) {
2051                                                // may need to do something here re: station being defeated & timed out
2052                                                //FireBest.fire(null, dialog, memoryMap, "BeatDefendersContinue");
2053                                        } else {
2054                                                //dialog.dismiss();
2055                                        }
2056                                        
2057                                        if (context.isEngagedInHostilities()) {
2058                                                dialog.getInteractionTarget().getMemoryWithoutUpdate().set("$tradeMode", "NONE", 0);
2059                                        }
2060                                        
2061                                        showDefenses(context.isEngagedInHostilities());
2062                                } else {
2063                                        showDefenses(false);
2064                                }
2065                                dialog.getVisualPanel().finishFadeFast();
2066                                
2067                                //dialog.dismiss();
2068                        }
2069                        @Override
2070                        public void battleContextCreated(InteractionDialogAPI dialog, BattleCreationContext bcc) {
2071                                //bcc.aiRetreatAllowed = false;
2072                                bcc.objectivesAllowed = false;
2073                        }
2074                        @Override
2075                        public void postPlayerSalvageGeneration(InteractionDialogAPI dialog, FleetEncounterContext context, CargoAPI salvage) {
2076                        }
2077                        
2078                };
2079                
2080                dialog.setPlugin(plugin);
2081                plugin.init(dialog);
2082                
2083        }
2084
2085        protected CampaignFleetAPI getStationFleet() {
2086                CampaignFleetAPI station = Misc.getStationFleet(market);
2087                if (station == null) return null;
2088                
2089                if (station.getFleetData().getMembersListCopy().isEmpty()) return null;
2090                
2091                return station;
2092        }
2093        
2094        protected CampaignFleetAPI getInteractionTargetForFIDPI() {
2095                CampaignFleetAPI primary = getStationFleet();
2096                if (primary == null) {
2097                        CampaignFleetAPI best = null;
2098                        float minDist = Float.MAX_VALUE;
2099                        for (CampaignFleetAPI fleet : Misc.getNearbyFleets(entity, 2000)) {
2100                                if (fleet.getBattle() != null) continue;
2101                                
2102                                if (fleet.getFaction() != market.getFaction()) continue;
2103                                if (fleet.getFleetData().getNumMembers() <= 0) continue;
2104                                
2105                                float dist = Misc.getDistance(entity.getLocation(), fleet.getLocation());
2106                                dist -= entity.getRadius();
2107                                dist -= fleet.getRadius();
2108                                
2109                                if (dist < Misc.getBattleJoinRange() ) {
2110                                        if (dist < minDist) {
2111                                                best = fleet;
2112                                                minDist = dist;
2113                                        }
2114                                }
2115                        }
2116                        primary = best;
2117                } else {
2118                        //primary.setLocation(entity.getLocation().x, entity.getLocation().y);
2119                }
2120                return primary;
2121        }
2122        
2123        public static enum StationState {
2124                NONE,
2125                OPERATIONAL,
2126                UNDER_CONSTRUCTION,
2127                REPAIRS
2128        }
2129        
2130        protected StationState getStationState() {
2131                CampaignFleetAPI fleet = Misc.getStationFleet(market);
2132                boolean destroyed = false;
2133                if (fleet == null) {
2134                        fleet = Misc.getStationBaseFleet(market);
2135                        if (fleet != null) {
2136                                destroyed = true;
2137                        }
2138                }
2139                
2140                if (fleet == null) return StationState.NONE;
2141                
2142                MarketAPI market = Misc.getStationMarket(fleet);
2143                if (market != null) {
2144                        for (Industry ind : market.getIndustries()) {
2145                                if (ind.getSpec().hasTag(Industries.TAG_STATION)) {
2146                                        if (ind.isBuilding() && !ind.isDisrupted() && !ind.isUpgrading()) {
2147                                                return StationState.UNDER_CONSTRUCTION;
2148                                        }
2149                                }
2150                        }
2151                }
2152                
2153                if (destroyed) return StationState.REPAIRS;
2154                
2155                return StationState.OPERATIONAL;
2156        }
2157        
2158        
2159        public static int applyRaidStabiltyPenalty(MarketAPI target, String desc, float re) {
2160                int penalty = 0;
2161                if (re >= 0.79f) penalty = 3;
2162                else if (re >= 0.59f) penalty = 2;
2163                else if (re >= 0.29f) penalty = 1;
2164                if (penalty > 0) {
2165                        RecentUnrest.get(target).add(penalty, desc);
2166                }
2167                return penalty;
2168        }
2169        
2170        public static int applyRaidStabiltyPenalty(MarketAPI target, String desc, float re, float maxPenalty) {
2171                int penalty = Math.round((0.45f + maxPenalty) * re);
2172                if (penalty > 0) {
2173                        RecentUnrest.get(target).add(penalty, desc);
2174                }
2175                return penalty;
2176        }
2177        
2178        
2179        public static StatModValueGetter statPrinter(final boolean withNegative) {
2180                return new StatModValueGetter() {
2181                        public String getPercentValue(StatMod mod) {
2182                                String prefix = mod.getValue() > 0 ? "+" : "";
2183                                return prefix + (int)(mod.getValue()) + "%";
2184                        }
2185                        public String getMultValue(StatMod mod) {
2186                                return Strings.X + "" + Misc.getRoundedValue(mod.getValue());
2187                        }
2188                        public String getFlatValue(StatMod mod) {
2189                                String prefix = mod.getValue() > 0 ? "+" : "";
2190                                return prefix + (int)(mod.getValue()) + "";
2191                        }
2192                        public Color getModColor(StatMod mod) {
2193                                if (withNegative && mod.getValue() < 1f) return Misc.getNegativeHighlightColor();
2194                                return null;
2195                        }
2196                };
2197        }
2198        
2199        
2200        public static int getBombardmentCost(MarketAPI market, CampaignFleetAPI fleet) {
2201                float str = getDefenderStr(market, true);
2202                int result = (int) (str * Global.getSettings().getFloat("bombardFuelFraction"));
2203                if (result < 2) result = 2;
2204                if (fleet != null) {
2205                        float bomardBonus = Misc.getFleetwideTotalMod(fleet, Stats.FLEET_BOMBARD_COST_REDUCTION, 0f);
2206                        result -= bomardBonus;
2207                        if (result < 0) result = 0;
2208                }
2209                return result;
2210        }
2211        
2212        public static int getTacticalBombardmentStabilityPenalty() {
2213                return (int) Global.getSettings().getFloat("bombardTacticalStability");
2214        }
2215        public static int getSaturationBombardmentStabilityPenalty() {
2216                return (int) Global.getSettings().getFloat("bombardSaturationStability");
2217        }
2218        
2219
2220        protected void bombardMenu() {
2221                float width = 350;
2222                float opad = 10f;
2223                float small = 5f;
2224                
2225                Color h = Misc.getHighlightColor();
2226                Color b = Misc.getNegativeHighlightColor();
2227                
2228                dialog.getVisualPanel().showImagePortion("illustrations", "bombard_prepare", 640, 400, 0, 0, 480, 300);
2229
2230                StatBonus defender = market.getStats().getDynamic().getMod(Stats.GROUND_DEFENSES_MOD);
2231                
2232                float bomardBonus = Misc.getFleetwideTotalMod(playerFleet, Stats.FLEET_BOMBARD_COST_REDUCTION, 0f);
2233                String increasedBombardKey = "core_addedBombard";
2234                StatBonus bombardBonusStat = new StatBonus();
2235                if (bomardBonus > 0) {
2236                        bombardBonusStat.modifyFlat(increasedBombardKey, -bomardBonus, "Specialized fleet bombardment capability");
2237                }
2238                
2239                float defenderStr = (int) Math.round(defender.computeEffective(0f));
2240                defenderStr -= bomardBonus;
2241                if (defenderStr < 0) defenderStr = 0;
2242                
2243                temp.defenderStr = defenderStr;
2244                
2245                TooltipMakerAPI info = text.beginTooltip();
2246                
2247                info.setParaSmallInsignia();
2248                
2249                String has = faction.getDisplayNameHasOrHave();
2250                String is = faction.getDisplayNameIsOrAre();
2251                boolean hostile = faction.isHostileTo(Factions.PLAYER);
2252                boolean tOn = playerFleet.isTransponderOn();
2253                float initPad = 0f;
2254                if (!hostile) {
2255                        info.addPara(Misc.ucFirst(faction.getDisplayNameWithArticle()) + " " + is + 
2256                                        " not currently hostile. A bombardment is a major enough hostile action that it can't be concealed, " +
2257                                        "regardless of transponder status.",
2258                                        initPad, faction.getBaseUIColor(), faction.getDisplayNameWithArticleWithoutArticle());
2259                        initPad = opad;
2260                }
2261
2262                info.addPara("Starship fuel can be easily destabilized, unlocking the destructive " +
2263                                "potential of the antimatter it contains. Ground defenses can counter " +
2264                                "a bombardment, though in practice it only means that more fuel is required to achieve " +
2265                                "the same result.", initPad);
2266                                
2267                
2268                if (bomardBonus > 0) {
2269                        info.addPara("Effective ground defense strength: %s", opad, h, "" + (int)defenderStr);
2270                } else {
2271                        info.addPara("Ground defense strength: %s", opad, h, "" + (int)defenderStr);
2272                }
2273                info.addStatModGrid(width, 50, opad, small, defender, true, statPrinter(true));
2274                if (!bombardBonusStat.isUnmodified()) {
2275                        info.addStatModGrid(width, 50, opad, 3f, bombardBonusStat, true, statPrinter(false));
2276                }
2277                
2278                text.addTooltip();
2279
2280//              text.addPara("A tactical bombardment will only hit military targets and costs less fuel. A saturation " +
2281//                              "bombardment will devastate the whole colony, and only costs marginally more fuel, as the non-military " +
2282//                              "targets don't have nearly the same degree of hardening.");
2283                
2284                temp.bombardCost = getBombardmentCost(market, playerFleet);
2285                
2286                int fuel = (int) playerFleet.getCargo().getFuel();
2287                boolean canBombard = fuel >= temp.bombardCost;
2288                
2289                LabelAPI label = text.addPara("A bombardment requires %s fuel. " +
2290                                                                          "You have %s fuel.",
2291                                h, "" + temp.bombardCost, "" + fuel);
2292                label.setHighlight("" + temp.bombardCost, "" + fuel);
2293                label.setHighlightColors(canBombard ? h : b, h);
2294
2295                options.clearOptions();
2296                
2297                options.addOption("Prepare a tactical bombardment", BOMBARD_TACTICAL);
2298                options.addOption("Prepare a saturation bombardment", BOMBARD_SATURATION);
2299                
2300                if (DebugFlags.MARKET_HOSTILITIES_DEBUG) {
2301                        canBombard = true;
2302                }
2303                if (!canBombard) {
2304                        options.setEnabled(BOMBARD_TACTICAL, false);
2305                        options.setTooltip(BOMBARD_TACTICAL, "Not enough fuel.");
2306                        options.setEnabled(BOMBARD_SATURATION, false);
2307                        options.setTooltip(BOMBARD_SATURATION, "Not enough fuel.");
2308                }
2309
2310                options.addOption("Go back", RAID_GO_BACK);
2311                options.setShortcut(RAID_GO_BACK, Keyboard.KEY_ESCAPE, false, false, false, true);
2312        }
2313        
2314        
2315        protected void addConfirmOptions() {
2316                options.clearOptions();
2317//              if (temp.isSurpriseRaid) {
2318//                      options.addOption("Launch surprise raid", RAID_CONFIRM);
2319//              } else {
2320                        //options.addOption("Launch full-scale raid", RAID_CONFIRM);
2321                        options.addOption("Launch raid", RAID_CONFIRM);
2322//              }
2323                
2324                boolean tOn = playerFleet.isTransponderOn();
2325                        
2326                //if (!temp.nonMarket) {
2327                if (market != null && !market.isPlanetConditionMarketOnly()) {
2328                        options.addOption("Make special efforts to keep your preparations secret, then proceed", RAID_CONFIRM_STORY);
2329                        String req = "";
2330                        if (tOn) {
2331                                req = "\n\nRequires transponder to be turned off";
2332                                options.setEnabled(RAID_CONFIRM_STORY, false);
2333                        }
2334                        options.setTooltip(RAID_CONFIRM_STORY, "Suffer no penalty to your standing with " + market.getFaction().getDisplayNameWithArticle() + ". " +
2335                                        "Will not help if forced to turn your transponder on by patrols arriving to investigate the raid." + req);
2336                        options.setTooltipHighlightColors(RAID_CONFIRM_STORY, market.getFaction().getBaseUIColor(), Misc.getNegativeHighlightColor());
2337                        options.setTooltipHighlights(RAID_CONFIRM_STORY, market.getFaction().getDisplayNameWithArticleWithoutArticle(), req.isEmpty() ? req : req.substring(2));
2338                        StoryOptionParams params = new StoryOptionParams(RAID_CONFIRM_STORY, 1, "noRepPenaltyRaid", Sounds.STORY_POINT_SPEND_LEADERSHIP,
2339                                        "Secretly raided " + market.getName() + "");
2340                        SetStoryOption.set(dialog, params, 
2341                                new BaseOptionStoryPointActionDelegate(dialog, params) {
2342                                        @Override
2343                                        public void confirm() {
2344                                                super.confirm();
2345                                                raidConfirm(true);
2346                                        }
2347                        });
2348                }
2349                        
2350                        
2351                options.addOption("Never mind", RAID_NEVER_MIND);
2352                options.setShortcut(RAID_NEVER_MIND, Keyboard.KEY_ESCAPE, false, false, false, true);
2353                
2354                boolean hostile = faction.isHostileTo(Factions.PLAYER);
2355                if (tOn && !hostile && !faction.isNeutralFaction()) {
2356                        options.addOptionConfirmation(RAID_CONFIRM,
2357                                        "The " + faction.getDisplayNameLong() + 
2358                                        " " + faction.getDisplayNameIsOrAre() + 
2359                                        " not currently hostile, and you have been positively identified. " +
2360                                        "Are you sure you want to engage in open hostilities?", "Yes", "Never mind");
2361                }
2362        }
2363        
2364        public static List<Industry> getTacticalBombardmentTargets(MarketAPI market) {
2365                int dur = getBombardDisruptDuration();
2366                List<Industry> targets = new ArrayList<Industry>();
2367                for (Industry ind : market.getIndustries()) {
2368                        if (ind.getSpec().hasTag(Industries.TAG_TACTICAL_BOMBARDMENT)) {
2369                                if (ind.getDisruptedDays() >= dur * 0.8f) continue;
2370                                targets.add(ind);
2371                        }
2372                }
2373                return targets;
2374        }
2375        
2376        protected void bombardTactical() {
2377                
2378                temp.bombardType = BombardType.TACTICAL; 
2379                
2380                boolean hostile = faction.isHostileTo(Factions.PLAYER);
2381                temp.willBecomeHostile.clear();
2382                temp.willBecomeHostile.add(faction);    
2383                
2384                float opad = 10f;
2385                float small = 5f;
2386                
2387                Color h = Misc.getHighlightColor();
2388                Color b = Misc.getNegativeHighlightColor();
2389                
2390                
2391                int dur = getBombardDisruptDuration();
2392                
2393                List<Industry> targets = getTacticalBombardmentTargets(market);
2394                temp.bombardmentTargets.clear();
2395                temp.bombardmentTargets.addAll(targets);
2396                
2397                if (targets.isEmpty()) {
2398                        text.addPara(market.getName() + " does not have any undisrupted military targets that would be affected by a tactical bombardment.");
2399                        addBombardNeverMindOption();
2400                        return; 
2401                }
2402                
2403                
2404                int fuel = (int) playerFleet.getCargo().getFuel();
2405                text.addPara("A tactical bombardment will destabilize the colony, and will also disrupt the " +
2406                                "following military targets for approximately %s days:",
2407                                         h, "" + dur);
2408                
2409                TooltipMakerAPI info = text.beginTooltip();
2410                
2411                info.setParaSmallInsignia();
2412                info.setParaFontDefault();
2413                
2414                info.setBulletedListMode(BaseIntelPlugin.INDENT);
2415                float initPad = 0f;
2416                for (Industry ind : targets) {
2417                        //info.addPara(ind.getCurrentName(), faction.getBaseUIColor(), initPad);
2418                        info.addPara(ind.getCurrentName(), initPad);
2419                        initPad = 3f;
2420                }
2421                info.setBulletedListMode(null);
2422                
2423                text.addTooltip();
2424                
2425                text.addPara("The bombardment requires %s fuel. " +
2426                                         "You have %s fuel.",
2427                                         h, "" + temp.bombardCost, "" + fuel);
2428                
2429                addBombardConfirmOptions();
2430        }
2431        
2432        protected void bombardSaturation() {
2433                
2434                temp.bombardType = BombardType.SATURATION;
2435
2436                temp.willBecomeHostile.clear();
2437                temp.willBecomeHostile.add(faction);
2438                
2439                List<FactionAPI> nonHostile = new ArrayList<FactionAPI>();
2440                nonHostile.add(faction);
2441                for (FactionAPI faction : Global.getSector().getAllFactions()) {
2442                        if (temp.willBecomeHostile.contains(faction)) continue;
2443                        if (faction.getCustomBoolean(Factions.CUSTOM_CARES_ABOUT_ATROCITIES)) {
2444                                boolean hostile = faction.isHostileTo(Factions.PLAYER);
2445                                temp.willBecomeHostile.add(faction);
2446                                if (!hostile) {
2447                                        nonHostile.add(faction);
2448                                }
2449                        }
2450                        
2451                }
2452                
2453                float opad = 10f;
2454                float small = 5f;
2455                
2456                Color h = Misc.getHighlightColor();
2457                Color b = Misc.getNegativeHighlightColor();
2458                
2459                
2460                int dur = getBombardDisruptDuration();
2461                
2462                List<Industry> targets = new ArrayList<Industry>();
2463                for (Industry ind : market.getIndustries()) {
2464                        if (!ind.getSpec().hasTag(Industries.TAG_NO_SATURATION_BOMBARDMENT)) {
2465                                if (ind.getDisruptedDays() >= dur * 0.8f) continue;
2466                                targets.add(ind);
2467                        }
2468                }
2469                temp.bombardmentTargets.clear();
2470                temp.bombardmentTargets.addAll(targets);
2471                
2472                boolean destroy = market.getSize() <= getBombardDestroyThreshold();
2473                if (Misc.isStoryCritical(market)) destroy = false;
2474                
2475                int fuel = (int) playerFleet.getCargo().getFuel();
2476                if (destroy) {
2477                        text.addPara("A saturation bombardment of a colony this size will destroy it utterly.");
2478                } else {
2479                        text.addPara("A saturation bombardment will destabilize the colony, reduce its population, " +
2480                                        "and disrupt all operations for a long time.");
2481                }
2482                
2483                
2484//              TooltipMakerAPI info = text.beginTooltip();
2485//              info.setParaFontDefault();
2486//              
2487//              info.setBulletedListMode(BaseIntelPlugin.INDENT);
2488//              float initPad = 0f;
2489//              for (Industry ind : targets) {
2490//                      //info.addPara(ind.getCurrentName(), faction.getBaseUIColor(), initPad);
2491//                      info.addPara(ind.getCurrentName(), initPad);
2492//                      initPad = 3f;
2493//              }
2494//              info.setBulletedListMode(null);
2495//              
2496//              text.addTooltip();
2497                
2498
2499                if (nonHostile.isEmpty()) {
2500                        text.addPara("An atrocity of this scale can not be hidden, but any factions that would " +
2501                                        "be dismayed by such actions are already hostile to you.");
2502                } else {
2503                        text.addPara("An atrocity of this scale can not be hidden, " +
2504                                                 "and will make the following factions hostile:");
2505                }
2506                
2507                if (!nonHostile.isEmpty()) {
2508                        TooltipMakerAPI info = text.beginTooltip();
2509                        info.setParaFontDefault();
2510                        
2511                        info.setBulletedListMode(BaseIntelPlugin.INDENT);
2512                        float initPad = 0f;
2513                        for (FactionAPI fac : nonHostile) {
2514                                info.addPara(Misc.ucFirst(fac.getDisplayName()), fac.getBaseUIColor(), initPad);
2515                                initPad = 3f;
2516                        }
2517                        info.setBulletedListMode(null);
2518                        
2519                        text.addTooltip();
2520                }
2521                
2522                text.addPara("The bombardment requires %s fuel. " +
2523                                         "You have %s fuel.",
2524                                         h, "" + temp.bombardCost, "" + fuel);
2525                
2526                addBombardConfirmOptions();
2527        }
2528        
2529        protected void bombardConfirm() {
2530                
2531                if (temp.bombardType == null) {
2532                        bombardNeverMind();
2533                        return;
2534                }
2535                
2536                if (temp.bombardType == BombardType.TACTICAL) {
2537                        dialog.getVisualPanel().showImagePortion("illustrations", "bombard_tactical_result", 640, 400, 0, 0, 480, 300);
2538                } else {
2539                        dialog.getVisualPanel().showImagePortion("illustrations", "bombard_saturation_result", 640, 400, 0, 0, 480, 300);
2540                }
2541                
2542                Random random = getRandom();
2543                
2544                if (!DebugFlags.MARKET_HOSTILITIES_DEBUG) {
2545                        float timeout = TACTICAL_BOMBARD_TIMEOUT_DAYS;
2546                        if (temp.bombardType == BombardType.SATURATION) {
2547                                timeout = SATURATION_BOMBARD_TIMEOUT_DAYS;
2548                        }
2549                        Misc.increaseMarketHostileTimeout(market, timeout);
2550                        
2551                        timeout *= 0.7f;
2552                        
2553                        for (MarketAPI curr : Global.getSector().getEconomy().getMarkets(market.getContainingLocation())) {
2554                                if (curr == market) continue;
2555                                boolean cares = curr.getFaction().getCustomBoolean(Factions.CUSTOM_CARES_ABOUT_ATROCITIES);
2556                                cares &= temp.bombardType == BombardType.SATURATION;
2557                                
2558                                if (curr.getFaction().isNeutralFaction()) continue;
2559                                if (curr.getFaction().isPlayerFaction()) continue;
2560                                if (curr.getFaction().isHostileTo(market.getFaction()) && !cares) continue;
2561                                
2562                                Misc.increaseMarketHostileTimeout(curr, timeout);
2563                        }
2564                }
2565                
2566                addMilitaryResponse();
2567                
2568                playerFleet.getCargo().removeFuel(temp.bombardCost);
2569                AddRemoveCommodity.addCommodityLossText(Commodities.FUEL, temp.bombardCost, text);
2570        
2571                for (FactionAPI curr : temp.willBecomeHostile) {
2572                        CustomRepImpact impact = new CustomRepImpact();
2573                        impact.delta = market.getSize() * -0.01f * 1f;
2574                        impact.ensureAtBest = RepLevel.HOSTILE;
2575                        if (temp.bombardType == BombardType.SATURATION) {
2576                                if (curr == faction) {
2577                                        impact.ensureAtBest = RepLevel.VENGEFUL;
2578                                }
2579                                impact.delta = market.getSize() * -0.01f * 1f;
2580                        }
2581                        Global.getSector().adjustPlayerReputation(
2582                                new RepActionEnvelope(RepActions.CUSTOM, 
2583                                        impact, null, text, true, true),
2584                                        curr.getId());
2585                }
2586        
2587                if (temp.bombardType == BombardType.SATURATION) {
2588                        int atrocities = (int) Global.getSector().getCharacterData().getMemoryWithoutUpdate().getFloat(MemFlags.PLAYER_ATROCITIES);
2589                        atrocities++;
2590                        Global.getSector().getCharacterData().getMemoryWithoutUpdate().set(MemFlags.PLAYER_ATROCITIES, atrocities);
2591                        
2592                        if (market != null && market.getFaction() != null) {
2593                                MemoryAPI mem = market.getFaction().getMemoryWithoutUpdate();
2594                                int count = mem.getInt(MemFlags.FACTION_SATURATION_BOMBARED_BY_PLAYER);
2595                                count++;
2596                                mem.set(MemFlags.FACTION_SATURATION_BOMBARED_BY_PLAYER, count);
2597                        }
2598                }
2599                
2600                
2601                int stabilityPenalty = getTacticalBombardmentStabilityPenalty();
2602                if (temp.bombardType == BombardType.SATURATION) {
2603                        stabilityPenalty = getSaturationBombardmentStabilityPenalty();
2604                }
2605                boolean destroy = temp.bombardType == BombardType.SATURATION && market.getSize() <= getBombardDestroyThreshold();
2606                if (Misc.isStoryCritical(market)) destroy = false;
2607                
2608                if (stabilityPenalty > 0 && !destroy) {
2609                        String reason = "Recently bombarded";
2610                        if (Misc.isPlayerFactionSetUp()) {
2611                                reason = playerFaction.getDisplayName() + " bombardment";
2612                        }
2613                        RecentUnrest.get(market).add(stabilityPenalty, reason);
2614                        text.addPara("Stability of " + market.getName() + " reduced by %s.",
2615                                        Misc.getHighlightColor(), "" + stabilityPenalty);
2616                }
2617                
2618                if (market.hasCondition(Conditions.HABITABLE) && !market.hasCondition(Conditions.POLLUTION)) {
2619                        market.addCondition(Conditions.POLLUTION);
2620                }
2621                
2622                if (!destroy) {
2623                        for (Industry curr : temp.bombardmentTargets) {
2624                                int dur = getBombardDisruptDuration();
2625                                dur *= StarSystemGenerator.getNormalRandom(random, 1f, 1.25f);
2626                                curr.setDisrupted(dur);
2627                        }
2628                }
2629                
2630                
2631                
2632                if (temp.bombardType == BombardType.TACTICAL) {
2633                        text.addPara("Military operations disrupted.");
2634                        
2635                        ListenerUtil.reportTacticalBombardmentFinished(dialog, market, temp);
2636                } else if (temp.bombardType == BombardType.SATURATION) {
2637                        if (destroy) {
2638                                DecivTracker.decivilize(market, true);
2639                                text.addPara(market.getName() + " destroyed.");
2640                        } else {
2641                                int prevSize = market.getSize();
2642                                CoreImmigrationPluginImpl.reduceMarketSize(market);
2643                                if (prevSize == market.getSize()) {
2644                                        text.addPara("All operations disrupted.");
2645                                } else {
2646                                        text.addPara("All operations disrupted. Colony size reduced to %s.", 
2647                                                        Misc.getHighlightColor()
2648                                                        , "" + market.getSize());
2649                                }
2650                                
2651                        }
2652                        ListenerUtil.reportSaturationBombardmentFinished(dialog, market, temp);
2653                }
2654                
2655                if (dialog != null && dialog.getPlugin() instanceof RuleBasedDialog) {
2656                        if (dialog.getInteractionTarget() != null &&
2657                                        dialog.getInteractionTarget().getMarket() != null) {
2658                                Global.getSector().setPaused(false);
2659                                dialog.getInteractionTarget().getMarket().getMemoryWithoutUpdate().advance(0.0001f);
2660                                Global.getSector().setPaused(true);
2661                        }
2662                        ((RuleBasedDialog) dialog.getPlugin()).updateMemory();
2663                }
2664                
2665                Misc.setFlagWithReason(market.getMemoryWithoutUpdate(), MemFlags.RECENTLY_BOMBARDED, 
2666                                                          Factions.PLAYER, true, 30f);
2667
2668                if (destroy) {
2669                        if (dialog != null && dialog.getPlugin() instanceof RuleBasedDialog) {
2670                                ((RuleBasedDialog) dialog.getPlugin()).updateMemory();
2671//                              market.getMemoryWithoutUpdate().unset("$tradeMode");
2672//                              entity.getMemoryWithoutUpdate().unset("$tradeMode");
2673                        }
2674                }
2675                
2676                addBombardVisual(market.getPrimaryEntity());
2677                
2678                addBombardContinueOption();
2679        }
2680        
2681        
2682        protected void bombardNeverMind() {
2683                bombardMenu();          
2684        }
2685
2686        protected void raidResult() {
2687                if (temp.raidLoot != null) {
2688                        if (temp.raidLoot.isEmpty()) {
2689//                              clearTemp();
2690//                              showDefenses(true);
2691                                //dialog.dismiss();
2692                                finishedRaidOrBombard();
2693                        } else {
2694                                raidShowLoot();
2695                        }
2696                        return;
2697                } else {
2698                        //dialog.dismiss();
2699                        finishedRaidOrBombard();
2700                }
2701        }
2702        
2703        protected void bombardResult() {
2704                //dialog.dismiss();
2705                finishedRaidOrBombard();
2706        }
2707        
2708        protected void finishedRaidOrBombard() {
2709                //showDefenses(true);
2710        
2711                new ShowDefaultVisual().execute(null, dialog, Misc.tokenize(""), memoryMap);
2712                
2713                //FireAll.fire(null, dialog, memoryMap, "MarketPostOpen");
2714                dialog.getInteractionTarget().getMemoryWithoutUpdate().set("$menuState", "main", 0);
2715                if (dialog.getInteractionTarget().getMemoryWithoutUpdate().contains("$tradeMode")) {
2716                        if (market.isPlanetConditionMarketOnly()) {
2717                                dialog.getInteractionTarget().getMemoryWithoutUpdate().unset("$hasMarket");
2718                        }
2719                        dialog.getInteractionTarget().getMemoryWithoutUpdate().set("$tradeMode", "NONE", 0);
2720                } else {
2721                        // station that's now abandoned
2722                        dialog.getInteractionTarget().getMemoryWithoutUpdate().set("$tradeMode", "OPEN", 0);
2723                }
2724                
2725                if (temp.nonMarket) {
2726                        String trigger = temp.raidContinueTrigger;
2727                        if (trigger == null || trigger.isEmpty()) trigger = "OpenInteractionDialog";
2728                        FireAll.fire(null, dialog, memoryMap, trigger);
2729                } else {
2730                        FireAll.fire(null, dialog, memoryMap, "PopulateOptions");
2731                }
2732                
2733                clearTemp();
2734        }
2735        
2736        protected void addBombardContinueOption() {
2737                addBombardContinueOption(null);
2738        }
2739        protected void addBombardContinueOption(String text) {
2740                if (text == null) text = "Continue";
2741                options.clearOptions();
2742                options.addOption(text, BOMBARD_RESULT);
2743        }
2744        
2745        
2746        protected boolean checkDebtEffect() {
2747                String key = "$debt_effectTimeout";
2748                if (Global.getSector().getMemoryWithoutUpdate().contains(key)) return false;
2749                
2750                //if (true) return true;
2751                
2752                // can't exactly melt away in that small an outpost, not that it's outright desertion
2753                // but it's also not a great place to leave the fleet
2754                if (market.isPlayerOwned() && market.getSize() <= 3) return false;
2755                
2756                MonthlyReport report = SharedData.getData().getPreviousReport();
2757                
2758                
2759                // require 2 months of debt in a row
2760                if (report.getPreviousDebt() <= 0 || report.getDebt() <= 0) return false;
2761                
2762                float debt = report.getDebt() + report.getPreviousDebt();
2763                float income = report.getRoot().totalIncome;
2764                if (income < 1) income = 1;
2765                
2766                float f = debt / income;
2767                if (f > 1) f = 1;
2768                if (f < 0) f = 0;
2769                // don't penalize minor shortfalls
2770                if (f < 0.1f) return false;
2771                
2772                // and don't reduce crew below a certain minimum
2773                int crew = playerFleet.getCargo().getCrew();
2774                int marines = playerFleet.getCargo().getMarines();
2775                if (crew <= 10 && marines <= 10) return false;
2776                
2777                return true;
2778        }
2779        
2780        protected void applyDebtEffect() {
2781                
2782                MonthlyReport report = SharedData.getData().getPreviousReport();
2783                float debt = report.getDebt() + report.getPreviousDebt();
2784                float income = report.getRoot().totalIncome;
2785                if (income < 1) income = 1;
2786                
2787                float f = debt / income;
2788                if (f > 1) f = 1;
2789                if (f < 0) f = 0;
2790                
2791                int crew = playerFleet.getCargo().getCrew();
2792                int marines = playerFleet.getCargo().getMarines();
2793                
2794                float maxLossFraction = 0.03f + Math.min(f + 0.05f, 0.2f) * (float) Math.random();
2795                float marineLossFraction = 0.03f + Math.min(f + 0.05f, 0.2f) * (float) Math.random();
2796                
2797                
2798                int crewLoss = (int) (crew * maxLossFraction);
2799                if (crewLoss < 2) crewLoss = 2;
2800                
2801                int marineLoss = (int) (marines * marineLossFraction);
2802                if (marineLoss < 2) marineLoss = 2;
2803                
2804                dialog.getVisualPanel().showImagePortion("illustrations", "crew_leaving", 640, 400, 0, 0, 480, 300);
2805                
2806                text.addPara("The lack of consistent pay over the last few months has caused discontent among your crew. " +
2807                                "A number take this opportunity to leave your employment.");
2808                
2809                if (crewLoss < crew) {
2810                        playerFleet.getCargo().removeCrew(crewLoss);
2811                        AddRemoveCommodity.addCommodityLossText(Commodities.CREW, crewLoss, text);
2812                }
2813                if (marineLoss <= marines) {
2814                        playerFleet.getCargo().removeMarines(marineLoss);
2815                        AddRemoveCommodity.addCommodityLossText(Commodities.MARINES, marineLoss, text);
2816                }
2817                
2818                String key = "$debt_effectTimeout";
2819                Global.getSector().getMemoryWithoutUpdate().set(key, true, 30f + (float) Math.random() * 10f);
2820                
2821                options.clearOptions();
2822                options.addOption("Continue", DEBT_RESULT_CONTINUE);
2823        }
2824
2825        
2826        public void doGenericRaid(FactionAPI faction, float attackerStr) {
2827                doGenericRaid(faction, attackerStr, 3f);
2828        }
2829        
2830        public void doGenericRaid(FactionAPI faction, float attackerStr, float maxPenalty) {
2831                doGenericRaid(faction, attackerStr, maxPenalty, false);
2832        }
2833        public void doGenericRaid(FactionAPI faction, float attackerStr, float maxPenalty, boolean allowedRepeat) {
2834                // needed for pirate raids not to stack
2835                // not needed anymore, but doesn't hurt anything
2836                if (!allowedRepeat && Misc.flagHasReason(market.getMemoryWithoutUpdate(), 
2837                                MemFlags.RECENTLY_RAIDED, faction.getId())) {
2838                        return;
2839                }
2840                
2841                float re = getRaidEffectiveness(market, attackerStr);
2842                if (maxPenalty == 3) {
2843                        applyRaidStabiltyPenalty(market, Misc.ucFirst(faction.getPersonNamePrefix()) + " raid", re);
2844                } else {
2845                        applyRaidStabiltyPenalty(market, Misc.ucFirst(faction.getPersonNamePrefix()) + " raid", re, maxPenalty);
2846                }
2847                //RecentUnrest.get(market).add(3, Misc.ucFirst(faction.getPersonNamePrefix()) + " raid");
2848                
2849                Misc.setFlagWithReason(market.getMemoryWithoutUpdate(), MemFlags.RECENTLY_RAIDED, 
2850                                                           faction.getId(), true, 30f);
2851                Misc.setRaidedTimestamp(market);
2852        }
2853        
2854        public boolean doIndustryRaid(FactionAPI faction, float attackerStr, Industry industry, float durMult) {
2855                temp.raidType = RaidType.DISRUPT;
2856                temp.target = industry;
2857                
2858                StatBonus defenderBase = new StatBonus(); 
2859                
2860                StatBonus defender = market.getStats().getDynamic().getMod(Stats.GROUND_DEFENSES_MOD);
2861                String increasedDefensesKey = "core_addedDefStr";
2862                float added = getDefenderIncreaseValue(market);
2863                if (added > 0) {
2864                        defender.modifyFlat(increasedDefensesKey, added, "Increased defender preparedness");
2865                }
2866                float defenderStr = (int) Math.round(defender.computeEffective(defenderBase.computeEffective(0f)));
2867                defender.unmodifyFlat(increasedDefensesKey);
2868                
2869                temp.attackerStr = attackerStr;
2870                temp.defenderStr = defenderStr;
2871                
2872                boolean hasForces = true;
2873                boolean canDisrupt = true;
2874                temp.raidMult = attackerStr / Math.max(1f, (attackerStr + defenderStr));
2875                temp.raidMult = Math.round(temp.raidMult * 100f) / 100f;
2876                
2877                if (temp.raidMult < VALUABLES_THRESHOLD) {
2878                        hasForces = false;
2879                }
2880                if (temp.raidMult < DISRUPTION_THRESHOLD) {
2881                        canDisrupt = false;
2882                }
2883                if (!canDisrupt) return false;
2884                
2885                
2886                Random random = getRandom();
2887                
2888                applyDefenderIncreaseFromRaid(market);
2889                
2890                String reason = faction.getDisplayName() + " raid";
2891                if (faction.getPersonNamePrefix() != null) {
2892                        reason = Misc.ucFirst(faction.getPersonNamePrefix()) + " raid";
2893                }
2894                
2895                applyRaidStabiltyPenalty(market, reason, temp.raidMult);
2896                Misc.setFlagWithReason(market.getMemoryWithoutUpdate(), MemFlags.RECENTLY_RAIDED, 
2897                                                           faction.getId(), true, 30f);
2898                Misc.setRaidedTimestamp(market);
2899                
2900                if (temp.target != null) {
2901                        float dur = computeBaseDisruptDuration(temp.target);
2902                        dur *= StarSystemGenerator.getNormalRandom(random, 1f, 1.25f);
2903                        dur *= durMult;
2904                        if (dur < 2) dur = 2;
2905                        float already = temp.target.getDisruptedDays();
2906                        temp.target.setDisrupted(already + dur);
2907                }
2908                
2909                return true;
2910        }
2911        
2912        
2913        public void doBombardment(FactionAPI faction, BombardType type) {
2914                temp.bombardType = type;
2915                
2916                Random random = getRandom();
2917        
2918                int dur = getBombardDisruptDuration();
2919                
2920                int stabilityPenalty = getTacticalBombardmentStabilityPenalty();
2921                if (temp.bombardType == BombardType.SATURATION) {
2922                        stabilityPenalty = getSaturationBombardmentStabilityPenalty();
2923                        
2924                        List<Industry> targets = new ArrayList<Industry>();
2925                        for (Industry ind : market.getIndustries()) {
2926                                if (!ind.getSpec().hasTag(Industries.TAG_NO_SATURATION_BOMBARDMENT)) {
2927                                        if (ind.getDisruptedDays() >= dur * 0.8f) continue;
2928                                        targets.add(ind);
2929                                }
2930                        }
2931                        temp.bombardmentTargets.clear();
2932                        temp.bombardmentTargets.addAll(targets);
2933                } else {
2934                        List<Industry> targets = new ArrayList<Industry>();
2935                        for (Industry ind : market.getIndustries()) {
2936                                if (ind.getSpec().hasTag(Industries.TAG_TACTICAL_BOMBARDMENT)) {
2937                                        if (ind.getDisruptedDays() >= dur * 0.8f) continue;
2938                                        targets.add(ind);
2939                                }
2940                        }
2941                        temp.bombardmentTargets.clear();
2942                        temp.bombardmentTargets.addAll(targets);
2943                }
2944                
2945                
2946                if (stabilityPenalty > 0) {
2947                        //String reason = faction.getDisplayName() + " bombardment";
2948                        String reason = "Saturation bombardment";
2949                        if (temp.bombardType == BombardType.TACTICAL) {
2950                                reason = "Tactical bombardment";
2951                        }
2952                        
2953                        RecentUnrest.get(market).add(stabilityPenalty, reason);
2954                }
2955                
2956                if (market.hasCondition(Conditions.HABITABLE) && !market.hasCondition(Conditions.POLLUTION)) {
2957                        market.addCondition(Conditions.POLLUTION);
2958                }
2959                
2960                for (Industry curr : temp.bombardmentTargets) {
2961                        dur = getBombardDisruptDuration();
2962                        dur *= StarSystemGenerator.getNormalRandom(random, 1f, 1.25f);
2963                        curr.setDisrupted(dur);
2964                }
2965                
2966                if (temp.bombardType == BombardType.TACTICAL) {
2967                } else if (temp.bombardType == BombardType.SATURATION) {
2968                        boolean destroy = market.getSize() <= getBombardDestroyThreshold();
2969                        if (Misc.isStoryCritical(market)) destroy = false;
2970                        if (destroy) {
2971                                DecivTracker.decivilize(market, true);
2972                        } else {
2973                                CoreImmigrationPluginImpl.reduceMarketSize(market);
2974                        }
2975                }
2976                
2977                
2978                Misc.setFlagWithReason(market.getMemoryWithoutUpdate(), MemFlags.RECENTLY_BOMBARDED, 
2979                                                           faction.getId(), true, 30f);
2980                
2981                addBombardVisual(market.getPrimaryEntity());
2982        }
2983        
2984        
2985        public static void addBombardVisual(SectorEntityToken target) {
2986                if (target != null && target.isInCurrentLocation()) {
2987                        int num = (int) (target.getRadius() * target.getRadius() / 300f);
2988                        num *= 2;
2989                        if (num > 150) num = 150;
2990                        if (num < 10) num = 10;
2991                        target.addScript(new BombardmentAnimation(num, target));
2992                }
2993        }
2994        
2995        public static class BombardmentAnimation implements EveryFrameScript {
2996                public BombardmentAnimation(int num, SectorEntityToken target) {
2997                        this.num = num;
2998                        this.target = target;
2999                }
3000                int num = 0;
3001                SectorEntityToken target;
3002                int added = 0;
3003                float elapsed = 0;
3004                public boolean runWhilePaused() {
3005                        return false;
3006                }
3007                public boolean isDone() {
3008                        return added >= num;
3009                }
3010                public void advance(float amount) {
3011                        elapsed += amount * (float) Math.random();
3012                        if (elapsed < 0.03f) return;
3013                        
3014                        elapsed = 0f;
3015                        
3016                        int curr = (int) Math.round(Math.random() * 4);
3017                        if (curr < 1) curr = 0;
3018                        
3019                        Color color = new Color(255, 165, 100, 255);
3020                        
3021                        Vector2f vel = new Vector2f();
3022                        
3023                        if (target.getOrbit() != null && 
3024                                        target.getCircularOrbitRadius() > 0 && 
3025                                        target.getCircularOrbitPeriod() > 0 && 
3026                                        target.getOrbitFocus() != null) {
3027                                float circumference = 2f * (float) Math.PI * target.getCircularOrbitRadius();
3028                                float speed = circumference / target.getCircularOrbitPeriod();
3029                                
3030                                float dir = Misc.getAngleInDegrees(target.getLocation(), target.getOrbitFocus().getLocation()) + 90f;
3031                                vel = Misc.getUnitVectorAtDegreeAngle(dir);
3032                                vel.scale(speed / Global.getSector().getClock().getSecondsPerDay());
3033                        }
3034                        
3035                        for (int i = 0; i < curr; i++) {
3036                                float glowSize = 50f + 50f * (float) Math.random();
3037                                float angle = (float) Math.random() * 360f;
3038                                float dist = (float) Math.sqrt(Math.random()) * target.getRadius();
3039                                
3040                                float factor = 0.5f + 0.5f * (1f - (float)Math.sqrt(dist / target.getRadius()));;
3041                                glowSize *= factor;
3042                                Vector2f loc = Misc.getUnitVectorAtDegreeAngle(angle);
3043                                loc.scale(dist);
3044                                Vector2f.add(loc, target.getLocation(), loc);
3045                                
3046                                Color c2 = Misc.scaleColor(color, factor);
3047                                //c2 = color;
3048                                Misc.addHitGlow(target.getContainingLocation(), loc, vel, glowSize, c2);
3049                                added++;
3050                                
3051                                if (i == 0) {
3052                                        dist = Misc.getDistance(loc, Global.getSector().getPlayerFleet().getLocation());
3053                                        if (dist < HyperspaceTerrainPlugin.STORM_STRIKE_SOUND_RANGE) {
3054                                                float volumeMult = 1f - (dist / HyperspaceTerrainPlugin.STORM_STRIKE_SOUND_RANGE);
3055                                                volumeMult = (float) Math.sqrt(volumeMult);
3056                                                volumeMult *= 0.1f * factor;
3057                                                if (volumeMult > 0) {
3058                                                        Global.getSoundPlayer().playSound("mine_explosion", 1f, 1f * volumeMult, loc, Misc.ZERO);
3059                                                }
3060                                        }
3061                                }
3062                        }
3063                }
3064        }
3065        
3066        
3067        protected boolean checkMercsLeaving() {
3068                String key = "$mercs_leaveTimeout";
3069                if (Global.getSector().getMemoryWithoutUpdate().contains(key)) return false;
3070                
3071                if (market.isHidden()) return false;
3072                if (market.getSize() <= 3) return false;
3073                
3074                List<OfficerDataAPI> mercs = Misc.getMercs(playerFleet);
3075                if (mercs.isEmpty()) return false;
3076                
3077                MonthlyReport report = SharedData.getData().getPreviousReport();
3078                boolean debt = report.getDebt() > 0;
3079                
3080                float contractDur = Global.getSettings().getFloat("officerMercContractDur");
3081                
3082                for (OfficerDataAPI od : mercs) {
3083                        if (debt && od.getPerson().getMemoryWithoutUpdate().contains(key)) {
3084                                continue;
3085                        }
3086                        float elapsed = Misc.getMercDaysSinceHired(od.getPerson());
3087                        
3088                        if (elapsed > contractDur || (debt && elapsed > 45f)) { // make sure the merc was hired long enough to have seen the debt
3089                                dialog.getInteractionTarget().setActivePerson(od.getPerson());
3090                                //dialog.getVisualPanel().showPersonInfo(getPerson(), true);
3091                                ((RuleBasedInteractionDialogPluginImpl)dialog.getPlugin()).notifyActivePersonChanged();
3092                                return true;
3093                        }
3094                }
3095                
3096                return false;
3097        }
3098        
3099        protected void convinceMercToStay() {
3100                PersonAPI merc = dialog.getInteractionTarget().getActivePerson();
3101                dialog.getInteractionTarget().setActivePerson(null);
3102                
3103                if (merc != null) {
3104                        Misc.setMercHiredNow(merc);
3105                        
3106                        String key = "$mercs_leaveTimeout";
3107                        Global.getSector().getMemoryWithoutUpdate().set(key, true, 5f + (float) Math.random() * 5f);
3108                        merc.getMemoryWithoutUpdate().set(key, true, 35f + (float) Math.random() * 5f);
3109                }
3110                
3111        }
3112        
3113        protected void mercLeaves() {
3114                PersonAPI merc = dialog.getInteractionTarget().getActivePerson();
3115                dialog.getInteractionTarget().setActivePerson(null);
3116                
3117                if (merc != null) {
3118                        FleetMemberAPI member = playerFleet.getFleetData().getMemberWithCaptain(merc);
3119                        if (member != null) {
3120                                member.setCaptain(null);
3121                        }
3122                        playerFleet.getFleetData().removeOfficer(merc);
3123                        
3124                        AddRemoveCommodity.addOfficerLossText(merc, text);
3125                        
3126                        String key = "$mercs_leaveTimeout";
3127                        Global.getSector().getMemoryWithoutUpdate().set(key, true, 5f + (float) Math.random() * 5f);
3128                }
3129        }
3130
3131}
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151