001package com.fs.starfarer.api.impl.campaign; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.List; 006 007import com.fs.starfarer.api.Global; 008import com.fs.starfarer.api.campaign.BattleAPI; 009import com.fs.starfarer.api.campaign.BattleAutoresolverPlugin; 010import com.fs.starfarer.api.campaign.CampaignFleetAPI; 011import com.fs.starfarer.api.campaign.CombatDamageData; 012import com.fs.starfarer.api.campaign.EngagementResultForFleetAPI; 013import com.fs.starfarer.api.campaign.FleetEncounterContextPlugin; 014import com.fs.starfarer.api.campaign.ai.CampaignFleetAIAPI.EncounterOption; 015import com.fs.starfarer.api.campaign.listeners.ListenerUtil; 016import com.fs.starfarer.api.combat.DeployedFleetMemberAPI; 017import com.fs.starfarer.api.combat.EngagementResultAPI; 018import com.fs.starfarer.api.combat.MutableShipStatsAPI; 019import com.fs.starfarer.api.combat.ShieldAPI.ShieldType; 020import com.fs.starfarer.api.combat.ShipHullSpecAPI; 021import com.fs.starfarer.api.combat.ShipVariantAPI; 022import com.fs.starfarer.api.fleet.FleetGoal; 023import com.fs.starfarer.api.fleet.FleetMemberAPI; 024import com.fs.starfarer.api.impl.campaign.ids.HullMods; 025import com.fs.starfarer.api.loading.WeaponSlotAPI; 026import com.fs.starfarer.api.util.Misc; 027import com.fs.starfarer.api.util.WeightedRandomPicker; 028 029public class BattleAutoresolverPluginImpl implements BattleAutoresolverPlugin { 030 031 public static class EngagementResultImpl implements EngagementResultAPI { 032 public BattleAPI battle; 033 public EngagementResultForFleetImpl winnerResult, loserResult; 034 035 public EngagementResultImpl(BattleAPI battle, CampaignFleetAPI winner, CampaignFleetAPI loser) { 036 this.battle = battle; 037 winnerResult = new EngagementResultForFleetImpl(winner); 038 loserResult = new EngagementResultForFleetImpl(loser); 039 } 040 041 public BattleAPI getBattle() { 042 return battle; 043 } 044 045 public boolean didPlayerWin() { 046 //return winnerResult.getFleet() != null && winnerResult.getFleet().isPlayerFleet(); 047 return winnerResult.getFleet() != null && Misc.isPlayerOrCombinedContainingPlayer(winnerResult.getFleet()); 048 } 049 050 public EngagementResultForFleetAPI getLoserResult() { 051 return loserResult; 052 } 053 054 public EngagementResultForFleetAPI getWinnerResult() { 055 return winnerResult; 056 } 057 058 public boolean isPlayerOutBeforeEnd() { 059 return false; 060 } 061 062 public void setPlayerOutBeforeEnd(boolean playerOutBeforeEnd) { 063 } 064 065 public void setBattle(BattleAPI battle) { 066 this.battle = battle; 067 } 068 069 public CombatDamageData getLastCombatDamageData() { 070 return null; 071 } 072 073 public void setLastCombatDamageData(CombatDamageData lastCombatData) { 074 075 } 076 } 077 078 public static class EngagementResultForFleetImpl implements EngagementResultForFleetAPI { 079 public CampaignFleetAPI fleet; 080 public FleetGoal goal; 081 public boolean winner = false; 082 public List<FleetMemberAPI> deployed = new ArrayList<FleetMemberAPI>(); 083 public List<FleetMemberAPI> reserves = new ArrayList<FleetMemberAPI>(); 084 public List<FleetMemberAPI> destroyed = new ArrayList<FleetMemberAPI>(); 085 public List<FleetMemberAPI> disabled = new ArrayList<FleetMemberAPI>(); 086 public List<FleetMemberAPI> retreated = new ArrayList<FleetMemberAPI>(); 087 088 public EngagementResultForFleetImpl(CampaignFleetAPI fleet) { 089 this.fleet = fleet; 090 } 091 092 public List<FleetMemberAPI> getDeployed() { 093 return deployed; 094 } 095 public List<FleetMemberAPI> getDestroyed() { 096 return destroyed; 097 } 098 public List<FleetMemberAPI> getDisabled() { 099 return disabled; 100 } 101 public CampaignFleetAPI getFleet() { 102 return fleet; 103 } 104 public FleetGoal getGoal() { 105 return goal; 106 } 107 public List<FleetMemberAPI> getReserves() { 108 return reserves; 109 } 110 public List<FleetMemberAPI> getRetreated() { 111 return retreated; 112 } 113 public List<DeployedFleetMemberAPI> getAllEverDeployedCopy() { 114 return null; 115 } 116 public boolean isWinner() { 117 return winner; 118 } 119 public void setWinner(boolean winner) { 120 this.winner = winner; 121 } 122 public void resetAllEverDeployed() { 123 } 124 public void setGoal(FleetGoal goal) { 125 this.goal = goal; 126 } 127 public boolean isPlayer() { 128 return false; 129 } 130 131 public boolean enemyCanCleanDisengage() { 132 // TODO Auto-generated method stub 133 return false; 134 } 135 } 136 137 public static enum FleetMemberBattleOutcome { 138 UNSCATHED, 139 LIGHT_DAMAGE, 140 MEDIUM_DAMAGE, 141 HEAVY_DAMAGE, 142 DISABLED, 143 } 144 145 public static class FleetMemberAutoresolveData { 146 public FleetMemberAPI member; 147 public float strength; 148 public float shieldRatio; 149 public boolean combatReady; 150 } 151 152 public static class FleetAutoresolveData { 153 public CampaignFleetAPI fleet; 154 public float fightingStrength; 155 public List<FleetMemberAutoresolveData> members = new ArrayList<FleetMemberAutoresolveData>(); 156 157 public void report() { 158 if (!report) return; 159 160 BattleAutoresolverPluginImpl.report(String.format("Fighting srength of %s: %f", fleet.getNameWithFaction(), fightingStrength)); 161 for (FleetMemberAutoresolveData data : members) { 162 String str = String.format("%40s: CR % 3d%% FP % 4d STR % 3f Shield %3.2f", 163 data.member.getVariant().getFullDesignationWithHullName(), 164 (int)(data.member.getRepairTracker().getCR() * 100f), 165 data.member.getFleetPointCost(), 166 data.strength, 167 data.shieldRatio); 168 BattleAutoresolverPluginImpl.report(" " + str); 169 } 170 171 } 172 } 173 174 protected CampaignFleetAPI one; 175 protected CampaignFleetAPI two; 176 protected final BattleAPI battle; 177 178 protected boolean playerPursuitAutoresolveMode = false; 179 protected List<FleetMemberAPI> playerShipsToDeploy; 180 181 public BattleAutoresolverPluginImpl(BattleAPI battle) { 182 this.battle = battle; 183 184 one = battle.getCombinedOne(); 185 two = battle.getCombinedTwo(); 186 if (battle.isPlayerInvolved()) { 187 one = battle.getPlayerCombined(); 188 two = battle.getNonPlayerCombined(); 189 } 190 191 setReport(Global.getSettings().isDevMode()); 192 setReport(false); 193 } 194 195 public void resolve() { 196 // figure out battle type (escape vs engagement) 197 198 report("***"); 199 report("***"); 200 report(String.format("Autoresolving %s vs %s", one.getNameWithFaction(), two.getNameWithFaction())); 201 202 context = new FleetEncounterContext(); 203 context.setAutoresolve(true); 204 context.setBattle(battle); 205 EncounterOption optionOne = one.getAI().pickEncounterOption(context, two); 206 EncounterOption optionTwo = two.getAI().pickEncounterOption(context, one); 207 208 if (optionOne == EncounterOption.DISENGAGE && optionTwo == EncounterOption.DISENGAGE) { 209 report("Both fleets want to disengage"); 210 report("Finished autoresolving engagement"); 211 report("***"); 212 report("***"); 213 return; 214 } 215 216 boolean oneEscaping = false; 217 boolean twoEscaping = false; 218 219 boolean freeDisengageIfCanOutrun = false; 220 221 if (optionOne == EncounterOption.DISENGAGE && optionTwo == EncounterOption.ENGAGE) { 222 report(String.format("%s wants to disengage", one.getNameWithFaction())); 223 oneEscaping = true; 224 if (freeDisengageIfCanOutrun && context.canOutrunOtherFleet(one, two)) { 225 report(String.format("%s can outrun other fleet", one.getNameWithFaction())); 226 report("Finished autoresolving engagement"); 227 report("***"); 228 report("***"); 229 return; 230 } 231 } 232 if (optionOne == EncounterOption.ENGAGE && optionTwo == EncounterOption.DISENGAGE) { 233 report(String.format("%s wants to disengage", two.getNameWithFaction())); 234 twoEscaping = true; 235 if (freeDisengageIfCanOutrun && context.canOutrunOtherFleet(two, one)) { 236 report(String.format("%s can outrun other fleet", two.getNameWithFaction())); 237 report("Finished autoresolving engagement"); 238 report("***"); 239 report("***"); 240 return; 241 } 242 } 243 244 resolveEngagement(context, oneEscaping, twoEscaping); 245 246 report(""); 247 report("Finished autoresolving engagement"); 248 report("***"); 249 report("***"); 250 } 251 252 253 public void resolvePlayerPursuit(FleetEncounterContext context, List<FleetMemberAPI> playerShipsToDeploy) { 254 this.context = context; 255 playerPursuitAutoresolveMode = true; 256 this.playerShipsToDeploy = playerShipsToDeploy; 257 resolveEngagement(context, false, true); 258 } 259 260 protected void resolveEngagement(FleetEncounterContext context, boolean oneEscaping, boolean twoEscaping) { 261 262 FleetAutoresolveData dataOne = computeDataForFleet(one); 263 FleetAutoresolveData dataTwo = computeDataForFleet(two); 264 265 if (dataOne.fightingStrength <= 0 && dataTwo.fightingStrength <= 0) { 266 return; 267 } 268 if (dataOne.fightingStrength <= 0.1f) { 269 dataOne.fightingStrength = 0.1f; 270 } 271 if (dataTwo.fightingStrength <= 0.1f) { 272 dataTwo.fightingStrength = 0.1f; 273 } 274 275 FleetAutoresolveData winner, loser; 276 277 report(""); 278 report("--------------------------------------------"); 279 dataOne.report(); 280 281 report(""); 282 report("--------------------------------------------"); 283 dataTwo.report(); 284 285 report(""); 286 report(""); 287 288 boolean loserEscaping = false; 289 if ((dataOne.fightingStrength > dataTwo.fightingStrength || twoEscaping) && !oneEscaping) { 290 report(String.format("%s won engagement", one.getNameWithFaction())); 291 winner = dataOne; 292 loser = dataTwo; 293 if (twoEscaping) { 294 loserEscaping = true; 295 } 296 } else { 297 report(String.format("%s won engagement", two.getNameWithFaction())); 298 winner = dataTwo; 299 loser = dataOne; 300 if (oneEscaping) { 301 loserEscaping = true; 302 } 303 } 304 305 float winnerAdvantage = winner.fightingStrength / loser.fightingStrength; 306// if (winnerAdvantage > 2f) winnerAdvantage = 2f; 307// if (winnerAdvantage < 0.5f) winnerAdvantage = 0.5f; 308 if (winnerAdvantage > 10f) winnerAdvantage = 10f; 309 if (winnerAdvantage < 0.1f) winnerAdvantage = 0.1f; 310 //if (winnerAdvantage < 0.1f) winnerAdvantage = 0.1f; 311 312 float damageDealtToWinner = loser.fightingStrength / winnerAdvantage; 313 float damageDealtToLoser = winner.fightingStrength * winnerAdvantage; 314 if (playerPursuitAutoresolveMode) { 315 damageDealtToWinner = 0f; 316 } 317 318 float damMult = Global.getSettings().getFloat("autoresolveDamageMult"); 319 damageDealtToWinner *= damMult; 320 damageDealtToLoser *= damMult; 321 322 //result = new EngagementResultImpl(winner.fleet, loser.fleet); 323 result = new EngagementResultImpl(context.getBattle(), 324 context.getBattle().getCombinedFor(winner.fleet), 325 context.getBattle().getCombinedFor(loser.fleet)); 326 327 report(""); 328 report("Applying damage to loser's ships"); 329 report("--------------------------------------------"); 330 Collections.shuffle(loser.members); 331 //boolean loserCarrierLeft = false; 332 for (FleetMemberAutoresolveData data : loser.members) { 333 report(String.format("Remaining damage to loser: %02.2f", damageDealtToLoser)); 334 FleetMemberBattleOutcome outcome = computeOutcomeForFleetMember(data, 1f/winnerAdvantage, damageDealtToLoser, loserEscaping, false); 335 damageDealtToLoser -= data.strength; 336 if (damageDealtToLoser < 0) damageDealtToLoser = 0; 337 } 338 339 for (FleetMemberAutoresolveData data : loser.members) { 340 if (data.member.getStatus().getHullFraction() > 0) { // || (data.member.isFighterWing() && loserCarrierLeft)) { 341 result.getLoserResult().getRetreated().add(data.member); 342 } else { 343 result.getLoserResult().getDisabled().add(data.member); 344 } 345 } 346 347 348 report(""); 349 report("Applying damage to winner's ships"); 350 report("--------------------------------------------"); 351 Collections.shuffle(winner.members); 352 353 boolean winnerCarrierLeft = false; 354 for (FleetMemberAutoresolveData data : winner.members) { 355 if (!data.combatReady) continue; 356 report(String.format("Remaining damage to winner: %02.2f", damageDealtToWinner)); 357 FleetMemberBattleOutcome outcome = computeOutcomeForFleetMember(data, winnerAdvantage, damageDealtToWinner, false, loserEscaping); 358 damageDealtToWinner -= data.strength; 359 if (damageDealtToWinner < 0) damageDealtToWinner = 0; 360 361 if (data.member.isMothballed()) continue; 362 if (data.member.getStatus().getHullFraction() > 0 && data.member.getNumFlightDecks() > 0) { 363 winnerCarrierLeft = true; 364 } 365 } 366 367 // which ships should count as "deployed" for CR loss purposes? 368 // anything that was disabled, and then anything up to double the loser's strength 369 float deployedStrength = 0f; 370 float maxDeployedStrength = loser.fightingStrength * 2f; 371 for (FleetMemberAutoresolveData data : winner.members) { 372 if (!(data.member.getStatus().getHullFraction() > 0 || (data.member.isFighterWing() && winnerCarrierLeft))) { 373 deployedStrength += data.strength; 374 } 375 } 376 377 for (FleetMemberAutoresolveData data : winner.members) { 378 if (playerPursuitAutoresolveMode) { 379 if (playerShipsToDeploy.contains(data.member) || data.member.isAlly()) { 380 result.getWinnerResult().getDeployed().add(data.member); 381 } else { 382 result.getWinnerResult().getReserves().add(data.member); 383 } 384 } else { 385 if (data.member.getStatus().getHullFraction() > 0) { 386 if (deployedStrength < maxDeployedStrength) { 387 result.getWinnerResult().getDeployed().add(data.member); 388 deployedStrength += data.strength; 389 } else { 390 result.getWinnerResult().getReserves().add(data.member); 391 } 392 } else { 393 result.getWinnerResult().getDisabled().add(data.member); 394 } 395 } 396 } 397 398 399 // CR hit, ship/crew losses get applied here 400 ((EngagementResultForFleetImpl)result.getWinnerResult()).setGoal(FleetGoal.ATTACK); 401 ((EngagementResultForFleetImpl)result.getWinnerResult()).setWinner(true); 402 403 if (loserEscaping) { 404 ((EngagementResultForFleetImpl)result.getLoserResult()).setGoal(FleetGoal.ESCAPE); 405 } else { 406 ((EngagementResultForFleetImpl)result.getLoserResult()).setGoal(FleetGoal.ATTACK); 407 } 408 ((EngagementResultForFleetImpl)result.getLoserResult()).setWinner(false); 409 410 411 // will be handled inside the interaction dialog if it's the player auto-resolving pursuit 412 if (!playerPursuitAutoresolveMode) { 413 context.processEngagementResults(result); 414 //context.applyPostEngagementOption(result); 415 context.performPostVictoryRecovery(result); 416 417 // need to set up one fleet as attacking, one fleet as losing + escaping/disengaging 418 // for the scrapping/looting/etc to work properly 419 context.getDataFor(winner.fleet).setDisengaged(false); 420 context.getDataFor(winner.fleet).setWonLastEngagement(true); 421 context.getDataFor(winner.fleet).setLastGoal(FleetGoal.ATTACK); 422 423 context.getDataFor(loser.fleet).setDisengaged(true); 424 context.getDataFor(loser.fleet).setWonLastEngagement(false); 425 context.getDataFor(loser.fleet).setLastGoal(FleetGoal.ESCAPE); 426 427 if (!winner.fleet.isAIMode()) { 428 context.generateLoot(null, true); 429 context.autoLoot(); 430 context.recoverCrew(winner.fleet); 431 } 432 //context.repairShips(); 433 434 context.applyAfterBattleEffectsIfThereWasABattle(); 435 } else { 436 // for ship recovery to recognize these ships as non-player 437// DataForEncounterSide data = context.getDataFor(loser.fleet); 438// for (FleetMemberAPI member : data.getDisabledInLastEngagement()) { 439// member.setOwner(1); 440// } 441// for (FleetMemberAPI member : data.getDestroyedInLastEngagement()) { 442// member.setOwner(1); 443// } 444 for (FleetMemberAutoresolveData data : loser.members) { 445 data.member.setOwner(1); 446 } 447 } 448 449// context.getBattle().uncombine(); 450// context.getBattle().finish(); 451 } 452 453 454 public static void applyDamageToFleetMember(FleetMemberAPI member, 455 float hullFraction) { 456 if (member.isFighterWing()) return; 457 if (hullFraction <= 0) return; 458 459 float num = member.getStatus().getNumStatuses(); 460 boolean someActiveRemaining = false; 461 462 if (num - 1f > member.getVariant().getModuleSlots().size() && num > 1f) { 463 String fleetName = "Unknown"; 464 String fleetLoc = "unknown"; 465 if (member.getFleetData() != null && member.getFleetData().getFleet() != null) { 466 fleetName = member.getFleetData().getFleet().getNameWithFaction(); 467 if (member.getFleetData().getFleet().getContainingLocation() != null) { 468 fleetLoc = member.getFleetData().getFleet().getContainingLocation().getName(); 469 } 470 } 471 String info = String.format("Fleet member [%s] from fleet [%s] with hull id [%s] and variant id [%s]" 472 + " in location [%s] has [%s] module statuses but only [%s] modules", 473 member.getShipName(), 474 fleetName, 475 member.getHullSpec().getHullId(), 476 member.getVariant(), 477 fleetLoc, 478 "" + (int)Math.round(num - 1f), 479 "" + member.getVariant().getModuleSlots().size()); 480 throw new RuntimeException(info); 481 } 482 483 for (int i = 0; i < num; i++) { 484 ShipVariantAPI variant = member.getVariant(); 485 if (i > 0) { 486 String slotId = member.getVariant().getModuleSlots().get(i - 1); 487 variant = variant.getModuleVariant(slotId); 488 } 489 490 if (variant.hasHullMod(HullMods.VASTBULK)) { 491 float dam = Math.min(hullFraction, 0.9f); 492 float hits = Math.min(5f, dam / 0.1f); 493 if (hits < 1) hits = 1; 494 //hits = 10; 495 for (int j = 0; j < hits; j++) { 496 member.getStatus().applyHullFractionDamage(dam / hits, i); 497 member.getStatus().setHullFraction(i, 1f); 498 } 499 continue; 500 } 501 502 if (i > 0 && !Misc.isActiveModule(variant)) continue; 503 504 float damageMult = 1f; 505 if (i > 0) { 506 damageMult = (float) Math.random(); 507 damageMult *= 2f; 508 } 509 510 float damage = hullFraction * damageMult; 511 if (damage <= 0) continue; 512 513 member.getStatus().applyHullFractionDamage(damage, i); 514 515 float hits = Math.min(5f, damage / 0.1f); 516 if (hits < 1) hits = 1; 517 for (int j = 0; j < hits; j++) { 518 member.getStatus().applyHullFractionDamage((damage / hits) + 0.001f, i); 519 } 520 521 if (i > 0 && member.getStatus().getHullFraction(i) <= 0) { 522 member.getStatus().setDetached(i, true); 523 } 524 525 if (Misc.isActiveModule(variant) && (!member.getStatus().isDetached(i) || member.getStatus().getHullFraction(i) > 0)) { 526 someActiveRemaining = true; 527 } 528 } 529 530 if (num > 1) { 531 float farthestDetached = 0; 532 for (int i = 1; i < num; i++) { 533 ShipVariantAPI variant = member.getVariant(); 534 if (member.getStatus().isDetached(i)) { 535 String slotId = variant.getModuleSlots().get(i - 1); 536 ShipVariantAPI mv = member.getVariant().getModuleVariant(slotId); 537 if (!Misc.isActiveModule(mv)) continue; 538 539 WeaponSlotAPI slot = variant.getHullSpec().getWeaponSlotAPI(slotId); 540 float dist = slot.getLocation().length(); 541 if (dist > farthestDetached) { 542 farthestDetached = dist; 543 } 544 } 545 } 546 547 for (int i = 1; i < num; i++) { 548 ShipVariantAPI variant = member.getVariant(); 549 if (!member.getStatus().isDetached(i)) { 550 String slotId = variant.getModuleSlots().get(i - 1); 551 ShipVariantAPI mv = member.getVariant().getModuleVariant(slotId); 552 if (mv.hasHullMod(HullMods.VASTBULK)) continue; 553 if (!Misc.isActiveModule(mv)) { 554 WeaponSlotAPI slot = variant.getHullSpec().getWeaponSlotAPI(slotId); 555 float dist = slot.getLocation().length(); 556 if (dist <= farthestDetached + 200f) { 557 member.getStatus().setHullFraction(i, 0f); 558 member.getStatus().setDetached(i, true); 559 } 560 } 561 } 562 } 563 } 564 565 566 if (!someActiveRemaining || hullFraction >= 1f) { 567 for (int i = 0; i < num; i++) { 568 member.getStatus().setHullFraction(i, 0f); 569 if (i > 0) { 570 member.getStatus().setDetached(i, true); 571 } 572 } 573 } 574 } 575 576 577 protected FleetMemberBattleOutcome computeOutcomeForFleetMember(FleetMemberAutoresolveData data, float advantageInBattle, 578 float maxDamage, boolean escaping, boolean enemyEscaping) { 579 ShipHullSpecAPI hullSpec = data.member.getHullSpec(); 580 581 float unscathed = 1f; 582 float lightDamage = 0f; 583 float mediumDamage = 0f; 584 float heavyDamage = 0f; 585 float disabled = 0f; 586 587 switch (hullSpec.getHullSize()) { 588 case CAPITAL_SHIP: 589 unscathed = 5f; 590 break; 591 case CRUISER: 592 unscathed = 10f; 593 break; 594 case DESTROYER: 595 unscathed = 15;; 596 break; 597 case FRIGATE: 598 case FIGHTER: 599 unscathed = 30f; 600 break; 601 } 602 603 float maxDamageRatio = maxDamage / data.strength; 604 if (maxDamageRatio > 1) maxDamageRatio = 1; 605 if (maxDamageRatio <= 0) maxDamageRatio = 0; 606 607 if (maxDamageRatio >= 0.8f) { 608 disabled = 20f; 609 heavyDamage = 10f; 610 mediumDamage = 10f; 611 lightDamage = 5f; 612 } else if (maxDamageRatio >= 0.6f) { 613 disabled = 5f; 614 heavyDamage = 20f; 615 mediumDamage = 10f; 616 lightDamage = 5f; 617 } else if (maxDamageRatio >= 0.4f) { 618 disabled = 0f; 619 heavyDamage = 10f; 620 mediumDamage = 20f; 621 lightDamage = 10f; 622 } else if (maxDamageRatio >= 0.2f) { 623 disabled = 0f; 624 heavyDamage = 0f; 625 mediumDamage = 10f; 626 lightDamage = 20f; 627 } else if (maxDamageRatio > 0) { 628 disabled = 0f; 629 heavyDamage = 0f; 630 mediumDamage = 5f; 631 lightDamage = 10f; 632 } 633 634 if (escaping) { 635 unscathed *= 2f; 636 lightDamage *= 1.5f; 637 } 638 639 if (enemyEscaping) { 640 disabled *= 0.5f; 641 heavyDamage *= 0.6f; 642 mediumDamage *= 0.7f; 643 lightDamage *= 0.8f; 644 unscathed *= 1f; 645 } 646 647 // advantageInBattle goes from 0.5 (bad) to 2 (good) 648 unscathed *= advantageInBattle; 649 lightDamage *= advantageInBattle; 650 651 float shieldRatio = data.shieldRatio; 652 653 // shieldRatio goes from 0 (no shields/no flux) to 1 (shields dominate hull/armor) 654 // shieldRatio at 0.5 roughly indicates balanced shields and hull/armor effectiveness 655 656 disabled *= 1.5f - shieldRatio * 1f; 657 heavyDamage *= 1.4f - shieldRatio * 0.8f; 658 mediumDamage *= 1.3f - shieldRatio * 0.6f; 659 lightDamage *= 1.2f - shieldRatio * 0.4f; 660 unscathed *= 0.9f + shieldRatio * 0.2f; 661 662 663 if (data.member.isStation()) { 664 heavyDamage += disabled; 665 disabled = 0f; // only disabled when heavy damage takes out all modules 666 } 667 668 669 WeightedRandomPicker<FleetMemberBattleOutcome> picker = new WeightedRandomPicker<FleetMemberBattleOutcome>(); 670 671 picker.add(FleetMemberBattleOutcome.DISABLED, disabled); 672 picker.add(FleetMemberBattleOutcome.HEAVY_DAMAGE, heavyDamage); 673 picker.add(FleetMemberBattleOutcome.MEDIUM_DAMAGE, mediumDamage); 674 picker.add(FleetMemberBattleOutcome.LIGHT_DAMAGE, lightDamage); 675 picker.add(FleetMemberBattleOutcome.UNSCATHED, unscathed); 676 677 678 report(String.format("Disabled: %d, Heavy: %d, Medium: %d, Light: %d, Unscathed: %d (Shield ratio: %3.2f)", 679 (int) disabled, (int) heavyDamage, (int) mediumDamage, (int) lightDamage, (int) unscathed, shieldRatio)); 680 681 FleetMemberBattleOutcome outcome = picker.pick(); 682 683 684 float damage = 0f; 685 686 data.member.getStatus().resetDamageTaken(); 687 688 689 switch (outcome) { 690 case DISABLED: 691 report(String.format("%40s: disabled", data.member.getVariant().getFullDesignationWithHullName())); 692 damage = 1f; 693 break; 694 case HEAVY_DAMAGE: 695 report(String.format("%40s: heavy damage", data.member.getVariant().getFullDesignationWithHullName())); 696 damage = 0.7f + (float) Math.random() * 0.1f; 697 break; 698 case MEDIUM_DAMAGE: 699 report(String.format("%40s: medium damage", data.member.getVariant().getFullDesignationWithHullName())); 700 damage = 0.45f + (float) Math.random() * 0.1f; 701 break; 702 case LIGHT_DAMAGE: 703 report(String.format("%40s: light damage", data.member.getVariant().getFullDesignationWithHullName())); 704 damage = 0.2f + (float) Math.random() * 0.1f; 705 break; 706 case UNSCATHED: 707 report(String.format("%40s: unscathed", data.member.getVariant().getFullDesignationWithHullName())); 708 damage = 0f; 709 break; 710 } 711 712 //damage = 0.8f; 713 applyDamageToFleetMember(data.member, damage); 714 715 return outcome; 716 } 717 718 protected FleetAutoresolveData computeDataForFleet(CampaignFleetAPI fleet) { 719 FleetAutoresolveData fleetData = new FleetAutoresolveData(); 720 fleetData.fleet = fleet; 721 722 fleetData.fightingStrength = 0; 723 for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) { 724 FleetMemberAutoresolveData data = computeDataForMember(member); 725 fleetData.members.add(data); 726 boolean okToDeployIfInPlayerPursuit = playerPursuitAutoresolveMode && playerShipsToDeploy != null && fleet == one && (playerShipsToDeploy.contains(member) || member.isAlly()); 727 if (data.combatReady && (!playerPursuitAutoresolveMode || fleet != one || okToDeployIfInPlayerPursuit)) { 728 float mult = 1f; 729 if (playerShipsToDeploy != null && playerShipsToDeploy.contains(member)) { 730 mult = 8f; 731 } 732 fleetData.fightingStrength += data.strength * mult; 733 } 734 } 735 736 ListenerUtil.modifyDataForFleet(fleetData); 737 738 return fleetData; 739 } 740 741 protected FleetMemberAutoresolveData computeDataForMember(FleetMemberAPI member) { 742 FleetMemberAutoresolveData data = new FleetMemberAutoresolveData(); 743 744 data.member = member; 745 ShipHullSpecAPI hullSpec = data.member.getHullSpec(); 746 if ((member.isCivilian() && !playerPursuitAutoresolveMode) || !member.canBeDeployedForCombat()) { 747 data.strength = 0.25f; 748 if (hullSpec.getShieldType() != ShieldType.NONE) { 749 data.shieldRatio = 0.5f; 750 } 751 data.combatReady = false; 752 return data; 753 } 754 755 data.combatReady = true; 756 757// if (data.member.getHullId().contains("astral")) { 758// System.out.println("testtesttest"); 759// } 760 761 MutableShipStatsAPI stats = data.member.getStats(); 762 763 float normalizedHullStr = stats.getHullBonus().computeEffective(hullSpec.getHitpoints()) + 764 stats.getArmorBonus().computeEffective(hullSpec.getArmorRating()) * 10f; 765 766 float normalizedShieldStr = stats.getFluxCapacity().getModifiedValue() + 767 stats.getFluxDissipation().getModifiedValue() * 10f; 768 769 770 if (hullSpec.getShieldType() == ShieldType.NONE) { 771 normalizedShieldStr = 0; 772 } else { 773 float shieldFluxPerDamage = hullSpec.getBaseShieldFluxPerDamageAbsorbed(); 774 shieldFluxPerDamage *= stats.getShieldAbsorptionMult().getModifiedValue() * stats.getShieldDamageTakenMult().getModifiedValue();; 775 if (shieldFluxPerDamage < 0.1f) shieldFluxPerDamage = 0.1f; 776 float shieldMult = 1f / shieldFluxPerDamage; 777 normalizedShieldStr *= shieldMult; 778 } 779 780 if (normalizedHullStr < 1) normalizedHullStr = 1; 781 if (normalizedShieldStr < 1) normalizedShieldStr = 1; 782 783 data.shieldRatio = normalizedShieldStr / (normalizedShieldStr + normalizedHullStr); 784 if (member.isStation()) { 785 data.shieldRatio = 0.5f; 786 } 787 788// float strength = member.getMemberStrength(); 789// 790// strength *= 0.5f + 0.5f * member.getStatus().getHullFraction(); 791// float captainMult = 0.5f; 792// if (member.getCaptain() != null) { 793// //captainMult = (10f + member.getCaptain().getStats().getAptitudeLevel("combat")) / 20f; 794// float captainLevel = member.getCaptain().getStats().getLevel(); 795// captainMult += captainLevel / 20f; 796// } 797// 798// strength *= captainMult; 799 800 float strength = Misc.getMemberStrength(member, true, true, true); 801 802 strength *= 0.85f + 0.3f * (float) Math.random(); 803 804 data.strength = Math.max(strength, 0.25f); 805 806 return data; 807 } 808 809 810 protected static void report(String str) { 811 if (report) { 812 System.out.println(str); 813 } 814 } 815 816 protected static boolean report = false; 817 protected EngagementResultAPI result; 818 protected FleetEncounterContext context; 819 820 public void setReport(boolean report) { 821 BattleAutoresolverPluginImpl.report = report; 822 } 823 824 public EngagementResultAPI getResult() { 825 return result; 826 } 827 828 public FleetEncounterContextPlugin getContext() { 829 return context; 830 } 831 832} 833 834 835 836 837 838 839 840 841 842