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