001package com.fs.starfarer.api.impl.campaign.intel.group; 002 003import java.awt.Color; 004import java.util.ArrayList; 005import java.util.Collections; 006import java.util.Comparator; 007import java.util.List; 008 009import org.lwjgl.util.vector.Vector2f; 010 011import com.fs.starfarer.api.Global; 012import com.fs.starfarer.api.campaign.CampaignFleetAPI; 013import com.fs.starfarer.api.campaign.FactionAPI; 014import com.fs.starfarer.api.campaign.SectorEntityToken; 015import com.fs.starfarer.api.campaign.StarSystemAPI; 016import com.fs.starfarer.api.campaign.ai.CampaignFleetAIAPI; 017import com.fs.starfarer.api.campaign.ai.CampaignFleetAIAPI.ActionType; 018import com.fs.starfarer.api.campaign.econ.Industry; 019import com.fs.starfarer.api.campaign.econ.MarketAPI; 020import com.fs.starfarer.api.impl.campaign.MilitaryResponseScript; 021import com.fs.starfarer.api.impl.campaign.MilitaryResponseScript.MilitaryResponseParams; 022import com.fs.starfarer.api.impl.campaign.command.WarSimScript; 023import com.fs.starfarer.api.impl.campaign.econ.impl.OrbitalStation; 024import com.fs.starfarer.api.impl.campaign.fleets.FleetFactoryV3; 025import com.fs.starfarer.api.impl.campaign.fleets.RouteManager; 026import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.OptionalFleetData; 027import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteData; 028import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteSegment; 029import com.fs.starfarer.api.impl.campaign.ids.MemFlags; 030import com.fs.starfarer.api.impl.campaign.ids.Tags; 031import com.fs.starfarer.api.impl.campaign.intel.group.GenericRaidFGI.GenericPayloadAction; 032import com.fs.starfarer.api.impl.campaign.procgen.themes.BaseAssignmentAI.FleetActionDelegate; 033import com.fs.starfarer.api.impl.campaign.procgen.themes.WarfleetAssignmentAI; 034import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.MarketCMD; 035import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.MarketCMD.BombardType; 036import com.fs.starfarer.api.util.CountingMap; 037import com.fs.starfarer.api.util.IntervalUtil; 038import com.fs.starfarer.api.util.Misc; 039 040public class FGRaidAction extends FGDurationAction implements FleetActionDelegate, GenericPayloadAction { 041 042 public static enum FGRaidType { 043 CONCURRENT, 044 SEQUENTIAL, 045 } 046 047 048 public static class FGRaidParams { 049 public StarSystemAPI where; 050 public FGRaidType type = FGRaidType.CONCURRENT; 051 public boolean doNotGetSidetracked = true; 052 public boolean tryToCaptureObjectives = true; 053 public boolean allowAnyHostileMarket = false; 054 public float maxDurationIfSpawnedFleetsConcurrent = 90f; 055 public float maxDurationIfSpawnedFleetsPerSequentialStage = 45f; 056 public int maxStabilityLostPerRaid = 3; 057 public int raidsPerColony = 2; 058 059 public String raidApproachText = null; 060 public String raidActionText = null; 061 public String targetTravelText = null; 062 public boolean appendTargetNameToTravelText = true; 063 public String inSystemActionText = null; 064 public BombardType bombardment = null; 065 public List<String> disrupt = new ArrayList<String>(); 066 067 public boolean allowNonHostileTargets = false; 068 public List<MarketAPI> allowedTargets = new ArrayList<MarketAPI>(); 069 070 public void setBombardment(BombardType type) { 071 this.bombardment = type; 072 raidApproachText = "moving to bombard"; 073 raidActionText = "bombarding"; 074 raidsPerColony = 1; 075 } 076 077 public void setDisrupt(String ... industries) { 078 for (String id : industries) { 079 disrupt.add(id); 080 } 081 raidsPerColony = Math.min(disrupt.size(), 3); 082 if (raidsPerColony < 1) raidsPerColony = 1; 083 } 084 } 085 086 public static class RaidSubstage { 087 protected boolean started = false; 088 public float maxDuration; 089 //public int raidsPerformed = 0; 090 public List<SectorEntityToken> objectives = new ArrayList<SectorEntityToken>(); 091 public List<SectorEntityToken> markets = new ArrayList<SectorEntityToken>(); 092 public List<SectorEntityToken> finishedRaiding = new ArrayList<SectorEntityToken>(); 093 094 protected Object readResolve() { 095 if (objectives == null) { 096 objectives = new ArrayList<SectorEntityToken>(); 097 } 098 if (markets == null) { 099 markets = new ArrayList<SectorEntityToken>(); 100 } 101 if (finishedRaiding == null) { 102 finishedRaiding = new ArrayList<SectorEntityToken>(); 103 } 104 return this; 105 } 106 107 public boolean allGoalsAchieved(FGRaidAction action) { 108 if (markets.isEmpty()) { 109 for (SectorEntityToken curr : objectives) { 110 if (curr.getFaction() != action.intel.getFaction()) { 111 return false; 112 } 113 } 114 } 115 for (SectorEntityToken curr : markets) { 116 if (action.raidCount.getCount(curr.getMarket()) < action.params.raidsPerColony) { 117 return false; 118 } 119 120 } 121 return true; 122 } 123 } 124 125 protected IntervalUtil interval = new IntervalUtil(0.1f, 0.3f); 126 127 128 protected boolean computedSubstages = false; 129 130 protected FGRaidParams params; 131 protected CountingMap<MarketAPI> raidCount = new CountingMap<MarketAPI>(); 132 protected int bombardCount = 0; 133 protected List<RaidSubstage> stages = new ArrayList<FGRaidAction.RaidSubstage>(); 134 protected List<MilitaryResponseScript> scripts = new ArrayList<MilitaryResponseScript>(); 135 protected float originalDuration = 0f; 136 137 public FGRaidAction(FGRaidParams params, float raidDays) { 138 super(raidDays); 139 originalDuration = raidDays; 140 this.params = params; 141 142 interval.forceIntervalElapsed(); 143 } 144 145 public Object readResolve() { 146 if (raidCount == null) { 147 raidCount = new CountingMap<MarketAPI>(); 148 } 149 return this; 150 } 151 152 @Override 153 public void addRouteSegment(RouteData route) { 154 RouteSegment segment = new RouteSegment(getDurDays(), params.where.getCenter()); 155 route.addSegment(segment); 156 } 157 158 159 @Override 160 public void notifyFleetsSpawnedMidSegment(RouteSegment segment) { 161 super.notifyFleetsSpawnedMidSegment(segment); 162 } 163 164 @Override 165 public void notifySegmentFinished(RouteSegment segment) { 166 super.notifySegmentFinished(segment); 167 168 autoresolve(); 169 } 170 171 protected void computeSubstages() { 172 List<CampaignFleetAPI> fleets = intel.getFleets(); 173 if (fleets.isEmpty()) return; 174 175// params.maxDurationIfSpawnedFleetsConcurrent = 90f; 176// params.maxDurationIfSpawnedFleetsPerSequentialStage = 45f; 177// params.maxStabilityLostPerRaid = 3; 178// params.raidsPerColony = 2; 179// params.doNotGetSidetracked = true; 180// params.appendTargetNameToTravelText = true; 181// params.type = FGRaidType.SEQUENTIAL; 182// intel.setApproximateNumberOfFleets(5); 183 184 185 for (CampaignFleetAPI fleet : fleets) { 186 if (!fleet.hasScriptOfClass(WarfleetAssignmentAI.class)) { 187 WarfleetAssignmentAI script = new WarfleetAssignmentAI(fleet, true, true); 188 script.setDelegate(this); 189 fleet.addScript(script); 190 } 191 fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_WAR_FLEET, true); 192 fleet.getMemoryWithoutUpdate().unset(MemFlags.FLEET_BUSY); 193 //fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_PIRATE, true); 194 } 195 196 List<MarketAPI> sortedTargetMarkets = new ArrayList<MarketAPI>(); 197 for (MarketAPI market : Misc.getMarketsInLocation(params.where)) { 198 if (!params.allowAnyHostileMarket && !params.allowedTargets.contains(market)) continue; 199 if (!params.allowNonHostileTargets && !intel.getFaction().isHostileTo(market.getFaction())) continue; 200 201 sortedTargetMarkets.add(market); 202 } 203 204 final Vector2f sortLoc = new Vector2f(fleets.get(0).getLocation()); 205 Collections.sort(sortedTargetMarkets, new Comparator<MarketAPI>() { 206 public int compare(MarketAPI o1, MarketAPI o2) { 207 float d1 = Misc.getDistance(sortLoc, o1.getPrimaryEntity().getLocation()); 208 float d2 = Misc.getDistance(sortLoc, o2.getPrimaryEntity().getLocation()); 209 return (int) Math.signum(d1 - d2); 210 } 211 }); 212 213 // otherwise, WasSimScript adds extra MilitaryResponseScripts for objectives and 214 // attacking fleets go there almost to the exclusion of other targets 215 for (SectorEntityToken objective : params.where.getEntitiesWithTag(Tags.OBJECTIVE)) { 216 WarSimScript.setNoFightingForObjective(objective, intel.getFaction(), 1000f); 217 } 218 219 List<SectorEntityToken> objectives = new ArrayList<SectorEntityToken>(); 220 float minDist = Float.MAX_VALUE; 221 SectorEntityToken closest = null; 222 for (SectorEntityToken objective : params.where.getEntitiesWithTag(Tags.NAV_BUOY)) { 223 float dist = Misc.getDistance(sortLoc, objective.getLocation()); 224 if (dist < minDist) { 225 closest = objective; 226 minDist = dist; 227 } 228 } 229 if (closest != null) { 230 objectives.add(closest); 231 } 232 233 234 minDist = Float.MAX_VALUE; 235 closest = null; 236 for (SectorEntityToken objective : params.where.getEntitiesWithTag(Tags.SENSOR_ARRAY)) { 237 float dist = Misc.getDistance(sortLoc, objective.getLocation()); 238 if (dist < minDist) { 239 closest = objective; 240 minDist = dist; 241 } 242 } 243 if (closest != null) { 244 objectives.add(closest); 245 } 246 247 if (!params.tryToCaptureObjectives) { 248 objectives.clear(); 249 } 250 251 if (params.type == FGRaidType.CONCURRENT) { 252 RaidSubstage stage = new RaidSubstage(); 253 stage.maxDuration = params.maxDurationIfSpawnedFleetsConcurrent; 254 stage.objectives.addAll(objectives); 255 for (MarketAPI market : sortedTargetMarkets) { 256 stage.markets.add(market.getPrimaryEntity()); 257 } 258 stages.add(stage); 259 } else { 260 if (!objectives.isEmpty()) { 261 RaidSubstage stage = new RaidSubstage(); 262 stage.maxDuration = params.maxDurationIfSpawnedFleetsPerSequentialStage; 263 stage.objectives.addAll(objectives); 264 stages.add(stage); 265 } 266 267 for (MarketAPI market : sortedTargetMarkets) { 268 RaidSubstage stage = new RaidSubstage(); 269 stage.maxDuration = params.maxDurationIfSpawnedFleetsConcurrent; 270 stage.markets.add(market.getPrimaryEntity()); 271 stages.add(stage); 272 } 273 } 274 275 float totalDur = 0f; 276 for (RaidSubstage stage : stages) { 277 totalDur += stage.maxDuration; 278 } 279 setDurDays(totalDur + 10f); 280 281 282 // system defenders protect targeted markets 283 float responseFraction = 1f / Math.max(1f, sortedTargetMarkets.size()); 284 for (MarketAPI market : sortedTargetMarkets) { 285 MilitaryResponseParams defParams = new MilitaryResponseParams(ActionType.HOSTILE, 286 "defRaid_" + market.getId(), 287 market.getFaction(), 288 market.getPrimaryEntity(), 289 responseFraction, 290 getDurDays()); 291 MilitaryResponseScript defScript = new MilitaryResponseScript(defParams); 292 params.where.addScript(defScript); 293 scripts.add(defScript); 294 } 295 296 computedSubstages = true; 297 } 298 299 public void removeAggroMilitaryScripts(boolean clearAssignments) { 300 if (clearAssignments) { 301 for (CampaignFleetAPI fleet : intel.getFleets()) { 302 fleet.clearAssignments(); 303 } 304 } 305 if (scripts != null) { 306 List<MilitaryResponseScript> remove = new ArrayList<MilitaryResponseScript>(); 307 for (MilitaryResponseScript s : scripts) { 308 if (s.getParams() != null && 309 s.getParams().responseReason != null && s.getParams().responseReason.startsWith("raid_")) { 310 s.forceDone(); 311 remove.add(s); 312 } 313 } 314 scripts.removeAll(remove); 315 } 316 } 317 318 @Override 319 public void setActionFinished(boolean finished) { 320 if (finished && !this.finished) { 321 List<CampaignFleetAPI> fleets = intel.getFleets(); 322 for (CampaignFleetAPI fleet : fleets) { 323 fleet.removeScriptsOfClass(WarfleetAssignmentAI.class); 324 Misc.setFlagWithReason(fleet.getMemoryWithoutUpdate(), MemFlags.FLEET_BUSY, fleet.getId(), true, -1f); 325 } 326 327 if (scripts != null) { 328 for (MilitaryResponseScript s : scripts) { 329 s.forceDone(); 330 } 331 } 332 333 for (SectorEntityToken objective : params.where.getEntitiesWithTag(Tags.OBJECTIVE)) { 334 WarSimScript.removeNoFightingTimeoutForObjective(objective, intel.getFaction()); 335 } 336 } 337 super.setActionFinished(finished); 338 } 339 340 341 @Override 342 public void directFleets(float amount) { 343 super.directFleets(amount); 344 if (isActionFinished()) return; 345 346 if (intel.isSpawning()) return; // could happen if source is in same system as target 347 348 List<CampaignFleetAPI> fleets = intel.getFleets(); 349 if (fleets.isEmpty()) { 350 setActionFinished(true); 351 return; 352 } 353 354 if (!computedSubstages) { 355 computeSubstages(); 356 } 357 358 if (stages.isEmpty()) { 359 setActionFinished(true); 360 return; 361 } 362 363 float days = Global.getSector().getClock().convertToDays(amount); 364 365 RaidSubstage stage = stages.get(0); 366 if (!stage.started) { 367 stage.started = true; 368 369 removeAggroMilitaryScripts(true); 370 371 List<SectorEntityToken> targets = new ArrayList<SectorEntityToken>(stage.objectives); 372 targets.addAll(stage.markets); 373 374 orderFleetMovements(targets); 375 } 376 377 378 stage.maxDuration -= days; 379 if (stage.maxDuration <= 0 || stage.allGoalsAchieved(this)) { 380 stages.remove(stage); 381 } 382 383 384 interval.advance(days); 385 if (!interval.intervalElapsed()) return; 386 387 boolean inSpawnRange = RouteManager.isPlayerInSpawnRange(params.where.getCenter()); 388 if (!inSpawnRange && elapsed > originalDuration) { 389 autoresolve(); 390 return; 391 } 392 393 394 for (SectorEntityToken obj : stage.objectives) { 395 if (obj.getFaction() == intel.getFaction()) { 396 WarSimScript.removeFightOrdersFor(obj, intel.getFaction()); 397 } 398 } 399 400 boolean someRaidsFinished = false; 401 for (SectorEntityToken e : stage.markets) { 402 if (stage.finishedRaiding.contains(e)) continue; 403 if (!canRaid(null, e.getMarket())) { 404 someRaidsFinished = true; 405 stage.finishedRaiding.add(e); 406 } 407 } 408 409 if (someRaidsFinished) { 410 for (SectorEntityToken e : stage.markets) { 411 WarSimScript.removeFightOrdersFor(e, intel.getFaction()); 412 } 413 List<SectorEntityToken> remaining = new ArrayList<SectorEntityToken>(); 414 remaining.addAll(stage.markets); 415 remaining.removeAll(stage.finishedRaiding); 416 417 // if wanted to re-fight for objectives that were lost, would add those here, but don't 418 419 if (!remaining.isEmpty()) { 420 orderFleetMovements(remaining); 421 } 422 } 423 424 425 426 for (CampaignFleetAPI fleet : fleets) { 427 if (params.doNotGetSidetracked) { 428 boolean battleNear = false; 429 for (CampaignFleetAPI other : fleets) { 430 if (other == fleet || other.getBattle() == null) continue; 431 if (other.getContainingLocation() != fleet.getContainingLocation()); 432 float dist = Misc.getDistance(fleet, other); 433 if (dist < 1000) { 434 CampaignFleetAIAPI ai = fleet.getAI(); 435 if (ai != null && ai.wantsToJoin(other.getBattle(), other.getBattle().isPlayerInvolved())) { 436 battleNear = true; 437 break; 438 } 439 } 440 } 441 if (!battleNear) { 442 fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_FLEET_DO_NOT_GET_SIDETRACKED, true, 0.4f); 443 } 444 } 445 } 446 } 447 448 449 protected void orderFleetMovements(List<SectorEntityToken> targets) { 450 float responseFraction = 1f / Math.max(1f, targets.size()); 451 452 for (SectorEntityToken target : targets) { 453 if (!target.hasTag(Tags.OBJECTIVE) &&!canRaid(null, target.getMarket())) { 454 continue; 455 } 456 457 float rf = responseFraction; 458 MilitaryResponseParams aggroParams = new MilitaryResponseParams(ActionType.HOSTILE, 459 "raid_" + target.getId(), 460 intel.getFaction(), 461 target, 462 rf, 463 getDurDays()); 464 if (params.appendTargetNameToTravelText) { 465 aggroParams.travelText = 466 (params.targetTravelText != null ? params.targetTravelText + " " : "traveling to ") + target.getName(); 467 } else if (params.targetTravelText != null) { 468 aggroParams.travelText = params.targetTravelText; 469 } 470 471 if (params.inSystemActionText != null) { 472 aggroParams.actionText = params.inSystemActionText; 473 } else { 474 aggroParams.actionText = "raiding system"; 475 } 476 477 MilitaryResponseScript script = new MilitaryResponseScript(aggroParams); 478 params.where.addScript(script); 479 scripts.add(script); 480 } 481 } 482 483 public FGRaidParams getParams() { 484 return params; 485 } 486 487 488 public boolean canRaid(CampaignFleetAPI fleet, MarketAPI market) { 489 if (market == null) return false; 490 if (!market.isInEconomy()) { 491 return false; 492 } 493 if (!params.allowedTargets.contains(market) && !params.allowedTargets.isEmpty() && !params.allowAnyHostileMarket) { 494 return false; 495 } 496 if (raidCount.getCount(market) >= params.raidsPerColony) { 497 return false; 498 } 499 500 if (fleet != null && !intel.getFleets().contains(fleet)) { 501 return false; 502 } 503 504 if (params.bombardment != null && fleet != null) { 505 float fp = fleet.getFleetPoints(); 506 for (CampaignFleetAPI other : intel.getFleets()) { 507 if (other == fleet) continue; 508 if (other.getContainingLocation() != fleet.getContainingLocation()) continue; 509 float dist = Misc.getDistance(fleet, other); 510 if (dist > 1000) continue; 511 float otherFP = other.getFleetPoints(); 512 if (otherFP > fp * 1.2f) { 513 return false; 514 } 515 } 516 } 517 518 FactionAPI faction = intel.getFaction(); 519 if (fleet != null) { 520 faction = fleet.getFaction(); 521 } 522 boolean hostile = market.getFaction().isHostileTo(faction); 523 if (fleet != null) { 524 hostile |= Misc.isFleetMadeHostileToFaction(fleet, market.getFaction()); 525 } 526 return (params.allowNonHostileTargets || hostile) && !isActionFinished(); 527 } 528 529 530 public void performRaid(CampaignFleetAPI fleet, MarketAPI market) { 531 raidCount.add(market); 532 533 FactionAPI faction = intel.getFaction(); 534 if (fleet != null) { 535 faction = fleet.getFaction(); 536 } 537 538 if (params.bombardment != null) { 539 float cost = MarketCMD.getBombardmentCost(market, fleet); 540 float bombardStr = intel.getRoute().getExtra().getStrengthModifiedByDamage() / intel.getApproximateNumberOfFleets() * 541 Misc.FP_TO_BOMBARD_COST_APPROX_MULT; 542 if (fleet != null) { 543 bombardStr = fleet.getCargo().getMaxFuel() * 0.5f; 544 } 545 546 if (cost <= bombardStr) { 547 new MarketCMD(market.getPrimaryEntity()).doBombardment(intel.getFaction(), params.bombardment); 548 bombardCount++; 549 } else { 550 Misc.setFlagWithReason(market.getMemoryWithoutUpdate(), MemFlags.RECENTLY_BOMBARDED, 551 intel.getFaction().getId(), true, 30f); 552 } 553 } else { 554 float raidStr = intel.getRoute().getExtra().getStrengthModifiedByDamage() / intel.getApproximateNumberOfFleets() * 555 Misc.FP_TO_GROUND_RAID_STR_APPROX_MULT; 556 if (fleet != null) { 557 raidStr = MarketCMD.getRaidStr(fleet); 558 } 559 560 Industry industry = null; 561 int index = raidCount.getCount(market) - 1; 562 if (index < 0) index = 0; 563 if (params.disrupt != null && index < params.disrupt.size()) { 564 int count = 0; 565 for (String industryId : params.disrupt) { 566 if (market.hasIndustry(industryId)) { 567 if (count >= index) { 568 industry = market.getIndustry(industryId); 569 break; 570 } 571 count++; 572 } 573 } 574// String industryId = params.disrupt.get(index); 575// industry = market.getIndustry(industryId); 576 } 577 578 if (intel instanceof GenericRaidFGI && ((GenericRaidFGI)intel).hasCustomRaidAction()) { 579 ((GenericRaidFGI)intel).doCustomRaidAction(fleet, market, raidStr); 580 Misc.setFlagWithReason(market.getMemoryWithoutUpdate(), MemFlags.RECENTLY_RAIDED, 581 faction.getId(), true, 30f); 582 Misc.setRaidedTimestamp(market); 583 } else if (industry != null) { 584 float durMult = Global.getSettings().getFloat("punitiveExpeditionDisruptDurationMult"); 585 new MarketCMD(market.getPrimaryEntity()).doIndustryRaid(faction, raidStr, industry, durMult); 586 } else { 587 new MarketCMD(market.getPrimaryEntity()).doGenericRaid(faction, 588 raidStr, params.maxStabilityLostPerRaid, params.raidsPerColony > 1); 589 } 590 } 591 } 592 593 594 595 public void autoresolve() { 596 if (isActionFinished()) return; 597 598 float str = WarSimScript.getFactionStrength(intel.getFaction(), params.where); 599 if (!intel.isSpawnedFleets() && intel.getRoute().isExpired()) { 600 // the above doesn't pick it up 601 OptionalFleetData data = intel.getRoute().getExtra(); 602 if (data != null) str += data.getStrengthModifiedByDamage(); 603 } 604 605 boolean playerTargeted = false; 606 if (intel instanceof GenericRaidFGI) { 607 playerTargeted = ((GenericRaidFGI)intel).getParams().playerTargeted; 608 } 609 610 float enemyStr = WarSimScript.getEnemyStrength(intel.getFaction(), params.where, playerTargeted); 611 float origStr = str; 612 float strMult = 1f; 613 for (MarketAPI target : Misc.getMarketsInLocation(params.where)) { 614 if (!params.allowAnyHostileMarket && !params.allowedTargets.contains(target)) continue; 615 if (!params.allowNonHostileTargets && !intel.getFaction().isHostileTo(target.getFaction())) continue; 616 617 float defensiveStr = enemyStr + WarSimScript.getStationStrength(target.getFaction(), params.where, target.getPrimaryEntity()); 618 619 float damage = 0.5f * defensiveStr / Math.max(str, 1f); 620 if (damage > 0.75f) damage = 0.75f; 621 strMult *= (1f - damage); 622 623 if (defensiveStr >= str) { 624 continue; 625 } 626 627 Industry station = Misc.getStationIndustry(target); 628 if (station != null) { 629 OrbitalStation.disrupt(station); 630 station.reapply(); 631 } 632 633 for (int i = 0; i < params.raidsPerColony; i++) { 634 performRaid(null, target); 635 } 636 637 //str -= defensiveStr * 0.5f; 638 str = origStr * strMult; 639 } 640 641 if (intel.isSpawnedFleets() && strMult < 1f) { 642 for (CampaignFleetAPI fleet : intel.getFleets()) { 643 FleetFactoryV3.applyDamageToFleet(fleet, 1f - strMult, false, intel.getRandom()); 644 } 645 } 646 647 if (!intel.isSpawnedFleets() && strMult < 1) { 648 OptionalFleetData extra = intel.getRoute().getExtra(); 649 if (extra != null) { 650 if (extra.damage == null) { 651 extra.damage = 0f; 652 } 653 extra.damage = 1f - (1f - extra.damage) * strMult; 654 //extra.damage = 1f; 655 if (extra.damage > 1f) extra.damage = 1f; 656 } 657 } else if (intel.isSpawnedFleets() && getSuccessFraction() <= 0f) { 658 intel.abort(); 659 } else { 660 // if fleets were not spawned and it needs to abort due to the damage taken, 661 // that's handled in FleetGroupIntel 662 } 663 664 setActionFinished(true); 665 } 666 667 668 669 public String getRaidApproachText(CampaignFleetAPI fleet, MarketAPI market) { 670 if (params.raidApproachText != null) { 671 return params.raidApproachText + " " + market.getName(); 672 } 673 return null; 674 } 675 676 public String getRaidActionText(CampaignFleetAPI fleet, MarketAPI market) { 677 if (params.raidActionText != null) { 678 return params.raidActionText + " " + market.getName(); 679 } 680 return null; 681 } 682 683 684 // not needed and not used by the WarfleetAssignmentAI 685 public String getRaidPrepText(CampaignFleetAPI fleet, SectorEntityToken from) { 686 return null; 687 } 688 689 public String getRaidInSystemText(CampaignFleetAPI fleet) { 690 return null; 691 } 692 693 public String getRaidDefaultText(CampaignFleetAPI fleet) { 694 return null; 695 } 696 697 public CountingMap<MarketAPI> getRaidCount() { 698 return raidCount; 699 } 700 701 702 public float getSuccessFraction() { 703 int totalGoal = params.raidsPerColony * params.allowedTargets.size(); 704 if (totalGoal < 1) totalGoal = 1; 705 int achieved = raidCount.getTotal(); 706 707 if (params.bombardment != null) { 708 achieved = bombardCount; 709 } 710 711 return Math.max(0f, (float)achieved / (float)totalGoal); 712 } 713 714 public Color getSystemNameHighlightColor() { 715 MarketAPI largest = null; 716 int max = 0; 717 boolean player = false; 718 for (MarketAPI target : Misc.getMarketsInLocation(params.where)) { 719 if (!params.allowAnyHostileMarket && !params.allowedTargets.contains(target)) continue; 720 if (!params.allowNonHostileTargets && !intel.getFaction().isHostileTo(target.getFaction())) continue; 721 722 int size = target.getSize(); 723 if (size > max) { 724 largest = target; 725 max = size; 726 } 727 if (target.isPlayerOwned()) { 728 player = true; 729 } 730 } 731 if (player) return Misc.getBasePlayerColor(); 732 if (largest != null) { 733 return largest.getFaction().getBaseUIColor(); 734 } 735 return Misc.getTextColor(); 736 } 737 738 public StarSystemAPI getWhere() { 739 return params.where; 740 } 741 742} 743 744 745 746 747 748 749 750