001package com.fs.starfarer.api.impl.campaign; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.Comparator; 006import java.util.HashMap; 007import java.util.HashSet; 008import java.util.Iterator; 009import java.util.LinkedHashMap; 010import java.util.List; 011import java.util.Map; 012import java.util.Random; 013import java.util.Set; 014 015import com.fs.starfarer.api.Global; 016import com.fs.starfarer.api.campaign.BattleAPI; 017import com.fs.starfarer.api.campaign.CampaignEventListener.FleetDespawnReason; 018import com.fs.starfarer.api.campaign.CampaignFleetAPI; 019import com.fs.starfarer.api.campaign.CargoAPI; 020import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType; 021import com.fs.starfarer.api.campaign.CargoStackAPI; 022import com.fs.starfarer.api.campaign.CombatDamageData; 023import com.fs.starfarer.api.campaign.CombatDamageData.DamageToFleetMember; 024import com.fs.starfarer.api.campaign.CombatDamageData.DealtByFleetMember; 025import com.fs.starfarer.api.campaign.EngagementResultForFleetAPI; 026import com.fs.starfarer.api.campaign.FactionAPI; 027import com.fs.starfarer.api.campaign.FleetAssignment; 028import com.fs.starfarer.api.campaign.FleetDataAPI; 029import com.fs.starfarer.api.campaign.FleetEncounterContextPlugin; 030import com.fs.starfarer.api.campaign.FleetEncounterContextPlugin.DataForEncounterSide.OfficerEngagementData; 031import com.fs.starfarer.api.campaign.InteractionDialogAPI; 032import com.fs.starfarer.api.campaign.TextPanelAPI; 033import com.fs.starfarer.api.campaign.ai.CampaignFleetAIAPI.PursuitOption; 034import com.fs.starfarer.api.campaign.ai.ModularFleetAIAPI; 035import com.fs.starfarer.api.campaign.econ.CommoditySpecAPI; 036import com.fs.starfarer.api.campaign.listeners.ListenerUtil; 037import com.fs.starfarer.api.campaign.rules.MemoryAPI; 038import com.fs.starfarer.api.characters.MutableCharacterStatsAPI; 039import com.fs.starfarer.api.characters.OfficerDataAPI; 040import com.fs.starfarer.api.characters.PersonAPI; 041import com.fs.starfarer.api.combat.DeployedFleetMemberAPI; 042import com.fs.starfarer.api.combat.EngagementResultAPI; 043import com.fs.starfarer.api.combat.FighterLaunchBayAPI; 044import com.fs.starfarer.api.combat.ShipAPI; 045import com.fs.starfarer.api.combat.ShipVariantAPI; 046import com.fs.starfarer.api.combat.WeaponAPI; 047import com.fs.starfarer.api.combat.WeaponAPI.WeaponType; 048import com.fs.starfarer.api.fleet.CrewCompositionAPI; 049import com.fs.starfarer.api.fleet.FleetGoal; 050import com.fs.starfarer.api.fleet.FleetMemberAPI; 051import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActionEnvelope; 052import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActions; 053import com.fs.starfarer.api.impl.campaign.ids.Commodities; 054import com.fs.starfarer.api.impl.campaign.ids.Factions; 055import com.fs.starfarer.api.impl.campaign.ids.MemFlags; 056import com.fs.starfarer.api.impl.campaign.ids.Stats; 057import com.fs.starfarer.api.impl.campaign.ids.Tags; 058import com.fs.starfarer.api.impl.campaign.intel.PromoteOfficerIntel; 059import com.fs.starfarer.api.impl.campaign.procgen.SalvageEntityGenDataSpec.DropData; 060import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.SalvageEntity; 061import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.BaseSalvageSpecial; 062import com.fs.starfarer.api.impl.campaign.tutorial.TutorialMissionIntel; 063import com.fs.starfarer.api.loading.FighterWingSpecAPI; 064import com.fs.starfarer.api.loading.HullModSpecAPI; 065import com.fs.starfarer.api.loading.VariantSource; 066import com.fs.starfarer.api.loading.WeaponGroupSpec; 067import com.fs.starfarer.api.loading.WeaponSlotAPI; 068import com.fs.starfarer.api.loading.WeaponSpecAPI; 069import com.fs.starfarer.api.util.Misc; 070import com.fs.starfarer.api.util.WeightedRandomPicker; 071 072public class FleetEncounterContext implements FleetEncounterContextPlugin { 073 074 protected List<DataForEncounterSide> sideData = new ArrayList<DataForEncounterSide>(); 075 protected boolean engagedInHostilities = false; 076 protected boolean engagedInActualBattle = false; 077 protected boolean playerOnlyRetreated = true; 078 protected boolean playerPursued = false; 079 protected boolean playerDidSeriousDamage = false; 080 protected BattleAPI battle; 081 protected boolean otherFleetHarriedPlayer = false; 082 protected boolean ongoingBattle = false; 083 084 protected boolean isAutoresolve = false; 085 086 087 protected CombatDamageData runningDamageTotal = null; 088 089 protected Map<FleetMemberAPI, CampaignFleetAPI> origSourceForRecoveredShips = new LinkedHashMap<>(); 090 091 public FleetEncounterContext() { 092 093 } 094 public boolean isAutoresolve() { 095 return isAutoresolve; 096 } 097 098 public void setAutoresolve(boolean isAutoresolve) { 099 this.isAutoresolve = isAutoresolve; 100 } 101 102 103 public BattleAPI getBattle() { 104 return battle; 105 } 106 107 public void setBattle(BattleAPI battle) { 108 this.battle = battle; 109 } 110 111 public DataForEncounterSide getDataFor(CampaignFleetAPI participantOrCombined) { 112 CampaignFleetAPI combined = battle.getCombinedFor(participantOrCombined); 113 if (combined == null) { 114 return new DataForEncounterSide(participantOrCombined); 115 } 116 117 for (DataForEncounterSide curr : sideData) { 118 if (curr.getFleet() == combined) return curr; 119 } 120 DataForEncounterSide dfes = new DataForEncounterSide(combined); 121 sideData.add(dfes); 122 123 return dfes; 124 } 125 126 public DataForEncounterSide getWinnerData() { 127 for (DataForEncounterSide curr : sideData) { 128 if (!curr.disengaged()) { 129 return curr; 130 } 131 } 132 return null; 133 } 134 135 public DataForEncounterSide getLoserData() { 136 for (DataForEncounterSide curr : sideData) { 137 if (curr.disengaged()) { 138 return curr; 139 } 140 } 141 return null; 142 } 143 144 public boolean isEngagedInHostilities() { 145 return engagedInHostilities; 146 } 147 148 public void setEngagedInHostilities(boolean engagedInHostilities) { 149 this.engagedInHostilities = engagedInHostilities; 150 } 151 152 public void setOtherFleetHarriedPlayer(boolean otherFleetHarriedPlayer) { 153 this.otherFleetHarriedPlayer = otherFleetHarriedPlayer; 154 } 155 156 public boolean isOtherFleetHarriedPlayer() { 157 return otherFleetHarriedPlayer; 158 } 159 160 protected void updateDeployedMap(EngagementResultForFleetAPI result) { 161 DataForEncounterSide data = getDataFor(result.getFleet()); 162 data.getMemberToDeployedMap().clear(); 163 164 List<DeployedFleetMemberAPI> deployed = result.getAllEverDeployedCopy(); 165 if (deployed != null && !deployed.isEmpty()) { 166 for (DeployedFleetMemberAPI dfm : deployed) { 167 if (dfm.getMember() != null) { 168 FleetMemberAPI member = dfm.getMember(); 169 data.getMemberToDeployedMap().put(member, dfm); 170 171 if (dfm != null && dfm.getShip() != null && dfm.getShip().getOriginalCaptain() != null && 172 !dfm.getShip().getOriginalCaptain().isDefault()) { 173 data.getMembersWithOfficerOrPlayerAsOrigCaptain().add(member); 174 } 175 } 176 } 177 } 178 } 179 180 /** 181 * There may be members with no source fleet if they were added to combat 182 * using scripts. 183 * @param result 184 */ 185 protected void clearNoSourceMembers(EngagementResultForFleetAPI result) { 186 Iterator<FleetMemberAPI> iter = result.getDeployed().iterator(); 187 while (iter.hasNext()) { 188 FleetMemberAPI member = iter.next(); 189 if (battle.getSourceFleet(member) == null) { 190 iter.remove(); 191 } 192 } 193 iter = result.getReserves().iterator(); 194 while (iter.hasNext()) { 195 FleetMemberAPI member = iter.next(); 196 if (battle.getSourceFleet(member) == null) { 197 iter.remove(); 198 } 199 } 200 iter = result.getDestroyed().iterator(); 201 while (iter.hasNext()) { 202 FleetMemberAPI member = iter.next(); 203 if (battle.getSourceFleet(member) == null) { 204 iter.remove(); 205 } 206 } 207 iter = result.getDisabled().iterator(); 208 while (iter.hasNext()) { 209 FleetMemberAPI member = iter.next(); 210 if (battle.getSourceFleet(member) == null) { 211 iter.remove(); 212 } 213 } 214 iter = result.getRetreated().iterator(); 215 while (iter.hasNext()) { 216 FleetMemberAPI member = iter.next(); 217 if (battle.getSourceFleet(member) == null) { 218 iter.remove(); 219 } 220 } 221 } 222 223 public boolean isEngagedInActualBattle() { 224 return engagedInActualBattle; 225 } 226 227 public void setEngagedInActualBattle(boolean engagedInActualBattle) { 228 this.engagedInActualBattle = engagedInActualBattle; 229 } 230 231 public void processEngagementResults(EngagementResultAPI result) { 232 engagedInHostilities = true; 233 engagedInActualBattle = true; // whether autoresolve or not, there was actual fighting 234 235 // the fleets we get back here are combined fleets from the BattleAPI, 236 // NOT the actual fleets involved, even if it's a 1 vs 1 battle. 237 EngagementResultForFleetAPI winnerResult = result.getWinnerResult(); 238 EngagementResultForFleetAPI loserResult = result.getLoserResult(); 239 240 clearNoSourceMembers(winnerResult); 241 clearNoSourceMembers(loserResult); 242 243 // only happens for combat where player is involved 244 CombatDamageData currDamageData = result.getLastCombatDamageData(); 245 if (currDamageData != null) { 246 if (runningDamageTotal == null) { 247 runningDamageTotal = currDamageData; 248 } else { 249 runningDamageTotal.add(currDamageData); 250 } 251 computeFPHullDamage(); 252 } 253 254 //if (winnerResult.getFleet().isPlayerFleet() || loserResult.getFleet().isPlayerFleet()) { 255 if (battle.isPlayerInvolved()) { 256 Global.getSector().reportPlayerEngagement(result); 257 } 258 259 updateDeployedMap(winnerResult); 260 updateDeployedMap(loserResult); 261 262 //result.applyToFleets(); 263 applyResultToFleets(result); 264 265 //if (winnerResult.getFleet().isPlayerFleet() && winnerResult.getGoal() != FleetGoal.ESCAPE) { 266 if (battle.isPlayerSide(winnerResult) && winnerResult.getGoal() != FleetGoal.ESCAPE) { 267 playerOnlyRetreated = false; 268 if (loserResult.getGoal() == FleetGoal.ESCAPE) { 269 playerPursued = true; 270 } 271 //} else if (loserResult.getFleet().isPlayerFleet() && loserResult.getGoal() != FleetGoal.ESCAPE) { 272 } else if (battle.isPlayerSide(loserResult) && loserResult.getGoal() != FleetGoal.ESCAPE) { 273 playerOnlyRetreated = false; 274 if (winnerResult.getGoal() == FleetGoal.ESCAPE) { 275 playerPursued = true; 276 } 277 } 278 279 DataForEncounterSide winnerData = getDataFor(winnerResult.getFleet()); 280 DataForEncounterSide loserData = getDataFor(loserResult.getFleet()); 281 282 winnerData.setWonLastEngagement(true); 283 winnerData.setEnemyCanCleanDisengage(winnerResult.enemyCanCleanDisengage()); 284 //winnerData.setFleetCanCleanDisengage(loserResult.enemyCanCleanDisengage()); 285 loserData.setWonLastEngagement(false); 286 loserData.setEnemyCanCleanDisengage(loserResult.enemyCanCleanDisengage()); 287 //loserData.setFleetCanCleanDisengage(winnerResult.enemyCanCleanDisengage()); 288 289 winnerData.setDidEnoughToDisengage(true); 290 float damageInFP = 0f; 291 for (FleetMemberAPI member : winnerResult.getDisabled()) { 292 damageInFP += member.getFleetPointCost(); 293 } 294 for (FleetMemberAPI member : winnerResult.getDestroyed()) { 295 damageInFP += member.getFleetPointCost(); 296 } 297 for (FleetMemberAPI member : winnerResult.getRetreated()) { 298 damageInFP += member.getFleetPointCost(); 299 } 300 301// float remaining = 0f; 302// for (FleetMemberAPI member : winnerResult.getFleet().getFleetData().getCombatReadyMembersListCopy()) { 303// remaining += member.getFleetPointCost(); 304// } 305// loserData.setDidEnoughToDisengage(damageInFP >= remaining); 306 loserData.setDidEnoughToDisengage(winnerResult.enemyCanCleanDisengage()); 307 308 309 winnerData.setLastGoal(winnerResult.getGoal()); 310 loserData.setLastGoal(loserResult.getGoal()); 311 312 winnerData.getDeployedInLastEngagement().clear(); 313 winnerData.getRetreatedFromLastEngagement().clear(); 314 winnerData.getInReserveDuringLastEngagement().clear(); 315 winnerData.getDisabledInLastEngagement().clear(); 316 winnerData.getDestroyedInLastEngagement().clear(); 317 winnerData.getDeployedInLastEngagement().addAll(winnerResult.getDeployed()); 318 winnerData.getRetreatedFromLastEngagement().addAll(winnerResult.getRetreated()); 319 winnerData.getInReserveDuringLastEngagement().addAll(winnerResult.getReserves()); 320 winnerData.getDisabledInLastEngagement().addAll(winnerResult.getDisabled()); 321 winnerData.getDestroyedInLastEngagement().addAll(winnerResult.getDestroyed()); 322 323 loserData.getDeployedInLastEngagement().clear(); 324 loserData.getRetreatedFromLastEngagement().clear(); 325 loserData.getInReserveDuringLastEngagement().clear(); 326 loserData.getDisabledInLastEngagement().clear(); 327 loserData.getDestroyedInLastEngagement().clear(); 328 loserData.getDeployedInLastEngagement().addAll(loserResult.getDeployed()); 329 loserData.getRetreatedFromLastEngagement().addAll(loserResult.getRetreated()); 330 loserData.getInReserveDuringLastEngagement().addAll(loserResult.getReserves()); 331 loserData.getDisabledInLastEngagement().addAll(loserResult.getDisabled()); 332 loserData.getDestroyedInLastEngagement().addAll(loserResult.getDestroyed()); 333 334 for (FleetMemberAPI member : loserResult.getDestroyed()) { 335 loserData.addOwn(member, Status.DESTROYED); 336 } 337 338 for (FleetMemberAPI member : loserResult.getDisabled()) { 339 loserData.addOwn(member, Status.DISABLED); 340 } 341 342 for (FleetMemberAPI member : winnerResult.getDestroyed()) { 343 winnerData.addOwn(member, Status.DESTROYED); 344 } 345 346 for (FleetMemberAPI member : winnerResult.getDisabled()) { 347 winnerData.addOwn(member, Status.DISABLED); 348 } 349 350 //if (winnerResult.getFleet().isPlayerFleet()) { 351 if (result.getWinnerResult().getAllEverDeployedCopy() != null) { 352 tallyOfficerTime(winnerData, winnerResult); 353 } 354 //} else if (loserResult.getFleet().isPlayerFleet()) { 355 if (result.getLoserResult().getAllEverDeployedCopy() != null) { 356 tallyOfficerTime(loserData, loserResult); 357 } 358 359 // important, so that in-combat Ship objects can be garbage collected. 360 // Probably some combat engine references in there, too. 361 winnerResult.resetAllEverDeployed(); 362 getDataFor(winnerResult.getFleet()).getMemberToDeployedMap().clear(); 363 loserResult.resetAllEverDeployed(); 364 getDataFor(loserResult.getFleet()).getMemberToDeployedMap().clear(); 365 366 367 // moved from applyPostEngagementResult 368 for (FleetMemberAPI member : winnerResult.getDestroyed()) { 369 loserData.addEnemy(member, Status.DESTROYED); 370 } 371 for (FleetMemberAPI member : winnerResult.getDisabled()) { 372 loserData.addEnemy(member, Status.DISABLED); 373 } 374 375 for (FleetMemberAPI member : loserResult.getDestroyed()) { 376 winnerData.addEnemy(member, Status.DESTROYED); 377 } 378 for (FleetMemberAPI member : loserResult.getDisabled()) { 379 winnerData.addEnemy(member, Status.DISABLED); 380 } 381 382 383 FleetGoal winnerGoal = winnerResult.getGoal(); 384 FleetGoal loserGoal = loserResult.getGoal(); 385 boolean totalWin = loserData.getFleet().getFleetData().getMembersListCopy().isEmpty(); 386 boolean playerOut = result.isPlayerOutBeforeEnd(); 387 388 if (playerOut) { 389 FleetGoal playerGoal = null; 390 FleetGoal otherGoal = null; 391 if (battle.isPlayerSide(battle.getSideFor(winnerResult.getFleet()))) { 392 playerGoal = winnerGoal; 393 otherGoal = loserGoal; 394 } else { 395 playerGoal = loserGoal; 396 otherGoal = winnerGoal; 397 } 398 if (playerGoal == FleetGoal.ATTACK) { 399 if (otherGoal == FleetGoal.ATTACK) { 400 if (winnerResult.isPlayer()) { 401 lastOutcome = EngagementOutcome.BATTLE_PLAYER_OUT_FIRST_WIN; 402 } else { 403 lastOutcome = EngagementOutcome.BATTLE_PLAYER_OUT_FIRST_LOSS; 404 } 405 } else { 406 if (winnerResult.isPlayer()) { 407 lastOutcome = EngagementOutcome.PURSUIT_PLAYER_OUT_FIRST_WIN; 408 } else { 409 lastOutcome = EngagementOutcome.PURSUIT_PLAYER_OUT_FIRST_LOSS; 410 } 411 } 412 } else { 413 if (winnerResult.isPlayer()) { 414 lastOutcome = EngagementOutcome.ESCAPE_PLAYER_OUT_FIRST_WIN; 415 } else { 416 lastOutcome = EngagementOutcome.ESCAPE_PLAYER_OUT_FIRST_LOSS; 417 } 418 } 419 } else { 420 if (totalWin && winnerData.getFleet().getFleetData().getMembersListCopy().isEmpty()) { 421 lastOutcome = EngagementOutcome.MUTUAL_DESTRUCTION; 422 } else { 423 if (battle.isPlayerSide(battle.getSideFor(winnerResult.getFleet()))) { 424 if (winnerGoal == FleetGoal.ATTACK && loserGoal == FleetGoal.ATTACK) { 425 if (totalWin) { 426 lastOutcome = EngagementOutcome.BATTLE_PLAYER_WIN_TOTAL; 427 } else { 428 lastOutcome = EngagementOutcome.BATTLE_PLAYER_WIN; 429 } 430 } else if (winnerGoal == FleetGoal.ESCAPE) { 431 if (totalWin) { 432 lastOutcome = EngagementOutcome.ESCAPE_PLAYER_WIN_TOTAL; 433 } else { 434 lastOutcome = EngagementOutcome.ESCAPE_PLAYER_WIN; 435 } 436 } else if (loserGoal == FleetGoal.ESCAPE) { 437 if (totalWin) { 438 lastOutcome = EngagementOutcome.ESCAPE_ENEMY_LOSS_TOTAL; 439 } else { 440 lastOutcome = EngagementOutcome.ESCAPE_ENEMY_SUCCESS; 441 } 442 } 443 } else { 444 if (winnerGoal == FleetGoal.ATTACK && loserGoal == FleetGoal.ATTACK) { 445 if (totalWin) { 446 lastOutcome = EngagementOutcome.BATTLE_ENEMY_WIN_TOTAL; 447 } else { 448 lastOutcome = EngagementOutcome.BATTLE_ENEMY_WIN; 449 } 450 } else if (winnerGoal == FleetGoal.ESCAPE) { 451 if (totalWin) { 452 lastOutcome = EngagementOutcome.ESCAPE_ENEMY_WIN_TOTAL; 453 } else { 454 lastOutcome = EngagementOutcome.ESCAPE_ENEMY_WIN; 455 } 456 } else if (loserGoal == FleetGoal.ESCAPE) { 457 if (totalWin) { 458 lastOutcome = EngagementOutcome.ESCAPE_PLAYER_LOSS_TOTAL; 459 } else { 460 lastOutcome = EngagementOutcome.ESCAPE_PLAYER_SUCCESS; 461 } 462 } 463 } 464 } 465 } 466 467 battle.uncombine(); 468 //battle.genCombinedDoNotRemoveEmpty(); 469 battle.genCombined(); 470 } 471 472 protected void tallyOfficerTime(DataForEncounterSide data, EngagementResultForFleetAPI result) { 473 float maxTime = 0f; 474 for (DeployedFleetMemberAPI dfm : result.getAllEverDeployedCopy()) { 475 float time = dfm.getShip().getFullTimeDeployed(); 476 477 if (time > maxTime) { 478 maxTime = time; 479 } 480 481 time -= dfm.getShip().getTimeDeployedUnderPlayerControl(); 482 if (time <= 0) continue; 483 484 PersonAPI person = dfm.getMember().getCaptain(); 485 CampaignFleetAPI source = battle.getSourceFleet(dfm.getMember()); 486 if (source == null) continue; 487 488 //if (data.getFleet().getFleetData().getOfficerData(person) == null) { 489 if (source.getFleetData().getOfficerData(person) == null) { 490 OfficerEngagementData oed = data.getFleetMemberDeploymentData().get(dfm.getMember()); 491 if (oed == null) { 492 oed = new OfficerEngagementData(source); 493 oed.person = null; 494 data.getFleetMemberDeploymentData().put(dfm.getMember(), oed); 495 } 496 time += dfm.getShip().getTimeDeployedUnderPlayerControl(); 497 oed.timeDeployed += time; 498 continue; // not an officer 499 } 500 501 OfficerEngagementData oed = data.getOfficerData().get(person); 502 if (oed == null) { 503 oed = new OfficerEngagementData(source); 504 oed.person = person; 505 data.getOfficerData().put(person, oed); 506 } 507 oed.timeDeployed += time; 508 } 509 data.setMaxTimeDeployed(data.getMaxTimeDeployed() + maxTime); 510 } 511 512 public PursueAvailability getPursuitAvailability(CampaignFleetAPI fleet, CampaignFleetAPI otherFleet) { 513 DataForEncounterSide otherData = getDataFor(otherFleet); 514 515 if (otherData.isWonLastEngagement()) return PursueAvailability.LOST_LAST_ENGAGEMENT; 516 if (canOutrunOtherFleet(otherFleet, fleet)) return PursueAvailability.TOO_SLOW; 517 if (fleet.getFleetData().getCombatReadyMembersListCopy().isEmpty()) return PursueAvailability.NO_READY_SHIPS; 518 if (otherData.isDidEnoughToDisengage()) return PursueAvailability.TOOK_SERIOUS_LOSSES; 519 return PursueAvailability.AVAILABLE; 520 } 521 522 public DisengageHarryAvailability getDisengageHarryAvailability(CampaignFleetAPI fleet, CampaignFleetAPI otherFleet) { 523 DataForEncounterSide otherData = getDataFor(otherFleet); 524 if (otherData.isWonLastEngagement()) return DisengageHarryAvailability.LOST_LAST_ENGAGEMENT; 525 if (fleet.getFleetData().getCombatReadyMembersListCopy().isEmpty()) return DisengageHarryAvailability.NO_READY_SHIPS; 526 //if (otherData.isDidEnoughToDisengage()) return DisengageHarryAvailability.LOST_LAST_ENGAGEMENT 527 return DisengageHarryAvailability.AVAILABLE; 528 } 529 530 public float getDeployCost(FleetMemberAPI member) { 531 return member.getDeployCost(); 532 } 533 534 public boolean isLowRepImpact() { 535 boolean lowImpact = getBattle() != null && getBattle().getNonPlayerSide() != null && 536 getBattle().getPrimary(getBattle().getNonPlayerSide()) != null && 537 getBattle().getPrimary(getBattle().getNonPlayerSide()).getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_LOW_REP_IMPACT) == true; 538 return lowImpact; 539 } 540 public boolean isNoRepImpact() { 541 boolean noImpact = getBattle() != null && getBattle().getNonPlayerSide() != null && 542 getBattle().getPrimary(getBattle().getNonPlayerSide()) != null && 543 getBattle().getPrimary(getBattle().getNonPlayerSide()).getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_NO_REP_IMPACT) == true; 544 return noImpact; 545 } 546 547 protected boolean alreadyAdjustedRep = false; 548 public boolean adjustPlayerReputation(InteractionDialogAPI dialog, String ffText) { 549 return adjustPlayerReputation(dialog, ffText, true, true); 550 } 551 public boolean adjustPlayerReputation(InteractionDialogAPI dialog, String ffText, boolean okToAdjustAlly, boolean okToAdjustEnemy) { 552 if (alreadyAdjustedRep) return false; 553 554 if (battle != null && battle.isPlayerInvolved() && engagedInHostilities) { 555 alreadyAdjustedRep = true; 556 557 boolean printedAdjustmentText = false; 558 559 boolean playerWon = didPlayerWinMostRecentBattleOfEncounter(); 560 List<CampaignFleetAPI> playerSide = battle.getPlayerSide(); 561 List<CampaignFleetAPI> enemySide = battle.getNonPlayerSide(); 562 563 CampaignFleetAPI pf = Global.getSector().getPlayerFleet(); 564 pf.setMoveDestination(pf.getLocation().x, pf.getLocation().y); 565 566 // cases to cover: 1) player destroyed ships 2) player harried/harassed 3) player pursued 567 // i.e. anything other than a non-destructive retreat, w/o an engagement 568 boolean playerWasAggressive = playerDidSeriousDamage || !playerOnlyRetreated; 569 RepActions action = null; 570// if (engagedInHostilities) { 571// action = RepActions.COMBAT_NO_DAMAGE_ESCAPE; 572// } 573 boolean knowsWhoPlayerIs = battle.knowsWhoPlayerIs(enemySide); 574 //boolean lowImpact = battle.getNonPlayerCombined().getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_LOW_REP_IMPACT) == true; 575 boolean lowImpact = isLowRepImpact(); 576 if (lowImpact) { 577 for (CampaignFleetAPI enemy : battle.getSnapshotFor(enemySide)) { 578 Misc.makeLowRepImpact(enemy, "battleOnLowImpactSide"); 579 } 580 } 581 if (playerPursued && playerWon) { 582 if (knowsWhoPlayerIs && !lowImpact) { 583 action = RepActions.COMBAT_AGGRESSIVE; 584 } else { 585 action = RepActions.COMBAT_AGGRESSIVE_TOFF; 586 } 587 } else if (playerWasAggressive) { 588 if (knowsWhoPlayerIs && !lowImpact) { 589 action = RepActions.COMBAT_NORMAL; 590 } else { 591 action = RepActions.COMBAT_NORMAL_TOFF; 592 } 593 } 594 595 if (isNoRepImpact()) { 596 action = null; 597 } 598 599 if (!okToAdjustEnemy) action = null; 600 601 Set<String> seen = new HashSet<String>(); 602 if (action != null) { 603 // use snapshot: ensure loss of reputation with factions if their fleets were destroyed 604 for (CampaignFleetAPI enemy : battle.getSnapshotFor(enemySide)) { 605 String factionId = enemy.getFaction().getId(); 606 if (seen.contains(factionId)) continue; 607 seen.add(factionId); 608 Global.getSector().adjustPlayerReputation(new RepActionEnvelope(action, null, dialog.getTextPanel()), factionId); 609 printedAdjustmentText = true; 610 } 611 } 612 613 //if (playerWon) { 614 action = RepActions.COMBAT_HELP_MINOR; 615 float playerFP = 0; 616 float allyFP = 0; 617 float enemyFP = 0; 618 for (CampaignFleetAPI fleet : battle.getSnapshotFor(playerSide)) { 619 for (FleetMemberAPI member : fleet.getFleetData().getSnapshot()) { 620 if (fleet.isPlayerFleet()) { 621 playerFP += member.getFleetPointCost(); 622 } else { 623 allyFP += member.getFleetPointCost(); 624 } 625 } 626 } 627 for (CampaignFleetAPI fleet : battle.getSnapshotFor(enemySide)) { 628 for (FleetMemberAPI member : fleet.getFleetData().getSnapshot()) { 629 enemyFP += member.getFleetPointCost(); 630 } 631 } 632 if (allyFP > enemyFP || !playerWon) { 633 action = RepActions.COMBAT_HELP_MINOR; 634 } else if (allyFP < enemyFP * 0.5f) { 635 action = RepActions.COMBAT_HELP_CRITICAL; 636 } else { 637 action = RepActions.COMBAT_HELP_MAJOR; 638 } 639 640// if (playerFPHullDamageToEnemies <= 0) { 641// action = null; 642// } else if (playerFPHullDamageToEnemies < allyFPHullDamageToEnemies * 0.1f) { 643// action = RepActions.COMBAT_HELP_MINOR; 644// } 645 float f = computePlayerContribFraction(); 646 if (f <= 0) { 647 action = null; 648 } else if (f < 0.1f) { 649 action = RepActions.COMBAT_HELP_MINOR; 650 } 651 652 if (action != null) { 653 float totalDam = allyFPHullDamageToEnemies + playerFPHullDamageToEnemies; 654 if (totalDam < 10) { 655 action = RepActions.COMBAT_HELP_MINOR; 656 } else if (totalDam < 20 && action == RepActions.COMBAT_HELP_CRITICAL) { 657 action = RepActions.COMBAT_HELP_MAJOR; 658 } 659 } 660 661 if (battle.isPlayerInvolvedAtStart() && action != null) { 662 //action = RepActions.COMBAT_HELP_MINOR; 663 action = null; 664 } 665 666 if (!okToAdjustAlly) action = null; 667// if (leavingEarly) { 668// action = null; 669// } 670 671 // rep increases 672 seen.clear(); 673 for (CampaignFleetAPI ally : battle.getSnapshotFor(playerSide)) { 674 if (ally.isPlayerFleet()) continue; 675 676 String factionId = ally.getFaction().getId(); 677 if (seen.contains(factionId)) continue; 678 seen.add(factionId); 679 680 Float friendlyFPHull = playerFPHullDamageToAlliesByFaction.get(ally.getFaction()); 681 float threshold = 2f; 682 if (action == RepActions.COMBAT_HELP_MAJOR) { 683 threshold = 5f; 684 } else if (action == RepActions.COMBAT_HELP_CRITICAL) { 685 threshold = 10f; 686 } 687 if (friendlyFPHull != null && friendlyFPHull > threshold) { 688 // can lose reputation with sides that didn't survive 689 //Global.getSector().adjustPlayerReputation(new RepActionEnvelope(RepActions.COMBAT_FRIENDLY_FIRE, (friendlyFPHull - threshold), dialog.getTextPanel()), factionId); 690 } else if (action != null && playerSide.contains(ally)) { 691 // only gain reputation with factions whose fleets actually survived 692 Global.getSector().adjustPlayerReputation(new RepActionEnvelope(action, null, dialog.getTextPanel()), factionId); 693 printedAdjustmentText = true; 694 } 695 } 696 697 698 // friendly fire rep decreases 699 if (okToAdjustAlly) { 700 boolean first = true; 701 seen.clear(); 702 for (CampaignFleetAPI ally : battle.getSnapshotFor(playerSide)) { 703 if (ally.isPlayerFleet()) continue; 704 705 String factionId = ally.getFaction().getId(); 706 if (Factions.PLAYER.equals(factionId)) continue; 707 if (seen.contains(factionId)) continue; 708 seen.add(factionId); 709 710 Float friendlyFPHull = playerFPHullDamageToAlliesByFaction.get(ally.getFaction()); 711 float threshold = 2f; 712 if (action == RepActions.COMBAT_HELP_MAJOR) { 713 threshold = 5f; 714 } else if (action == RepActions.COMBAT_HELP_CRITICAL) { 715 threshold = 10f; 716 } 717 if (friendlyFPHull != null && friendlyFPHull > threshold) { 718 if (first && ffText != null) { 719 first = false; 720 dialog.getTextPanel().addParagraph(ffText); 721 } 722 // can lose reputation with sides that didn't survive 723 Global.getSector().adjustPlayerReputation(new RepActionEnvelope(RepActions.COMBAT_FRIENDLY_FIRE, (friendlyFPHull - threshold), dialog.getTextPanel()), factionId); 724 printedAdjustmentText = true; 725 } else if (action != null && playerSide.contains(ally)) { 726 // only gain reputation with factions whose fleets actually survived 727 //Global.getSector().adjustPlayerReputation(new RepActionEnvelope(action, null, dialog.getTextPanel()), factionId); 728 } 729 } 730 } 731 //} 732 return printedAdjustmentText; 733 } 734 735 return false; 736 } 737 738 739 public TextPanelAPI textPanelForXPGain = null; 740 public TextPanelAPI getTextPanelForXPGain() { 741 return textPanelForXPGain; 742 } 743 744 public void setTextPanelForXPGain(TextPanelAPI textPanelForXPGain) { 745 this.textPanelForXPGain = textPanelForXPGain; 746 } 747 748 protected boolean noHarryBecauseOfStation = false; 749 public boolean isNoHarryBecauseOfStation() { 750 return noHarryBecauseOfStation; 751 } 752 public void setNoHarryBecauseOfStation(boolean noHarryBecauseOfStation) { 753 this.noHarryBecauseOfStation = noHarryBecauseOfStation; 754 } 755 756 public void applyAfterBattleEffectsIfThereWasABattle() { 757 if (!hasWinnerAndLoser() || !engagedInHostilities) { 758 for (FleetMemberAPI member : Global.getSector().getPlayerFleet().getFleetData().getMembersListCopy()) { 759 member.getStatus().resetAmmoState(); 760 } 761 762 if (noHarryBecauseOfStation && battle != null) { 763 List<CampaignFleetAPI> otherSide = battle.getNonPlayerSide(); 764 CampaignFleetAPI fleet = battle.getPrimary(otherSide); 765 if (fleet.getAI() != null && 766 !fleet.getAI().isCurrentAssignment(FleetAssignment.STANDING_DOWN)) { 767 fleet.getAI().addAssignmentAtStart(FleetAssignment.STANDING_DOWN, fleet, 0.5f + 0.5f * (float) Math.random(), null); 768 } 769 } 770 771 //Global.getSector().setLastPlayerBattleTimestamp(Global.getSector().getClock().getTimestamp()); 772 Global.getSector().getPlayerFleet().setNoEngaging(3f); 773 return; 774 } 775 776 gainXP(); 777 addPotentialOfficer(); 778 779// CampaignFleetAPI winner = getWinnerData().getFleet(); 780// CampaignFleetAPI loser = getLoserData().getFleet(); 781// List<CampaignFleetAPI> winners = battle.getSideFor(getWinnerData().getFleet()); 782// List<CampaignFleetAPI> losers = battle.getSideFor(getLoserData().getFleet()); 783 List<CampaignFleetAPI> winners = battle.getSnapshotSideFor(getWinnerData().getFleet()); 784 List<CampaignFleetAPI> losers = battle.getSnapshotSideFor(getLoserData().getFleet()); 785 if (winners == null || losers == null) return; 786 787 for (CampaignFleetAPI loser : losers) { 788 for (FleetMemberAPI member : loser.getFleetData().getMembersListCopy()) { 789 member.getStatus().resetAmmoState(); 790 } 791 792 loser.getVelocity().set(0, 0); 793 if (loser.isPlayerFleet()) continue; 794 if (loser.isPlayerFleet()) loser.setNoEngaging(3f); 795 796 } 797 for (CampaignFleetAPI winner : winners) { 798 for (FleetMemberAPI member : winner.getFleetData().getMembersListCopy()) { 799 member.getStatus().resetAmmoState(); 800 } 801 802 winner.getVelocity().set(0, 0); 803 if (winner.isPlayerFleet()) continue; 804 if (winner.isPlayerFleet()) winner.setNoEngaging(3f); 805 806 } 807 808 if (battle.isPlayerSide(winners)) { 809 for (CampaignFleetAPI fleet : battle.getPlayerSide()) { 810 if (fleet.isPlayerFleet()) continue; 811 812 Misc.forgetAboutTransponder(fleet); 813 } 814 } 815 816 battle.setPlayerInvolvementFraction(computePlayerContribFraction()); 817 818 if (!isAutoresolve && engagedInActualBattle) { 819 Global.getSector().reportBattleOccurred(battle.getPrimary(winners), battle); 820 Global.getSector().reportBattleFinished(battle.getPrimary(winners), battle); 821 } 822 823 CampaignFleetAPI largestWinner = battle.getPrimary(winners); 824 for (CampaignFleetAPI loser : losers) { 825 if (loser.getFleetData().getMembersListCopy().isEmpty()) { 826 //Global.getSector().reportFleetDewspawned(loser, FleetDespawnReason.DESTROYED_BY_FLEET, winner); 827 loser.despawn(FleetDespawnReason.DESTROYED_BY_BATTLE, battle); 828 } 829 } 830 831 for (CampaignFleetAPI winner : winners) { 832 if (winner.getFleetData().getMembersListCopy().isEmpty()) { 833 //Global.getSector().reportFleetDewspawned(loser, FleetDespawnReason.DESTROYED_BY_FLEET, winner); 834 winner.despawn(FleetDespawnReason.DESTROYED_BY_BATTLE, battle); 835 } 836 } 837 838 for (CampaignFleetAPI enemy : battle.getBothSides()) { 839 if (enemy.getAI() instanceof ModularFleetAIAPI) { 840 ModularFleetAIAPI mAI = (ModularFleetAIAPI) enemy.getAI(); 841 mAI.getTacticalModule().forceTargetReEval(); 842 } 843 } 844 845 } 846 847 848 849 850 public float performPostVictoryRecovery(EngagementResultAPI result) { 851 EngagementResultForFleetAPI winnerResult = result.getWinnerResult(); 852 EngagementResultForFleetAPI loserResult = result.getLoserResult(); 853 return performPostVictoryRecovery(winnerResult, loserResult); 854 } 855 public float performPostEngagementRecoveryBoth(EngagementResultAPI result) { 856 EngagementResultForFleetAPI winnerResult = result.getWinnerResult(); 857 EngagementResultForFleetAPI loserResult = result.getLoserResult(); 858 float f = performPostVictoryRecovery(winnerResult, loserResult); 859 f += performPostVictoryRecovery(loserResult, winnerResult); 860 f /= 2f; 861 return f; 862 } 863 864 865 public float performPostVictoryRecovery(EngagementResultForFleetAPI winnerResult, EngagementResultForFleetAPI loserResult) { 866 DataForEncounterSide winnerData = getDataFor(winnerResult.getFleet()); 867 DataForEncounterSide loserData = getDataFor(loserResult.getFleet()); 868 869 //float totalFpUsed = 0f; 870 float loserDepDestroyed = 0f; 871 float loserDepLeft = 0f; 872 873 for (FleetMemberAPI member : loserData.getRetreatedFromLastEngagement()) { 874 loserDepLeft += member.getDeploymentPointsCost(); 875 } 876 for (FleetMemberAPI member : loserData.getInReserveDuringLastEngagement()) { 877 loserDepLeft += member.getDeploymentPointsCost(); 878 } 879 880 for (FleetMemberAPI member : loserData.getDestroyedInLastEngagement()) { 881 loserDepDestroyed += member.getDeploymentPointsCost(); 882 } 883 for (FleetMemberAPI member : loserData.getDisabledInLastEngagement()) { 884 loserDepDestroyed += member.getDeploymentPointsCost(); 885 } 886 for (FleetMemberAPI member : loserData.getRetreatedFromLastEngagement()) { 887 if (member.isFighterWing()) { 888 DeployedFleetMemberAPI dfm = getDataFor(loserData.getFleet()).getMemberToDeployedMap().get(member); 889 if (dfm != null && dfm.getMember() == member) { 890 float deploymentCR = dfm.getShip().getWingCRAtDeployment(); 891 float finalCR = deploymentCR; 892 //float finalCR = dfm.getShip().getRemainingWingCR(); 893 if (deploymentCR > finalCR) { 894 float crPer = dfm.getMember().getStats().getCRPerDeploymentPercent().computeEffective(dfm.getMember().getVariant().getHullSpec().getCRToDeploy()) / 100f; 895 float extraCraftLost = (deploymentCR - finalCR) / crPer; 896 float wingSize = dfm.getMember().getNumFightersInWing(); 897 if (extraCraftLost >= 1) { 898 loserDepDestroyed += Math.min(1f, extraCraftLost / wingSize) * member.getDeploymentPointsCost(); 899 } 900 } 901 } 902 } 903 } 904 905 float totalRecovery = 0f; 906 float count = 0f; 907 for (FleetMemberAPI member : winnerData.getDeployedInLastEngagement()) { 908 float dp = member.getDeploymentPointsCost(); 909 float recoveryFraction = Math.max(0, (dp * 1.25f - loserDepDestroyed)) / dp; 910// if (member.getFleetData() != null && member.getFleetData().getFleet() != null && 911// member.getFleetData().getFleet().isPlayerFleet()) { 912 if (loserDepDestroyed > loserDepLeft * 2f) { 913 recoveryFraction = Math.max(0, (dp * 0.75f - loserDepDestroyed)) / dp; 914 } 915 if (recoveryFraction > 1f) recoveryFraction = 1f; 916 if (loserDepDestroyed <= 0) recoveryFraction = 1f; 917 918 float deployCost = getDeployCost(member); 919 if (preEngagementCRForWinner.containsKey(member)) { 920 float prevCR = preEngagementCRForWinner.get(member); 921 if (prevCR < deployCost) { 922 prevCR = deployCost; 923 } 924 } 925 926 float recoveryAmount = Math.round(deployCost * recoveryFraction * 100f) / 100f; 927 if (member.getHullSpec().hasTag(Tags.FULL_CR_RECOVERY)) { 928 recoveryAmount = member.getRepairTracker().getMaxCR() - member.getRepairTracker().getCR(); 929 } 930 931 totalRecovery += recoveryAmount; 932 count++; 933 934 if (recoveryAmount <= 0) continue; 935 936 937 member.getRepairTracker().applyCREvent(recoveryAmount, "Post engagement recovery"); 938 } 939 940 if (count <= 0) return 0; 941 942 return Math.round(totalRecovery / count * 100f) / 100f; 943 } 944 945 946 947 public void applyPursuitOption(CampaignFleetAPI pursuingFleet, CampaignFleetAPI otherFleet, PursuitOption pursuitOption) { 948 949 if (Misc.isPlayerOrCombinedPlayerPrimary(pursuingFleet) && pursuitOption != PursuitOption.LET_THEM_GO) { 950 playerOnlyRetreated = false; 951 } 952 953 DataForEncounterSide pursuer = getDataFor(pursuingFleet); 954 DataForEncounterSide other = getDataFor(otherFleet); 955 956 if (pursuitOption == PursuitOption.HARRY) { 957 for (FleetMemberAPI member : otherFleet.getFleetData().getMembersListCopy()) { 958 float deployCost = getDeployCost(member); 959 960 float harryCost = deployCost * 1f; 961 member.getRepairTracker().applyCREvent(-harryCost, "harried while disengaging"); 962 } 963 } 964 } 965 966 967 968 protected EngagementOutcome lastOutcome = null; 969 public EngagementOutcome getLastEngagementOutcome() { 970 return lastOutcome; 971 } 972 973 public boolean isBattleOver() { 974 if (hasWinnerAndLoser()) return true; 975 976 return lastOutcome != null && 977 lastOutcome != EngagementOutcome.BATTLE_PLAYER_OUT_FIRST_WIN && 978 lastOutcome != EngagementOutcome.BATTLE_PLAYER_OUT_FIRST_LOSS && 979 lastOutcome != EngagementOutcome.BATTLE_ENEMY_WIN && 980 lastOutcome != EngagementOutcome.BATTLE_PLAYER_WIN; 981 } 982 983 public boolean wasLastEngagementEscape() { 984 return lastOutcome != null && 985// lastOutcome != EngagementOutcome.ESCAPE_PLAYER_OUT_FIRST_WIN && 986// lastOutcome != EngagementOutcome.ESCAPE_PLAYER_OUT_FIRST_LOSS & 987 lastOutcome != EngagementOutcome.BATTLE_ENEMY_WIN && 988 lastOutcome != EngagementOutcome.BATTLE_PLAYER_WIN; 989 } 990 991 public boolean didPlayerWinLastEngagement() { 992 return lastOutcome == EngagementOutcome.BATTLE_PLAYER_WIN || 993 lastOutcome == EngagementOutcome.BATTLE_PLAYER_WIN_TOTAL || 994 lastOutcome == EngagementOutcome.BATTLE_PLAYER_OUT_FIRST_WIN || 995 lastOutcome == EngagementOutcome.PURSUIT_PLAYER_OUT_FIRST_WIN || 996 lastOutcome == EngagementOutcome.ESCAPE_PLAYER_OUT_FIRST_WIN || 997 lastOutcome == EngagementOutcome.ESCAPE_ENEMY_LOSS_TOTAL || 998 lastOutcome == EngagementOutcome.ESCAPE_ENEMY_SUCCESS || 999 lastOutcome == EngagementOutcome.ESCAPE_PLAYER_WIN || 1000 lastOutcome == EngagementOutcome.ESCAPE_PLAYER_WIN_TOTAL; 1001 } 1002 1003 /** 1004 * The difference from didPlayerWinEncounterOutright() is that the opposing fleet may 1005 * still choose to re-engage. 1006 * @return 1007 */ 1008 public boolean didPlayerWinMostRecentBattleOfEncounter() { 1009 if (getDataFor(Global.getSector().getPlayerFleet()).disengaged()) return false; 1010 1011 return didPlayerWinEncounterOutright() || lastOutcome == EngagementOutcome.BATTLE_PLAYER_WIN; 1012 } 1013 1014 /** 1015 * Player won, and it's over - no more fighting is *possible* in this encounter. 1016 * @return 1017 */ 1018 public boolean didPlayerWinEncounterOutright() { 1019 if (getDataFor(Global.getSector().getPlayerFleet()).disengaged()) return false; 1020 1021 // non-fighting "win", i.e. harrying a weaker enemy 1022 //if (lastOutcome == null && getWinner() == Global.getSector().getPlayerFleet()) { 1023 //if (lastOutcome == null && battle.isPlayerSide(battle.getSideFor(getWinner()))) { 1024 if ((lastOutcome == null || hasWinnerAndLoser()) && battle.isPlayerSide(battle.getSideFor(getWinner()))) { 1025 return true; 1026 } 1027 1028 return lastOutcome == EngagementOutcome.BATTLE_PLAYER_WIN_TOTAL || 1029 //lastOutcome == EngagementOutcome.BATTLE_PLAYER_WIN || 1030 lastOutcome == EngagementOutcome.ESCAPE_ENEMY_LOSS_TOTAL || 1031 lastOutcome == EngagementOutcome.ESCAPE_ENEMY_SUCCESS || 1032 lastOutcome == EngagementOutcome.ESCAPE_PLAYER_WIN || 1033 lastOutcome == EngagementOutcome.ESCAPE_PLAYER_WIN_TOTAL; 1034 } 1035 1036 1037 public int getCreditsLooted() { 1038 return creditsLooted; 1039 } 1040 1041 public float getSalvageMult(Status status) { 1042 float mult = 1f; 1043 switch (status) { 1044 case DESTROYED: 1045 //mult = 0.5f; 1046 mult = 1f; 1047 break; 1048 case DISABLED: 1049 mult = 1f; 1050 break; 1051 case REPAIRED: 1052 mult = 1f; 1053 break; 1054 case CAPTURED: 1055 mult = 0.1f; 1056 break; 1057 } 1058 return mult; 1059 } 1060 1061 public float getCargoLootMult(Status status) { 1062 float mult = 1f; 1063 switch (status) { 1064 case DESTROYED: 1065 mult = 1f; 1066 break; 1067 case DISABLED: 1068 mult = 1f; 1069 break; 1070 case REPAIRED: 1071 mult = 1f; 1072 break; 1073 case CAPTURED: 1074 mult = 1f; 1075 break; 1076 } 1077 return mult; 1078 } 1079 1080// public List<FleetMemberAPI> repairShips() { 1081// DataForEncounterSide winner = getWinnerData(); 1082// return repairShips(winner); 1083// 1084//// DataForEncounterSide loser = getLoserData(); 1085//// repairShips(loser); 1086// } 1087 1088 public static enum EngageBoardableOutcome { 1089 ESCAPED, 1090 DISABLED, 1091 DESTROYED, 1092 } 1093 1094 public EngageBoardableOutcome engageBoardableShip(FleetMemberAPI toBoard, 1095 CampaignFleetAPI fleetItBelongsTo, 1096 CampaignFleetAPI attackingFleet) { 1097 float r = (float) Math.random(); 1098// if (r < ENGAGE_ESCAPE_CHANCE && !Misc.isPlayerOrCombinedContainingPlayer(attackingFleet)) { 1099// // escaped 1100// CampaignFleetAPI fleet = getBattle().getSourceFleet(toBoard); 1101// letBoardableGo(toBoard, fleet, attackingFleet); 1102// 1103// return EngageBoardableOutcome.ESCAPED; 1104// } else 1105 if (r < ENGAGE_ESCAPE_CHANCE + ENGAGE_DISABLE_CHANCE) { 1106 // disabled 1107 DataForEncounterSide attackerSide = getDataFor(attackingFleet); 1108 attackerSide.changeEnemy(toBoard, Status.DISABLED); 1109 toBoard.getStatus().disable(); 1110 return EngageBoardableOutcome.DISABLED; 1111 } else { 1112 DataForEncounterSide attackerSide = getDataFor(attackingFleet); 1113 attackerSide.changeEnemy(toBoard, Status.DESTROYED); 1114 toBoard.getStatus().disable(); 1115 return EngageBoardableOutcome.DESTROYED; 1116 } 1117 } 1118 1119 1120 public static enum BoardingAttackType { 1121 SHIP_TO_SHIP, 1122 LAUNCH_FROM_DISTANCE, 1123 } 1124 public static enum BoardingOutcome { 1125 SUCCESS, 1126 SELF_DESTRUCT, 1127 SUCCESS_TOO_DAMAGED, 1128 SHIP_ESCAPED, 1129 SHIP_ESCAPED_CLEAN, 1130 } 1131 1132 public static class BoardingResult { 1133 private BoardingOutcome outcome; 1134 private CrewCompositionAPI attackerLosses = Global.getFactory().createCrewComposition(); 1135 private CrewCompositionAPI defenderLosses = Global.getFactory().createCrewComposition(); 1136 private FleetMemberAPI member; 1137 private List<FleetMemberAPI> lostInSelfDestruct = new ArrayList<FleetMemberAPI>(); 1138 1139 public BoardingOutcome getOutcome() { 1140 return outcome; 1141 } 1142 public List<FleetMemberAPI> getLostInSelfDestruct() { 1143 return lostInSelfDestruct; 1144 } 1145 public void setOutcome(BoardingOutcome outcome) { 1146 this.outcome = outcome; 1147 } 1148 public CrewCompositionAPI getAttackerLosses() { 1149 return attackerLosses; 1150 } 1151 public void setAttackerLosses(CrewCompositionAPI attackerLosses) { 1152 this.attackerLosses = attackerLosses; 1153 } 1154 public CrewCompositionAPI getDefenderLosses() { 1155 return defenderLosses; 1156 } 1157 public void setDefenderLosses(CrewCompositionAPI defenderLosses) { 1158 this.defenderLosses = defenderLosses; 1159 } 1160 public FleetMemberAPI getMember() { 1161 return member; 1162 } 1163 public void setMember(FleetMemberAPI member) { 1164 this.member = member; 1165 } 1166 1167 } 1168 1169 public static final float SELF_DESTRUCT_CHANCE = 0.25f; 1170 public static final float CIV_SELF_DESTRUCT_CHANCE = 0.05f; 1171 1172 public static final float ENGAGE_ESCAPE_CHANCE = 0.25f; 1173 public static final float ENGAGE_DISABLE_CHANCE = 0.5f; 1174 public static final float ENGAGE_DESTROY_CHANCE = 0.25f; 1175 1176 public static final float LAUNCH_CLEAN_ESCAPE_CHANCE = 0.5f; 1177 public static final float DOCK_SUCCESS_CHANCE = 0.5f; 1178 public static final float LAUNCH_SUCCESS_CHANCE = 0.25f; 1179 1180 //public static final float DEFENDER_BONUS = 4f; 1181 //public static final float DEFENDER_VS_LAUNCH_BONUS = 3f; 1182 1183 public BoardingResult boardShip(FleetMemberAPI member, CampaignFleetAPI attacker, CampaignFleetAPI defender) { 1184 1185 1186 DataForEncounterSide attackerSide = getDataFor(attacker); 1187 DataForEncounterSide defenderSide = getDataFor(defender); 1188 1189 float attackerMarineMult = attacker.getCommanderStats().getMarineEffectivnessMult().getModifiedValue(); 1190 float defenderMarineMult = defender.getCommanderStats().getMarineEffectivnessMult().getModifiedValue(); 1191 1192 float crewMult = 2f; 1193 float marineMult = 7f; 1194 1195 1196 float attackerStr = attacker.getCargo().getMarines() * marineMult; 1197 attackerStr *= attackerMarineMult; 1198 1199 CrewCompositionAPI defenderCrew = member.getCrewComposition(); 1200 float defenderStr = defenderCrew.getCrew() * crewMult + defenderCrew.getMarines() * marineMult; 1201 defenderStr *= defenderMarineMult; 1202 1203 //defenderStr *= Global.getSettings().getFloat("boardingDifficulty"); 1204 1205 Random rand = new Random(1300000 * (member.getId().hashCode() + defender.getId().hashCode() + Global.getSector().getClock().getDay())); 1206 attackerStr *= 0.75f + 0.25f * rand.nextFloat(); 1207 defenderStr *= 0.75f + 0.25f * rand.nextFloat(); 1208 1209 boolean attackerWin = attackerStr > defenderStr; 1210 boolean defenderWin = !attackerWin; 1211 1212 BoardingResult result = new BoardingResult(); 1213 result.setMember(member); 1214 1215 1216 BoardingOutcome outcome = BoardingOutcome.SUCCESS; 1217 if (defenderWin) { 1218 outcome = BoardingOutcome.SHIP_ESCAPED; 1219 } 1220 1221 CrewCompositionAPI boardingParty = Global.getFactory().createCrewComposition(); 1222 boardingParty.addMarines(attacker.getCargo().getMarines()); 1223 1224 result.setOutcome(outcome); 1225 switch (outcome) { 1226 case SHIP_ESCAPED: 1227 computeCrewLossFromBoarding(result, member, boardingParty, attackerStr, defenderStr); 1228 result.getAttackerLosses().removeFromCargo(attacker.getCargo()); 1229 member.getCrewComposition().removeAll(result.getDefenderLosses()); 1230 1231 letBoardableGo(member, defender, attacker); 1232 break; 1233 case SUCCESS: 1234 computeCrewLossFromBoarding(result, member, boardingParty, attackerStr, defenderStr); 1235 result.getAttackerLosses().removeFromCargo(attacker.getCargo()); 1236 member.getCrewComposition().removeAll(result.getDefenderLosses()); 1237 1238 //attackerSide.removeEnemyCasualty(member); 1239 attacker.getFleetData().addFleetMember(member); 1240 getBattle().getCombinedFor(attacker).getFleetData().addFleetMember(member); 1241 1242 member.getRepairTracker().setMothballed(true); 1243 //defender.getFleetData().removeFleetMember(member); 1244 1245 attackerSide.changeEnemy(member, Status.CAPTURED); 1246 defenderSide.changeOwn(member, Status.CAPTURED); 1247 1248 attackerSide.getInReserveDuringLastEngagement().add(member); 1249 defenderSide.getDestroyedInLastEngagement().remove(member); 1250 defenderSide.getDisabledInLastEngagement().remove(member); 1251 1252 member.setOwner(0); 1253 member.setCaptain(Global.getFactory().createPerson()); 1254 break; 1255 } 1256 1257 return result; 1258 } 1259 1260 public float getBoardingSuccessPercent(FleetMemberAPI member, CampaignFleetAPI attacker, CampaignFleetAPI defender) { 1261 DataForEncounterSide attackerSide = getDataFor(attacker); 1262 DataForEncounterSide defenderSide = getDataFor(defender); 1263 float attackerMarineMult = attacker.getCommanderStats().getMarineEffectivnessMult().getModifiedValue(); 1264 float defenderMarineMult = defender.getCommanderStats().getMarineEffectivnessMult().getModifiedValue(); 1265 1266 float crewMult = 2f; 1267 float marineMult = 7f; 1268 1269 Random rand = new Random(); 1270 1271 float wins = 0; 1272 float losses = 0; 1273 1274 for (int i = 0; i < 100; i++) { 1275 float attackerStr = attacker.getCargo().getMarines() * marineMult; 1276 attackerStr *= attackerMarineMult; 1277 1278 CrewCompositionAPI defenderCrew = member.getCrewComposition(); 1279 float defenderStr = defenderCrew.getCrew() * crewMult + defenderCrew.getMarines() * marineMult; 1280 defenderStr *= defenderMarineMult; 1281 1282 //defenderStr *= Global.getSettings().getFloat("boardingDifficulty"); 1283 1284 attackerStr *= 0.75f + 0.25f * rand.nextFloat(); 1285 defenderStr *= 0.75f + 0.25f * rand.nextFloat(); 1286 1287 boolean attackerWin = attackerStr > defenderStr; 1288 if (attackerWin) wins++; 1289 else losses++; 1290 } 1291 1292 return wins; 1293 } 1294 1295 1296 protected void computeMissedLaunchLosses(BoardingResult result, CrewCompositionAPI boardingParty) { 1297 result.getAttackerLosses().addAll(boardingParty); 1298 result.getAttackerLosses().multiplyBy((float) Math.random() * 0.2f); 1299 } 1300 1301 protected void computeCrewLossFromBoarding(BoardingResult result, 1302 FleetMemberAPI member, CrewCompositionAPI boardingParty, 1303 float attackerStr, float defenderStr) { 1304 1305 if (attackerStr < 1) attackerStr = 1; 1306 if (defenderStr < 1) defenderStr = 1; 1307 float cap = 2f; 1308 float attackerExtraStr = 0f; 1309 if (attackerStr > defenderStr * cap) { 1310 attackerExtraStr = attackerStr - defenderStr * cap; 1311 attackerStr = defenderStr * cap; 1312 } 1313 if (defenderStr > attackerStr * cap) { 1314 defenderStr = attackerStr * cap; 1315 } 1316 1317 float attackerLosses = defenderStr / (attackerStr + defenderStr); 1318 float defenderLosses = attackerStr / (attackerStr + defenderStr); 1319 1320 if (attackerStr > defenderStr) { 1321 result.getAttackerLosses().addAll(boardingParty); 1322 result.getAttackerLosses().multiplyBy(attackerLosses * attackerStr / (attackerExtraStr + attackerStr)); 1323 result.getDefenderLosses().addAll(member.getCrewComposition()); 1324 result.getDefenderLosses().multiplyBy(defenderLosses); 1325 } else { 1326 result.getAttackerLosses().addAll(boardingParty); 1327 result.getAttackerLosses().multiplyBy(attackerLosses); 1328 result.getDefenderLosses().addAll(member.getCrewComposition()); 1329 result.getDefenderLosses().multiplyBy(defenderLosses); 1330 } 1331 //member.getCrewComposition().removeAll(result.getDefenderLosses()); 1332 } 1333 1334 1335 protected void applyBoardingSelfDestruct(FleetMemberAPI member, 1336 CrewCompositionAPI boardingParty, BoardingAttackType attackType, 1337 List<FleetMemberAPI> boardingTaskForce, 1338 CampaignFleetAPI attacker, CampaignFleetAPI defender, 1339 BoardingResult result) { 1340 1341 DataForEncounterSide attackerSide = getDataFor(attacker); 1342 DataForEncounterSide defenderSide = getDataFor(defender); 1343 1344 attackerSide.changeEnemy(member, Status.DESTROYED); 1345 defenderSide.changeOwn(member, Status.DESTROYED); 1346 1347 1348 CrewCompositionAPI total = Global.getFactory().createCrewComposition(); 1349 1350 if (attackType == BoardingAttackType.SHIP_TO_SHIP) { 1351 for (FleetMemberAPI fm : boardingTaskForce) { 1352 float damage = member.getStats().getFluxCapacity().getModifiedValue() * (1f + (float) Math.random() * 0.5f); 1353 float hull = fm.getStatus().getHullFraction(); 1354 float hullDamageFactor = 0f; 1355 fm.getStatus().applyDamage(damage); 1356 if (fm.getStatus().getHullFraction() <= 0) { 1357 fm.getStatus().disable(); 1358 attacker.getFleetData().removeFleetMember(fm); 1359 attackerSide.addOwn(fm, Status.DESTROYED); 1360 //total.addAll(fm.getCrewComposition()); 1361 1362 attackerSide.getRetreatedFromLastEngagement().remove(fm); 1363 attackerSide.getInReserveDuringLastEngagement().remove(fm); 1364 attackerSide.getDeployedInLastEngagement().remove(fm); 1365 attackerSide.getDestroyedInLastEngagement().add(fm); 1366 1367 result.getLostInSelfDestruct().add(fm); 1368 1369 hullDamageFactor = 1f; 1370 } else { 1371 float newHull = fm.getStatus().getHullFraction(); 1372 float diff = hull - newHull; 1373 if (diff < 0) diff = 0; 1374 hullDamageFactor = diff; 1375 } 1376 CrewCompositionAPI temp = Global.getFactory().createCrewComposition(); 1377 temp.addAll(fm.getCrewComposition()); 1378 float lossFraction = computeLossFraction(fm, null, fm.getStatus().getHullFraction(), hullDamageFactor); 1379 temp.multiplyBy(lossFraction); 1380 total.addAll(temp); 1381 } 1382 //total.removeAll(boardingParty); 1383 } 1384 1385 float lossFraction = computeLossFraction(null, null, 0f, 1f); 1386 total.setMarines(Math.round(Math.max(total.getMarines(), boardingParty.getMarines() * lossFraction))); 1387 total.setCrew(Math.round(Math.max(total.getCrew(), boardingParty.getCrew() * lossFraction))); 1388 1389 //result.getAttackerLosses().addAll(boardingParty); 1390// float lossFraction = computeLossFraction(boardingTaskForce.get(0), 0f, 1f); 1391// total.multiplyBy(lossFraction); 1392 1393 result.getAttackerLosses().addAll(total); 1394 result.getDefenderLosses().addAll(member.getCrewComposition()); 1395 } 1396 1397 public void letBoardableGo(FleetMemberAPI toBoard, CampaignFleetAPI fleetItBelongsTo, CampaignFleetAPI attackingFleet) { 1398 DataForEncounterSide attackerSide = getDataFor(attackingFleet); 1399 attackerSide.removeEnemyCasualty(toBoard); 1400 1401 DataForEncounterSide defenderSide = getDataFor(fleetItBelongsTo); 1402 defenderSide.removeOwnCasualty(toBoard); 1403 1404 1405 defenderSide.getDestroyedInLastEngagement().remove(toBoard); 1406 defenderSide.getDisabledInLastEngagement().remove(toBoard); 1407 defenderSide.getRetreatedFromLastEngagement().add(toBoard); 1408 1409 if (!fleetItBelongsTo.isValidPlayerFleet()) { 1410 fleetItBelongsTo.getCargo().removeCrew(fleetItBelongsTo.getCargo().getCrew()); 1411 fleetItBelongsTo.getCargo().removeMarines(fleetItBelongsTo.getCargo().getMarines()); 1412 } 1413 1414 FleetDataAPI data = fleetItBelongsTo.getFleetData(); 1415 data.addFleetMember(toBoard); 1416 1417 getBattle().getCombinedFor(fleetItBelongsTo).getFleetData().addFleetMember(toBoard); 1418 1419 toBoard.getCrewComposition().addToCargo(fleetItBelongsTo.getCargo()); 1420 } 1421 1422 public List<FleetMemberAPI> getStoryRecoverableShips() { 1423 return storyRecoverableShips; 1424 } 1425 1426 protected List<FleetMemberAPI> recoverableShips = new ArrayList<FleetMemberAPI>(); 1427 protected List<FleetMemberAPI> storyRecoverableShips = new ArrayList<FleetMemberAPI>(); 1428 public List<FleetMemberAPI> getRecoverableShips(BattleAPI battle, CampaignFleetAPI winningFleet, CampaignFleetAPI otherFleet) { 1429 1430 storyRecoverableShips.clear(); 1431 1432 List<FleetMemberAPI> result = new ArrayList<FleetMemberAPI>(); 1433// int max = Global.getSettings().getMaxShipsInFleet() - 1434// Global.getSector().getPlayerFleet().getFleetData().getMembersListCopy().size(); 1435// if (Misc.isPlayerOrCombinedContainingPlayer(winningFleet) && max <= 0) { 1436// return result; 1437// } 1438 1439 if (Misc.isPlayerOrCombinedContainingPlayer(otherFleet)) { 1440 return result; 1441 } 1442 1443 DataForEncounterSide winnerData = getDataFor(winningFleet); 1444 DataForEncounterSide loserData = getDataFor(otherFleet); 1445 1446 float playerContribMult = computePlayerContribFraction(); 1447 List<FleetMemberData> enemyCasualties = winnerData.getEnemyCasualties(); 1448 List<FleetMemberData> ownCasualties = winnerData.getOwnCasualties(); 1449 List<FleetMemberData> all = new ArrayList<FleetMemberData>(); 1450 all.addAll(ownCasualties); 1451 Collections.sort(all, new Comparator<FleetMemberData>() { 1452 public int compare(FleetMemberData o1, FleetMemberData o2) { 1453 int result = o2.getMember().getVariant().getSMods().size() - o1.getMember().getVariant().getSMods().size(); 1454 if (result == 0) { 1455 result = o2.getMember().getHullSpec().getHullSize().ordinal() - o1.getMember().getHullSpec().getHullSize().ordinal(); 1456 } 1457 return result; 1458 } 1459 }); 1460 1461 1462 //Random random = Misc.getRandom(battle.getSeed(), 11); 1463 Random random = Misc.getRandom(Global.getSector().getPlayerBattleSeed(), 11); 1464 //System.out.println("BATTLE SEED: " + Global.getSector().getPlayerBattleSeed()); 1465 1466 // since the number of recoverable ships is limited, prefer "better" ships 1467 WeightedRandomPicker<FleetMemberData> enemyPicker = new WeightedRandomPicker<FleetMemberData>(random); 1468 1469 // doesn't matter how it's sorted, as long as it's consistent so that 1470 // the order it's insertied into the picker in is the same 1471 List<FleetMemberData> enemy = new ArrayList<FleetMemberData>(enemyCasualties); 1472 Collections.sort(enemy, new Comparator<FleetMemberData>() { 1473 public int compare(FleetMemberData o1, FleetMemberData o2) { 1474 int result = o2.getMember().getId().hashCode() - o1.getMember().getId().hashCode(); 1475 return result; 1476 } 1477 }); 1478 1479 for (FleetMemberData curr : enemy) { 1480 float base = 10f; 1481 switch (curr.getMember().getHullSpec().getHullSize()) { 1482 case CAPITAL_SHIP: base = 40f; break; 1483 case CRUISER: base = 20f; break; 1484 case DESTROYER: base = 10f; break; 1485 case FRIGATE: base = 5f; break; 1486 } 1487 float w = curr.getMember().getUnmodifiedDeploymentPointsCost() / base; 1488 1489 enemyPicker.add(curr, w); 1490 } 1491 List<FleetMemberData> sortedEnemy = new ArrayList<FleetMemberData>(); 1492 while (!enemyPicker.isEmpty()) { 1493 sortedEnemy.add(enemyPicker.pickAndRemove()); 1494 } 1495 1496 1497 all.addAll(sortedEnemy); 1498 1499// for (FleetMemberData curr : all) { 1500// System.out.println(curr.getMember().getHullId()); 1501// } 1502 1503 CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet(); 1504 1505 int maxRecoverablePerType = 24; 1506 1507 float probLessDModsOnNext = Global.getSettings().getFloat("baseProbLessDModsOnRecoverableEnemyShip"); 1508 float lessDmodsOnNextMult = Global.getSettings().getFloat("lessDModsOnRecoverableEnemyShipMultNext"); 1509 1510 int count = 0; 1511 for (FleetMemberData data : all) { 1512// if (data.getMember().getHullId().contains("legion")) { 1513// System.out.println("wefwefwefe"); 1514// } 1515 //if (data.getMember().getHullSpec().getHints().contains(ShipTypeHints.UNBOARDABLE)) continue; 1516 if (Misc.isUnboardable(data.getMember())) continue; 1517 if (data.getStatus() != Status.DISABLED && data.getStatus() != Status.DESTROYED) continue; 1518 1519 boolean own = ownCasualties.contains(data); 1520 if (own && data.getMember().isAlly()) continue; 1521 1522// if (data.getMember().getHullId().startsWith("vanguard_pirates")) { 1523// System.out.println("wefwefwefe12341234"); 1524// } 1525 1526 float mult = 1f; 1527 if (data.getStatus() == Status.DESTROYED) mult = 0.5f; 1528 if (!own) mult *= playerContribMult; 1529 1530 1531 boolean useOfficerRecovery = false; 1532 if (own) { 1533 useOfficerRecovery = winnerData.getMembersWithOfficerOrPlayerAsOrigCaptain().contains(data.getMember()); 1534 if (useOfficerRecovery) { 1535 mult = 1f; 1536 } 1537 } 1538 1539 boolean noRecovery = false; 1540 if (battle != null && 1541 battle.getSourceFleet(data.getMember()) != null) { 1542 CampaignFleetAPI fleet = battle.getSourceFleet(data.getMember()); 1543 if (fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_NO_SHIP_RECOVERY)) { 1544 noRecovery = true; 1545 } 1546 } 1547 1548// if (data.getMember().getHullId().startsWith("cerberus")) { 1549// System.out.println("wefwefew"); 1550// } 1551 boolean normalRecovery = !noRecovery && 1552 Misc.isShipRecoverable(data.getMember(), playerFleet, own, useOfficerRecovery, 1f * mult); 1553 boolean storyRecovery = !noRecovery && !normalRecovery; 1554 1555 boolean alwaysRec = data.getMember().getVariant().hasTag(Tags.VARIANT_ALWAYS_RECOVERABLE); 1556 1557 float shipRecProb = data.getMember().getStats().getDynamic().getMod(Stats.INDIVIDUAL_SHIP_RECOVERY_MOD).computeEffective(0f); 1558 if (!own && !alwaysRec && (storyRecovery || normalRecovery) && shipRecProb < 1f) { 1559 float per = Global.getSettings().getFloat("probNonOwnNonRecoverablePerDMod"); 1560 float perAlready = Global.getSettings().getFloat("probNonOwnNonRecoverablePerAlreadyRecoverable"); 1561 float max = Global.getSettings().getFloat("probNonOwnNonRecoverableMax"); 1562 int dmods = DModManager.getNumDMods(data.getMember().getVariant()); 1563 1564 float assumedAddedDmods = 3f; 1565 assumedAddedDmods -= Global.getSector().getPlayerFleet().getStats().getDynamic().getValue(Stats.SHIP_DMOD_REDUCTION, 0) * 0.5f; 1566 assumedAddedDmods = Math.min(assumedAddedDmods, 5 - dmods); 1567 1568 float recoveredSoFar = 0f; 1569 if (storyRecovery) recoveredSoFar = storyRecoverableShips.size(); 1570 else recoveredSoFar = result.size(); 1571 1572 if (random.nextFloat() < Math.min(max, (dmods + assumedAddedDmods) * per) + recoveredSoFar * perAlready) { 1573 noRecovery = true; 1574 } 1575 } 1576 1577 1578 //if (true || Misc.isShipRecoverable(data.getMember(), playerFleet, own, useOfficerRecovery, battle.getSeed(), 1f * mult)) { 1579 if (!noRecovery && (normalRecovery || storyRecovery)) { 1580 //if (Misc.isShipRecoverable(data.getMember(), playerFleet, battle.getSeed(), 1f * mult)) { 1581 1582 if (!own || !Misc.isUnremovable(data.getMember().getCaptain())) { 1583 String aiCoreId = null; 1584 if (own && data.getMember().getCaptain() != null && 1585 data.getMember().getCaptain().isAICore()) { 1586 aiCoreId = data.getMember().getCaptain().getAICoreId(); 1587 } 1588 1589 // if it's an AI core on a player ship, then: 1590 // 1. It's integrated/unremovable, so, don't remove (we don't even end up here) 1591 // 2. Ship will be recovered and will still have it, or 1592 // 3. Ship will not be recovered, and it will get added to loot in lootWeapons() 1593 boolean keepCaptain = false; 1594 // don't do this - want to only show the AI core in recovery dialog when 1595 // it's integrated and would be lost if not recovered 1596// if (own && (data.getMember().getCaptain() == null || 1597// data.getMember().getCaptain().isAICore())) { 1598// keepCaptain = true; 1599// } 1600 if (!keepCaptain) { 1601 if (aiCoreId != null) { 1602 data.getMember().setCaptain(Global.getFactory().createPerson()); 1603 data.getMember().getCaptain().getMemoryWithoutUpdate().set( 1604 "$aiCoreIdForRecovery", aiCoreId); 1605 } else if (!own && data.getMember().getCaptain() != null && 1606 data.getMember().getCaptain().isAICore()) { 1607 aiCoreId = data.getMember().getCaptain().getAICoreId(); 1608 data.getMember().setCaptain(Global.getFactory().createPerson()); 1609 data.getMember().getCaptain().getMemoryWithoutUpdate().set( 1610 "$aiCoreIdForPossibleRecovery", aiCoreId); 1611 1612 } 1613 } 1614 } 1615 1616 ShipVariantAPI variant = data.getMember().getVariant(); 1617 variant = variant.clone(); 1618 variant.setSource(VariantSource.REFIT); 1619 1620 // maybe this was necessary? commenting this out to for simulator to be able to unlock recoverable ship variants 1621 //variant.setOriginalVariant(null); 1622 1623 //DModManager.setDHull(variant); 1624 data.getMember().setVariant(variant, false, true); 1625 1626 boolean lessDmods = false; 1627 if (!own && data.getStatus() != Status.DESTROYED && random.nextFloat() < probLessDModsOnNext) { 1628 lessDmods = true; 1629 probLessDModsOnNext *= lessDmodsOnNextMult; 1630 } 1631 1632 //Random dModRandom = new Random(1000000 * (data.getMember().getId().hashCode() + Global.getSector().getClock().getDay())); 1633 Random dModRandom = new Random(1000000 * data.getMember().getId().hashCode() + Global.getSector().getPlayerBattleSeed()); 1634 dModRandom = Misc.getRandom(dModRandom.nextLong(), 5); 1635 if (lessDmods) { 1636 DModManager.reduceNextDmodsBy = 3; 1637 } 1638 1639 float probAvoidDmods = 1640 data.getMember().getStats().getDynamic().getMod( 1641 Stats.DMOD_AVOID_PROB_MOD).computeEffective(0f); 1642 1643 float probAcquireDmods = 1644 data.getMember().getStats().getDynamic().getMod( 1645 Stats.DMOD_ACQUIRE_PROB_MOD).computeEffective(1f); 1646 1647 if (dModRandom.nextFloat() >= probAvoidDmods && dModRandom.nextFloat() < probAcquireDmods) { 1648 DModManager.addDMods(data, own, Global.getSector().getPlayerFleet(), dModRandom); 1649 if (DModManager.getNumDMods(variant) > 0) { 1650 DModManager.setDHull(variant); 1651 } 1652 } 1653 1654 float weaponProb = Global.getSettings().getFloat("salvageWeaponProb"); 1655 float wingProb = Global.getSettings().getFloat("salvageWingProb"); 1656 if (own) { 1657 weaponProb = Global.getSettings().getFloat("salvageOwnWeaponProb"); 1658 wingProb = Global.getSettings().getFloat("salvageOwnWingProb"); 1659 weaponProb = playerFleet.getStats().getDynamic().getValue(Stats.OWN_WEAPON_RECOVERY_MOD, weaponProb); 1660 wingProb = playerFleet.getStats().getDynamic().getValue(Stats.OWN_WING_RECOVERY_MOD, wingProb); 1661 } 1662 1663 boolean retain = data.getMember().getHullSpec().hasTag(Tags.TAG_RETAIN_SMODS_ON_RECOVERY) || 1664 data.getMember().getVariant().hasTag(Tags.TAG_RETAIN_SMODS_ON_RECOVERY); 1665 prepareShipForRecovery(data.getMember(), own, true, !own && !retain, weaponProb, wingProb, salvageRandom); 1666 1667 if (normalRecovery) { 1668 if (result.size() < maxRecoverablePerType) { 1669 result.add(data.getMember()); 1670 } 1671 } else if (storyRecovery) { 1672 if (storyRecoverableShips.size() < maxRecoverablePerType) { 1673 storyRecoverableShips.add(data.getMember()); 1674 } 1675 } 1676 1677// count++; 1678// if (count >= max) break; 1679 } 1680 1681 1682// else { 1683// data.getMember().getVariant().removeTag(Tags.SHIP_RECOVERABLE); 1684// } 1685 } 1686 1687 //System.out.println("Recoverable: " + result.size() + ", story: " + storyRecoverableShips.size()); 1688 1689 1690 recoverableShips.clear(); 1691 recoverableShips.addAll(result); 1692 return result; 1693 } 1694 1695 1696 1697 1698 public static void recoverShips(List<FleetMemberAPI> ships, FleetEncounterContext context, CampaignFleetAPI winningFleet, CampaignFleetAPI otherFleet) { 1699 1700 if (!Misc.isPlayerOrCombinedContainingPlayer(winningFleet)) { 1701 return; 1702 } 1703 1704 DataForEncounterSide winnerData = null; 1705 DataForEncounterSide loserData = null; 1706 1707 if (context != null) { 1708 winnerData = context.getDataFor(winningFleet); 1709 loserData = context.getDataFor(otherFleet); 1710 } 1711 1712 CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet(); 1713 1714 for (FleetMemberAPI member : ships) { 1715 //CampaignFleetAPI sourceFleet = context.getBattle().getSourceFleet(member); 1716 //repairFleetMember(member, sourceFleet == playerFleet); 1717 1718 if (member.getStatus().getNumStatuses() <= 1) { 1719 member.getStatus().repairDisabledABit(); 1720 } 1721// for (int i = 1; i < member.getStatus().getNumStatuses(); i++) { 1722// if ((float) Math.random() > 0.33f) { 1723// member.getStatus().setDetached(i, true); 1724// member.getStatus().setHullFraction(i, 0f); 1725// } 1726// } 1727 1728 float minHull = playerFleet.getStats().getDynamic().getValue(Stats.RECOVERED_HULL_MIN, 0f); 1729 float maxHull = playerFleet.getStats().getDynamic().getValue(Stats.RECOVERED_HULL_MAX, 0f); 1730 float minCR = playerFleet.getStats().getDynamic().getValue(Stats.RECOVERED_CR_MIN, 0f); 1731 float maxCR = playerFleet.getStats().getDynamic().getValue(Stats.RECOVERED_CR_MAX, 0f); 1732 1733 minHull += member.getStats().getDynamic().getValue(Stats.RECOVERED_HULL_MIN, 0f); 1734 maxHull += member.getStats().getDynamic().getValue(Stats.RECOVERED_HULL_MAX, 0f); 1735 minCR += member.getStats().getDynamic().getValue(Stats.RECOVERED_CR_MIN, 0f); 1736 maxCR += member.getStats().getDynamic().getValue(Stats.RECOVERED_CR_MAX, 0f); 1737 1738 float hull = (float) Math.random() * (maxHull - minHull) + minHull; 1739 if (hull < 0.01f) hull = 0.01f; 1740 if (hull > 1f) hull = 1f; 1741 member.getStatus().setHullFraction(hull); 1742 1743 float cr = (float) Math.random() * (maxCR - minCR) + minCR; 1744 if (cr < 0 || member.isMothballed()) cr = 0; 1745 float max = member.getRepairTracker() == null ? 1f : member.getRepairTracker().getMaxCR(); 1746 if (cr > max) cr = max; 1747 member.getRepairTracker().setCR(cr); 1748 1749 if (winnerData != null) winnerData.getInReserveDuringLastEngagement().add(member); 1750 playerFleet.getFleetData().addFleetMember(member); 1751 if (context != null) { 1752 context.getBattle().getCombinedFor(playerFleet).getFleetData().addFleetMember(member); 1753 context.origSourceForRecoveredShips.put(member, context.getBattle().getSourceFleet(member)); 1754 context.getBattle().getMemberSourceMap().put(member, playerFleet); 1755 } 1756 1757 member.setFleetCommanderForStats(null, null); 1758 1759 1760 member.setOwner(0); 1761 1762 if (!Misc.isUnremovable(member.getCaptain())) { 1763 member.setCaptain(Global.getFactory().createPerson()); 1764 member.getCaptain().setFaction(Factions.PLAYER); 1765 } 1766 1767 //member.getRepairTracker().setMothballed(true); 1768 1769 if (winnerData != null) { 1770 winnerData.changeEnemy(member, Status.REPAIRED); 1771 winnerData.changeOwn(member, Status.REPAIRED); 1772 1773 winnerData.getDestroyedInLastEngagement().remove(member); 1774 winnerData.getDisabledInLastEngagement().remove(member); 1775 } 1776 1777 if (loserData != null) { 1778 loserData.changeEnemy(member, Status.REPAIRED); 1779 loserData.changeOwn(member, Status.REPAIRED); 1780 1781 loserData.getDestroyedInLastEngagement().remove(member); 1782 loserData.getDisabledInLastEngagement().remove(member); 1783 } 1784 } 1785 1786 return; 1787 } 1788 1789 1790 public static void prepareShipForRecovery(FleetMemberAPI member, 1791 boolean retainAllHullmods, boolean retainKnownHullmods, boolean clearSMods, 1792 float weaponRetainProb, float wingRetainProb, Random salvageRandom) { 1793 ShipVariantAPI variant = member.getVariant().clone(); 1794 //variant.setOriginalVariant(null); 1795 if (retainAllHullmods) { 1796 // do nothing 1797 } else if (retainKnownHullmods) { 1798 for (String modId : new ArrayList<String>(variant.getHullMods())) { 1799 if (!Global.getSector().getPlayerFaction().knowsHullMod(modId)) { 1800 variant.removeMod(modId); 1801 } 1802 } 1803 } else { 1804 variant.clearHullMods(); 1805 variant.setNumFluxCapacitors(0); 1806 variant.setNumFluxVents(0); 1807 } 1808 1809 if (clearSMods && !variant.hasTag(Tags.VARIANT_ALWAYS_RETAIN_SMODS_ON_SALVAGE)) { 1810 for (String id : new ArrayList<String>(variant.getSMods())) { 1811 variant.removePermaMod(id); 1812 } 1813 } 1814 1815 variant.setSource(VariantSource.REFIT); 1816 member.setVariant(variant, false, false); 1817 List<String> remove = new ArrayList<String>(); 1818 1819// if (!retainHullmods) { 1820// variant.clearHullMods(); 1821// variant.setNumFluxCapacitors(0); 1822// variant.setNumFluxVents(0); 1823// } 1824 1825 Random random = new Random(); 1826 if (salvageRandom != null) random = salvageRandom; 1827 1828 if (!member.isFighterWing()) { 1829 for (String slotId : variant.getNonBuiltInWeaponSlots()) { 1830 if (random.nextFloat() > weaponRetainProb) { 1831 remove.add(slotId); 1832 } 1833 } 1834 for (String slotId : remove) { 1835 variant.clearSlot(slotId); 1836 } 1837 1838 int index = 0; 1839 for (String id : variant.getFittedWings()) { 1840 if (random.nextFloat() > wingRetainProb) { 1841 variant.setWingId(index, null); // won't clear out built-in wings 1842 } 1843 index++; 1844 } 1845 } 1846 1847 for (String slotId : variant.getStationModules().keySet()) { 1848 prepareModuleForRecovery(member, slotId, 1849 retainAllHullmods, retainKnownHullmods, clearSMods, weaponRetainProb, wingRetainProb, salvageRandom); 1850 } 1851 1852 1853 for (int i = 1; i < member.getStatus().getNumStatuses(); i++) { 1854 if (random.nextFloat() > 0.5f) { 1855 member.getStatus().setDetached(i, false); 1856 member.getStatus().setHullFraction(i, 0.1f + 0.1f * random.nextFloat()); 1857 } 1858 } 1859 1860 // get rid of any short-term modifiers, such as "0 repair rate during emergency burn" 1861 for (int i = 0; i < 10; i++) { 1862 member.getBuffManager().advance(1f); 1863 } 1864// for (Buff buff : new ArrayList<Buff>(member.getBuffManager().getBuffs())) { 1865// member.getBuffManager().removeBuff(buff.getId()); 1866// } 1867 1868 variant.addTag(Tags.SHIP_RECOVERABLE); 1869 } 1870 1871 public static void prepareModuleForRecovery(FleetMemberAPI member, String moduleSlotId, 1872 boolean retainAllHullmods, boolean retainKnownHullmods, boolean clearSMods, 1873 float weaponRetainProb, float wingRetainProb, Random salvageRandom) { 1874 1875 ShipVariantAPI moduleCurrent = member.getVariant().getModuleVariant(moduleSlotId); 1876 if (moduleCurrent == null) return; 1877 1878 moduleCurrent = moduleCurrent.clone(); 1879 moduleCurrent.setOriginalVariant(null); 1880 if (retainAllHullmods) { 1881 // do nothing 1882 } else if (retainKnownHullmods) { 1883 for (String modId : new ArrayList<String>(moduleCurrent.getHullMods())) { 1884 if (!Global.getSector().getPlayerFaction().knowsHullMod(modId)) { 1885 moduleCurrent.removeMod(modId); 1886 } 1887 } 1888 } else { 1889 moduleCurrent.clearHullMods(); 1890 moduleCurrent.setNumFluxCapacitors(0); 1891 moduleCurrent.setNumFluxVents(0); 1892 } 1893 1894 if (clearSMods && !moduleCurrent.hasTag(Tags.VARIANT_ALWAYS_RETAIN_SMODS_ON_SALVAGE)) { 1895 for (String id : new ArrayList<String>(moduleCurrent.getSMods())) { 1896 moduleCurrent.removePermaMod(id); 1897 } 1898 } 1899 1900 moduleCurrent.setSource(VariantSource.REFIT); 1901 member.getVariant().setModuleVariant(moduleSlotId, moduleCurrent); 1902 1903 List<String> remove = new ArrayList<String>(); 1904 1905 Random random = Misc.random; 1906 if (salvageRandom != null) random = salvageRandom; 1907 1908 for (String slotId : moduleCurrent.getNonBuiltInWeaponSlots()) { 1909 if (random.nextFloat() > weaponRetainProb) { 1910 remove.add(slotId); 1911 } 1912 } 1913 for (String slotId : remove) { 1914 moduleCurrent.clearSlot(slotId); 1915 } 1916 1917 int index = 0; 1918 for (String id : moduleCurrent.getFittedWings()) { 1919 if (random.nextFloat() > wingRetainProb) { 1920 moduleCurrent.setWingId(index, null); // won't clear out built-in wings 1921 } 1922 index++; 1923 } 1924 } 1925 1926 1927 public void gainXP() { 1928 if (sideData.size() != 2) return; 1929 if (!battle.isPlayerInvolved()) return; 1930 1931 DataForEncounterSide sideOne = sideData.get(0); 1932 DataForEncounterSide sideTwo = sideData.get(1); 1933 if (battle.isPlayerSide(battle.getSideFor(sideOne.getFleet()))) { 1934 gainXP(sideOne, sideTwo); 1935 } else if (battle.isPlayerSide(battle.getSideFor(sideTwo.getFleet()))) { 1936 gainXP(sideTwo, sideOne); 1937 } 1938 } 1939 1940 protected void gainOfficerXP(DataForEncounterSide data, float xp) { 1941 float max = data.getMaxTimeDeployed(); 1942 if (max < 1) max = 1; 1943 float num = data.getOfficerData().size(); 1944 if (num < 1) num = 1; 1945 for (PersonAPI person : data.getOfficerData().keySet()) { 1946 OfficerEngagementData oed = data.getOfficerData().get(person); 1947 if (oed.sourceFleet == null || !oed.sourceFleet.isPlayerFleet()) continue; 1948 1949 OfficerDataAPI od = oed.sourceFleet.getFleetData().getOfficerData(person); 1950 if (od == null) continue; // shouldn't happen, as this is checked earlier before it goes into the map 1951 1952 float f = oed.timeDeployed / max; 1953 if (f < 0) f = 0; 1954 if (f > 1) f = 1; 1955 1956 od.addXP((long)(f * xp / num), textPanelForXPGain); 1957 } 1958 } 1959 1960 1961 public float getPlayerFPHullDamageToEnemies() { 1962 return playerFPHullDamageToEnemies; 1963 } 1964 1965 public void setPlayerFPHullDamageToEnemies(float playerFPHullDamageToEnemies) { 1966 this.playerFPHullDamageToEnemies = playerFPHullDamageToEnemies; 1967 } 1968 1969 public float getAllyFPHullDamageToEnemies() { 1970 return allyFPHullDamageToEnemies; 1971 } 1972 1973 public void setAllyFPHullDamageToEnemies(float allyFPHullDamageToEnemies) { 1974 this.allyFPHullDamageToEnemies = allyFPHullDamageToEnemies; 1975 } 1976 1977 protected float playerFPHullDamageToEnemies = 0f; 1978 protected float allyFPHullDamageToEnemies = 0f; 1979 protected float playerFPHullDamageToAllies = 0f; 1980 protected Map<FactionAPI, Float> playerFPHullDamageToAlliesByFaction = new HashMap<FactionAPI, Float>(); 1981 protected void computeFPHullDamage() { 1982 if (runningDamageTotal == null) return; 1983 1984// playerFPHullDamageToEnemies = 0f; 1985// allyFPHullDamageToEnemies = 0f; 1986// playerFPHullDamageToAllies = 0f; 1987 1988 for (FleetMemberAPI member : runningDamageTotal.getDealt().keySet()) { 1989 if (member.getOwner() != 0) continue; 1990 1991 1992 DealtByFleetMember dealt = runningDamageTotal.getDealt().get(member); 1993 for (FleetMemberAPI target : dealt.getDamage().keySet()) { 1994 if (battle.getSourceFleet(target) == null) continue; 1995 1996 DamageToFleetMember damage = dealt.getDamageTo(target); 1997 float maxHull = target.getStats().getHullBonus().computeEffective(target.getHullSpec().getHitpoints()); 1998 if (maxHull <= 0) continue; 1999 if (target.isFighterWing()) { 2000 maxHull *= target.getNumFightersInWing(); 2001 } 2002 2003 float currDam = Math.min(damage.hullDamage, maxHull) / maxHull * (float) target.getFleetPointCost(); 2004 if (target.getOwner() == 1) { 2005 CampaignFleetAPI fleet = battle != null ? battle.getSourceFleet(member) : null; 2006 boolean ally = member.isAlly(); 2007 if (ally && fleet != null && 2008 fleet.getFaction() != null && fleet.getFaction().isPlayerFaction()) { 2009 ally = false; 2010 } 2011 if (ally) { 2012 allyFPHullDamageToEnemies += currDam; 2013 } else { 2014 playerFPHullDamageToEnemies += currDam; 2015 } 2016 } else if (!member.isAlly() && target.isAlly() && !target.isFighterWing()) { 2017 playerFPHullDamageToAllies += currDam; 2018 CampaignFleetAPI fleet = battle != null ? battle.getSourceFleet(target) : null; 2019 if (fleet != null) { 2020 float curr = currDam; 2021 if (playerFPHullDamageToAlliesByFaction.containsKey(fleet.getFaction())) { 2022 curr += playerFPHullDamageToAlliesByFaction.get(fleet.getFaction()); 2023 } 2024 playerFPHullDamageToAlliesByFaction.put(fleet.getFaction(), curr); 2025 } 2026 } 2027 } 2028 } 2029 2030// if (playerFPHullDamageToEnemies <= 0) { 2031// System.out.println("HERE 12523423"); 2032// } 2033// allyFPHullDamageToEnemies += playerFPHullDamageToEnemies; 2034// playerFPHullDamageToEnemies = 0f; 2035 runningDamageTotal = null; 2036 } 2037 2038 2039 public float computePlayerContribFraction() { 2040 float total = playerFPHullDamageToEnemies + allyFPHullDamageToEnemies; 2041 if (total <= 0) { 2042 if (battle == null) return 1f; 2043 if (battle.isPlayerInvolved() && (battle.getPlayerSideSnapshot().size() <= 1 || battle.getPlayerSide().size() <= 1)) return 1f; 2044 return 0f; 2045 } 2046 2047 boolean hasAllies = false; 2048 boolean startedWithAllies = false; 2049 if (battle != null) { 2050 hasAllies = battle.getPlayerSide().size() <= 1; 2051 startedWithAllies = battle.getPlayerSideSnapshot().size() > 1; 2052 } 2053 if (startedWithAllies) { // && hasAllies) { 2054 //return Math.min(0.9f, playerFPHullDamageToEnemies / total); 2055 return Math.min(1f, playerFPHullDamageToEnemies / total); 2056 } else { 2057 return 1f; 2058 } 2059 } 2060 2061 protected float xpGained = 0; 2062 protected void gainXP(DataForEncounterSide side, DataForEncounterSide otherSide) { 2063 float bonusXP = 0f; 2064 float points = 0f; 2065 for (FleetMemberData data : side.getOwnCasualties()) { 2066 if (data.getStatus() == Status.DISABLED || 2067 data.getStatus() == Status.DESTROYED) { 2068 float [] bonus = Misc.getBonusXPForScuttling(data.getMember()); 2069 points += bonus[0]; 2070 bonusXP += bonus[1] * bonus[0]; 2071 } 2072 } 2073 if (bonusXP > 0 && points > 0) { 2074 points = 1; 2075 Global.getSector().getPlayerStats().setOnlyAddBonusXPDoNotSpendStoryPoints(true); 2076 Global.getSector().getPlayerStats().setBonusXPGainReason("from losing s-modded ships"); 2077 Global.getSector().getPlayerStats().spendStoryPoints((int)Math.round(points), true, textPanelForXPGain, false, bonusXP, null); 2078 Global.getSector().getPlayerStats().setOnlyAddBonusXPDoNotSpendStoryPoints(false); 2079 Global.getSector().getPlayerStats().setBonusXPGainReason(null); 2080 } 2081 2082 //CampaignFleetAPI fleet = side.getFleet(); 2083 CampaignFleetAPI fleet = Global.getSector().getPlayerFleet(); 2084 float fpTotal = 0; 2085 for (FleetMemberData data : otherSide.getOwnCasualties()) { 2086 float fp = data.getMember().getFleetPointCost(); 2087 2088// String prefix = "xp_mult_"; 2089// for (String tag : data.getMember().getHullSpec().getTags()) { 2090// if (tag.startsWith(prefix)) { 2091// tag = tag.replaceFirst(prefix, ""); 2092// fp *= Float.parseFloat(tag); 2093// break; 2094// } 2095// } 2096 2097 fp *= 1f + data.getMember().getCaptain().getStats().getLevel() / 5f; 2098 fpTotal += fp; 2099 } 2100 2101 float xp = (float) fpTotal * 250; 2102 xp *= 2f; 2103 2104 float difficultyMult = Math.max(1f, difficulty); 2105 xp *= difficultyMult; 2106 2107 xp *= computePlayerContribFraction(); 2108 2109 xp *= Global.getSettings().getFloat("xpGainMult"); 2110 2111 2112 if (xp > 0) { 2113 //fleet.getCargo().gainCrewXP(xp); 2114 2115 //if (side.getFleet().isPlayerFleet()) { 2116 //} 2117 // only gain XP if it's the player fleet anyway, no need to check this here 2118 gainOfficerXP(side, xp); 2119 2120 fleet.getCommander().getStats().addXP((long) xp, textPanelForXPGain); 2121 fleet.getCommander().getStats().levelUpIfNeeded(textPanelForXPGain); 2122 2123 xpGained = xp; 2124 } 2125 } 2126 2127 public void addPotentialOfficer() { 2128 if (!isEngagedInHostilities()) return; 2129 if (xpGained <= 0) return; 2130 if (sideData.size() != 2) return; 2131 if (!battle.isPlayerInvolved()) return; 2132 2133 DataForEncounterSide sideOne = sideData.get(0); 2134 DataForEncounterSide sideTwo = sideData.get(1); 2135 2136 DataForEncounterSide player = sideOne; 2137 DataForEncounterSide enemy = sideTwo; 2138 if (battle.isPlayerSide(battle.getSideFor(sideTwo.getFleet()))) { 2139 player = sideTwo; 2140 enemy = sideOne; 2141 } 2142 2143 float fpDestroyed = 0; 2144 for (FleetMemberData data : enemy.getOwnCasualties()) { 2145 float fp = data.getMember().getFleetPointCost(); 2146 fp *= 1f + data.getMember().getCaptain().getStats().getLevel() / 5f; 2147 fpDestroyed += fp; 2148 } 2149 fpDestroyed *= computePlayerContribFraction(); 2150 for (FleetMemberData data : player.getOwnCasualties()) { 2151 if (data.getMember().isAlly()) continue; 2152 float fp = data.getMember().getFleetPointCost(); 2153 fp *= 1f + data.getMember().getCaptain().getStats().getLevel() / 5f; 2154 fpDestroyed += fp; 2155 } 2156 2157 float fpInFleet = 0f; 2158 for (FleetMemberAPI member : Global.getSector().getPlayerFleet().getFleetData().getMembersListCopy()) { 2159 float fp = member.getFleetPointCost(); 2160 fp *= 1f + member.getCaptain().getStats().getLevel() / 5f; 2161 fpInFleet += fp; 2162 } 2163 2164 2165 float maxProb = Global.getSettings().getFloat("maxOfficerPromoteProb"); 2166 float probMult = Global.getSettings().getFloat("officerPromoteProbMult"); 2167 int max = Misc.getMaxOfficers(Global.getSector().getPlayerFleet()); 2168 int curr = Misc.getNumNonMercOfficers(Global.getSector().getPlayerFleet()); 2169 2170 float prob = fpDestroyed / (Math.max(1f, fpInFleet)); 2171 //prob /= 5f; 2172 prob *= probMult; 2173 2174 if (curr >= max) prob *= 0.5f; 2175 if (prob > maxProb) prob = maxProb; 2176 2177 Random random = Misc.random; 2178 if (salvageRandom != null) { 2179 random = salvageRandom; 2180 } 2181 2182 if (TutorialMissionIntel.isTutorialInProgress()) { 2183 prob = 0f; 2184 } 2185 2186 if (random.nextFloat() < prob) { 2187 PromoteOfficerIntel intel = new PromoteOfficerIntel(textPanelForXPGain); 2188 Global.getSector().getIntelManager().addIntel(intel, false, textPanelForXPGain); 2189 } 2190 } 2191 2192 2193 protected CargoAPI loot = Global.getFactory().createCargo(false); 2194 protected int creditsLooted = 0; 2195 public void generateLoot(List<FleetMemberAPI> recoveredShips, boolean withCredits) { 2196 creditsLooted = 0; 2197 loot.clear(); 2198 //if (getWinner() == Global.getSector().getPlayerFleet()) { 2199 if (battle.isPlayerSide(battle.getSideFor(getWinner()))) { 2200 generatePlayerLoot(recoveredShips, withCredits); 2201 } else { //if (getLoser() == Global.getSector().getPlayerFleet()) { 2202 handleCargoLooting(recoveredShips, true); 2203 } 2204 loot.sort(); 2205 } 2206 2207 private Random salvageRandom = null; 2208 public Random getSalvageRandom() { 2209 return salvageRandom; 2210 } 2211 public void setSalvageRandom(Random salvageRandom) { 2212 this.salvageRandom = salvageRandom; 2213 } 2214 2215 protected void generatePlayerLoot(List<FleetMemberAPI> recoveredShips, boolean withCredits) { 2216 //computeFPHullDamage(); 2217 2218 2219 DataForEncounterSide winner = getWinnerData(); 2220 DataForEncounterSide loser = getLoserData(); 2221 2222 if (winner == null || loser == null) return; 2223 2224 float adjustedFPSalvage = 0; 2225 float playerContribMult = computePlayerContribFraction(); 2226 2227 Random origSalvageRandom = salvageRandom; 2228 long extraSeed = 1340234324325L; 2229 if (origSalvageRandom != null) extraSeed = origSalvageRandom.nextLong(); 2230 2231 for (FleetMemberData data : winner.getEnemyCasualties()) { 2232 if (data.getStatus() == Status.REPAIRED) { 2233 continue; 2234 } 2235 2236 if (data.getMember() != null && data.getMember().getHullSpec().hasTag(Tags.NO_BATTLE_SALVAGE)) { 2237 continue; 2238 } 2239 2240 if (origSalvageRandom != null) { 2241 String sig = data.getMember().getHullId(); 2242 if (data.getMember().getVariant() != null) { 2243 for (WeaponGroupSpec spec : data.getMember().getVariant().getWeaponGroups()) { 2244 for (String slotId : spec.getSlots()) { 2245 String w = data.getMember().getVariant().getWeaponId(slotId); 2246 if (w != null) sig += w; 2247 } 2248 } 2249 } 2250 if (loser != null && loser.getFleet() != null && loser.getFleet().getFleetData() != null) { 2251 List<FleetMemberAPI> members = loser.getFleet().getFleetData().getMembersListCopy(); 2252 if (members != null) { 2253 int index = members.indexOf(data.getMember()); 2254 if (index >= 0) { 2255 sig += "" + index; 2256 } 2257 } 2258 } 2259 long seed = sig.hashCode() * 143234234234L * extraSeed; 2260 salvageRandom = new Random(seed); 2261 //System.out.println("Seed for " + data.getMember() + ": " + seed); 2262 } 2263 2264 float mult = getSalvageMult(data.getStatus()) * playerContribMult; 2265 lootWeapons(data.getMember(), data.getMember().getVariant(), false, mult, false); 2266 lootHullMods(data.getMember(), data.getMember().getVariant(), mult); 2267 lootWings(data.getMember(), data.getMember().getVariant(), false, mult); 2268 adjustedFPSalvage += (float) data.getMember().getFleetPointCost() * mult; 2269 } 2270 2271 for (FleetMemberData data : winner.getOwnCasualties()) { 2272 if (data.getMember().isAlly()) continue; 2273 2274 if (data.getStatus() == Status.CAPTURED || data.getStatus() == Status.REPAIRED) { 2275 continue; 2276 } 2277 2278 if (data.getMember() != null && data.getMember().getHullSpec().hasTag(Tags.NO_BATTLE_SALVAGE)) { 2279 continue; 2280 } 2281 2282 // only care about salvageRandom for enemy casualties, not player 2283// if (origSalvageRandom != null) { 2284// salvageRandom = new Random(data.getMember().getId().hashCode() * 143234234234L * extraSeed); 2285// } 2286 2287 float mult = getSalvageMult(data.getStatus()); 2288 lootWeapons(data.getMember(), data.getMember().getVariant(), true, mult, false); 2289 lootWings(data.getMember(), data.getMember().getVariant(), true, mult); 2290 2291 adjustedFPSalvage += (float) data.getMember().getFleetPointCost() * mult; 2292 } 2293 2294 if (recoveredShips != null) { 2295 for (FleetMemberAPI member : recoveredShips) { 2296 float mult = getSalvageMult(Status.CAPTURED); 2297 adjustedFPSalvage += (float) member.getFleetPointCost() * mult; 2298 } 2299 } 2300 2301 salvageRandom = origSalvageRandom; 2302 2303 // don't want salvageRandom to be influenced by the number of losses on either side 2304 Random resetSalvageRandomTo = null; 2305 Random forRandomDrops = null; 2306 Random forCargoDrops = null; 2307 2308 Random random = Misc.random; 2309 if (salvageRandom != null) { 2310 random = salvageRandom; 2311 resetSalvageRandomTo = Misc.getRandom(random.nextLong(), 11); 2312 forRandomDrops = Misc.getRandom(random.nextLong(), 17); 2313 forCargoDrops = Misc.getRandom(random.nextLong(), 31); 2314 } else { 2315 if (getBattle() != null) { 2316 MemoryAPI memory = getBattle().getNonPlayerCombined().getMemoryWithoutUpdate(); 2317 if (memory.contains(MemFlags.SALVAGE_SEED)) { 2318 random = new Random(memory.getLong(MemFlags.SALVAGE_SEED)); 2319 } 2320 } 2321 } 2322 2323 float minCreditsFraction = Global.getSettings().getFloat("salvageFractionCreditsMin"); 2324 float maxCreditsFraction = Global.getSettings().getFloat("salvageFractionCreditsMax"); 2325 2326 float creditsFraction = minCreditsFraction + (maxCreditsFraction - minCreditsFraction) * random.nextFloat(); 2327 creditsFraction *= playerContribMult; 2328 2329 float maxSalvageValue = adjustedFPSalvage * Global.getSettings().getFloat("salvageValuePerFP"); 2330 if (Misc.isEasy()) { 2331 maxSalvageValue *= Global.getSettings().getFloat("easySalvageMult"); 2332 } 2333 2334 CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet(); 2335 float valueMultFleet = playerFleet.getStats().getDynamic().getValue(Stats.BATTLE_SALVAGE_MULT_FLEET); 2336 float valueModShips = getSalvageValueModPlayerShips(); 2337 2338 2339 maxSalvageValue *= valueMultFleet + valueModShips; 2340 2341 creditsLooted = Math.round(maxSalvageValue * creditsFraction); 2342 if (!withCredits) creditsLooted = 0; 2343 maxSalvageValue -= creditsLooted; 2344 2345 float salvageValue = 0f; 2346 WeightedRandomPicker<String> lootPicker = new WeightedRandomPicker<String>(random); 2347 lootPicker.add(Commodities.METALS, 20); 2348 lootPicker.add(Commodities.SUPPLIES, 10); 2349 lootPicker.add(Commodities.FUEL, 10); 2350 lootPicker.add(Commodities.HEAVY_MACHINERY, 1); 2351 2352 while (salvageValue < maxSalvageValue) { 2353 String commodityId = lootPicker.pick(); 2354 if (commodityId == null) break; 2355 2356 CommoditySpecAPI spec = Global.getSector().getEconomy().getCommoditySpec(commodityId); 2357 float qty = 1f; 2358 salvageValue += spec.getBasePrice() * qty; 2359 loot.addCommodity(commodityId, qty); 2360 } 2361 2362 2363 float fuelMult = playerFleet.getStats().getDynamic().getValue(Stats.FUEL_SALVAGE_VALUE_MULT_FLEET); 2364 float fuel = loot.getFuel(); 2365 if (fuelMult > 1f) { 2366 loot.addFuel((int) Math.round(fuel * (fuelMult - 1f))); 2367 } 2368 2369 if (getBattle().getSnapshotSideFor(loser.getFleet()) == null) return; 2370 2371 List<DropData> dropRandom = new ArrayList<DropData>(); 2372 List<DropData> dropValue = new ArrayList<DropData>(); 2373 //for (CampaignFleetAPI other : getBattle().getSideFor(loser.getFleet())) { 2374 for (CampaignFleetAPI other : getBattle().getSnapshotSideFor(loser.getFleet())) { 2375 dropRandom.addAll(other.getDropRandom()); 2376 dropValue.addAll(other.getDropValue()); 2377 other.getDropRandom().clear(); 2378 other.getDropValue().clear(); 2379 2380 CargoAPI extra = BaseSalvageSpecial.getCombinedExtraSalvage(other); 2381 loot.addAll(extra); 2382 2383 BaseSalvageSpecial.clearExtraSalvage(other); 2384 if (!extra.isEmpty()) { 2385 ListenerUtil.reportExtraSalvageShown(other); 2386 } 2387 } 2388 2389 if (forRandomDrops != null) { 2390 random = forRandomDrops; 2391 } 2392 CargoAPI extra = SalvageEntity.generateSalvage(random, valueMultFleet + valueModShips, 1f, fuelMult, dropValue, dropRandom); 2393 for (CargoStackAPI stack : extra.getStacksCopy()) { 2394 loot.addFromStack(stack); 2395 } 2396 2397 if (forCargoDrops != null) { 2398 salvageRandom = forCargoDrops; 2399 } 2400 handleCargoLooting(recoveredShips, false); 2401 2402 if (resetSalvageRandomTo != null) { 2403 salvageRandom = resetSalvageRandomTo; 2404 } 2405 } 2406 2407 public float getSalvageValueModPlayerShips() { 2408 return RepairGantry.getAdjustedGantryModifierForPostCombatSalvage(Global.getSector().getPlayerFleet()); 2409// float valueModShips = 0; 2410// for (FleetMemberAPI member : Global.getSector().getPlayerFleet().getFleetData().getMembersListCopy()) { 2411// if (member.isMothballed()) continue; 2412// float maxCurr = member.getStats().getDynamic().getValue(Stats.BATTLE_SALVAGE_VALUE_MULT_MOD, 0f); 2413// OfficerEngagementData data = getWinnerData().getFleetMemberDeploymentData().get(member); 2414// if (data == null) continue; 2415// float memberDeployed = data.timeDeployed; 2416// float maxDeployed = getWinnerData().getMaxTimeDeployed(); 2417// if (maxDeployed <= 0) continue; 2418// maxCurr *= Math.min(1f, memberDeployed / maxDeployed); 2419// 2420// valueModShips += maxCurr; 2421// } 2422// return valueModShips; 2423 } 2424 2425 protected static class LootableCargoStack { 2426 public CargoAPI source; 2427 public CargoStackAPI stack; 2428 public LootableCargoStack(CargoAPI source, CargoStackAPI stack) { 2429 this.source = source; 2430 this.stack = stack; 2431 } 2432 } 2433 2434 protected static class LossFraction { 2435 public float maxCargo; 2436 public float maxFuel; 2437 public float lostCargo; 2438 public float lostFuel; 2439 } 2440 2441 2442 protected void handleCargoLooting(List<FleetMemberAPI> recoveredShips, boolean takingFromPlayer) { 2443 DataForEncounterSide winner = getWinnerData(); 2444 DataForEncounterSide loser = getLoserData(); 2445 2446 if (winner == null || loser == null) return; 2447 2448 loser.getFleet().getFleetData().updateCargoCapacities(); 2449 CargoAPI loserCargo = (CargoAPI) loser.getFleet().getCargo(); 2450 float maxCargo = loserCargo.getMaxCapacity(); 2451 float maxFuel = loserCargo.getMaxFuel(); 2452 2453 Random random = Misc.random; 2454 if (salvageRandom != null) random = salvageRandom; 2455 2456// boolean playerLost = battle.isPlayerSide(battle.getSideFor(loser.getFleet())); 2457 2458 Map<CargoAPI, LossFraction> fractions = new HashMap<CargoAPI, LossFraction>(); 2459 2460 float lostCargo = 0f; 2461 float lostFuel = 0f; 2462 2463 float totalLoss = 0f; 2464 for (FleetMemberData data : winner.getEnemyCasualties()) { 2465// if (data.getStatus() == Status.REPAIRED) { 2466// continue; 2467// } 2468// if (playerLost && data.getMember().isAlly()) { 2469// continue; 2470// } 2471 2472 CampaignFleetAPI source = battle.getSourceFleet(data.getMember()); 2473 CampaignFleetAPI orig = origSourceForRecoveredShips.get(data.getMember()); 2474 if (orig != null) source = orig; 2475 2476 if (source != null) { 2477 CargoAPI c = source.getCargo(); 2478 LossFraction loss = fractions.get(c); 2479 if (loss == null) { 2480 loss = new LossFraction(); 2481 loss.maxCargo = c.getMaxCapacity(); 2482 loss.maxFuel = c.getMaxFuel(); 2483 fractions.put(c, loss); 2484 } 2485 2486 loss.lostCargo += data.getMember().getCargoCapacity(); 2487 loss.lostFuel += data.getMember().getFuelCapacity(); 2488 2489 loss.maxCargo += data.getMember().getCargoCapacity(); 2490 loss.maxFuel += data.getMember().getFuelCapacity(); 2491 2492 totalLoss += loss.maxCargo + loss.maxFuel; 2493 } else { 2494 lostCargo += data.getMember().getCargoCapacity(); 2495 lostFuel += data.getMember().getFuelCapacity(); 2496 2497 maxCargo += data.getMember().getCargoCapacity(); 2498 maxFuel += data.getMember().getFuelCapacity(); 2499 2500 totalLoss += maxCargo + maxFuel; 2501 } 2502 2503 } 2504 2505 //if (lostCargo <= 0 && lostFuel <= 0) { 2506 if (totalLoss <= 0) { 2507 return; 2508 } 2509 2510 if (maxCargo < 1) maxCargo = 1; 2511 if (maxFuel < 1) maxFuel = 1; 2512 2513 float recoveryFraction = Global.getSettings().getFloat("salvageCargoFraction"); 2514 2515 if (battle.isPlayerSide(battle.getSideFor(winner.getFleet()))) { 2516 float playerContribMult = computePlayerContribFraction(); 2517 recoveryFraction *= playerContribMult; 2518 } 2519 2520 float cargoFractionLost = lostCargo / maxCargo; 2521 float fuelFractionLost = lostFuel / maxFuel; 2522 if (lostCargo > maxCargo) cargoFractionLost = 1f; 2523 if (lostFuel > maxFuel) fuelFractionLost = 1f; 2524 2525 2526 List<CampaignFleetAPI> losers = battle.getSnapshotSideFor(loser.getFleet()); 2527 if (losers == null) return; 2528 2529 List<LootableCargoStack> stacks = new ArrayList<LootableCargoStack>(); 2530 for (CampaignFleetAPI curr : losers) { 2531 for (CargoStackAPI stack : curr.getCargo().getStacksCopy()) { 2532 stacks.add(new LootableCargoStack(curr.getCargo(), stack)); 2533 } 2534 } 2535 2536 for (LootableCargoStack stack : stacks) { 2537 if (stack.stack.isNull()) continue; 2538 if (stack.stack.isPersonnelStack()) continue; 2539 if (stack.stack.getSize() < 1) continue; 2540 2541 float actualCargoFractionLost = cargoFractionLost; 2542 float actualFuelFractionLost = fuelFractionLost; 2543 LossFraction loss = fractions.get(stack.source); 2544 if (loss != null) { 2545 actualCargoFractionLost = loss.lostCargo / loss.maxCargo; 2546 actualFuelFractionLost = loss.lostFuel / loss.maxFuel; 2547 if (loss.lostCargo > loss.maxCargo) actualCargoFractionLost = 1f; 2548 if (loss.lostFuel > loss.maxFuel) actualFuelFractionLost = 1f; 2549 } 2550 2551 2552 if (takingFromPlayer) { 2553 if (stack.stack.isSpecialStack()) continue; 2554 if (stack.stack.isCommodityStack()) { 2555 CommoditySpecAPI spec = stack.stack.getResourceIfResource(); 2556 if (spec != null && spec.hasTag(Commodities.TAG_NO_LOSS_FROM_COMBAT)){ 2557 continue; 2558 } 2559 } 2560 } 2561 2562 float numLost = 0; 2563 float numTaken = 0; 2564 if (stack.stack.isFuelStack()) { 2565 numLost = actualFuelFractionLost * stack.stack.getSize(); 2566 numTaken = Math.round(numLost * (0.5f + random.nextFloat() * 0.5f)); 2567 } else { 2568 numLost = actualCargoFractionLost * stack.stack.getSize(); 2569 numTaken = Math.round(numLost * (0.5f + random.nextFloat() * 0.5f)); 2570 } 2571 2572 if (numLost < 1) { 2573 if (random.nextFloat() < numLost) { 2574 numLost = 1; 2575 } else { 2576 numLost = 0; 2577 numTaken = 0; 2578 } 2579 } 2580 2581 if (numLost <= 0) continue; 2582 2583 stack.stack.add(-numLost); 2584 if (numTaken * recoveryFraction >= 1) { 2585 loot.addItems(stack.stack.getType(), stack.stack.getData(), numTaken * recoveryFraction); 2586 } 2587 } 2588 2589 for (CampaignFleetAPI fleet : battle.getSideFor(loser.getFleet())) { 2590 if (fleet.isPlayerFleet()) { 2591 fleet.getCargo().sort(); 2592 break; 2593 } 2594 } 2595 } 2596 2597 2598 public CargoAPI getLoot() { 2599 return loot; 2600 } 2601 2602 protected void lootHullMods(FleetMemberAPI member, ShipVariantAPI variant, float mult) { 2603 if (variant == null) return; 2604 if (member.isFighterWing()) return; 2605 Random random = Misc.random; 2606 if (salvageRandom != null) random = salvageRandom; 2607 2608 float p = Global.getSettings().getFloat("salvageHullmodProb"); 2609 float pItem = Global.getSettings().getFloat("salvageHullmodRequiredItemProb"); 2610 2611 for (String id : variant.getHullMods()) { 2612 if (!variant.getHullSpec().isBuiltInMod(id)) { 2613 if (random.nextFloat() < pItem && random.nextFloat() < mult) { 2614 HullModSpecAPI spec = Global.getSettings().getHullModSpec(id); 2615 CargoStackAPI item = spec.getEffect().getRequiredItem(); 2616 if (item != null) { 2617 boolean addToLoot = true; 2618 if (item.getSpecialItemSpecIfSpecial() != null && item.getSpecialItemSpecIfSpecial().hasTag(Tags.NO_DROP)) { 2619 addToLoot = false; 2620 } else if (item.getResourceIfResource() != null && item.getResourceIfResource().hasTag(Tags.NO_DROP)) { 2621 addToLoot = false; 2622 } else if (item.getFighterWingSpecIfWing() != null && item.getFighterWingSpecIfWing().hasTag(Tags.NO_DROP)) { 2623 addToLoot = false; 2624 } else if (item.getWeaponSpecIfWeapon() != null && item.getWeaponSpecIfWeapon().hasTag(Tags.NO_DROP)) { 2625 addToLoot = false; 2626 } 2627 if (addToLoot) { 2628 loot.addItems(item.getType(), item.getData(), 1); 2629 } 2630 } 2631 } 2632 } 2633 2634 //if (random.nextFloat() > mult) continue; 2635 if (random.nextFloat() < p && random.nextFloat() < mult) { 2636 HullModSpecAPI spec = Global.getSettings().getHullModSpec(id); 2637 boolean known = Global.getSector().getPlayerFaction().knowsHullMod(id); 2638 if (DebugFlags.ALLOW_KNOWN_HULLMOD_DROPS) known = false; 2639 if (known || spec.isHidden() || spec.isHiddenEverywhere()) continue; 2640 //if (spec.isAlwaysUnlocked()) continue; 2641 if (spec.hasTag(Tags.HULLMOD_NO_DROP)) continue; 2642 2643 loot.addHullmods(id, 1); 2644 } 2645 } 2646 2647 for (String slotId : variant.getModuleSlots()) { 2648 WeaponSlotAPI slot = variant.getSlot(slotId); 2649 if (slot.isStationModule()) { 2650 ShipVariantAPI module = variant.getModuleVariant(slotId); 2651 if (module == null) continue; 2652 lootHullMods(member, module, mult); 2653 } 2654 } 2655 } 2656 2657 protected void lootWings(FleetMemberAPI member, ShipVariantAPI variant, boolean own, float mult) { 2658 if (variant == null) return; 2659 if (member.isFighterWing()) return; 2660 Random random = Misc.random; 2661 if (salvageRandom != null) random = salvageRandom; 2662 2663 float p = Global.getSettings().getFloat("salvageWingProb"); 2664 if (own) { 2665 p = Global.getSettings().getFloat("salvageOwnWingProb"); 2666 p = Global.getSector().getPlayerFleet().getStats().getDynamic().getValue(Stats.OWN_WING_RECOVERY_MOD, p); 2667 } else { 2668 p = Global.getSector().getPlayerFleet().getStats().getDynamic().getValue(Stats.ENEMY_WING_RECOVERY_MOD, p); 2669 } 2670 2671 boolean alreadyStripped = recoverableShips.contains(member); 2672 2673 for (String id : variant.getNonBuiltInWings()) { 2674 if (!alreadyStripped) { 2675 if (random.nextFloat() > mult) continue; 2676 if (random.nextFloat() > p) continue; 2677 } 2678 2679 FighterWingSpecAPI spec = Global.getSettings().getFighterWingSpec(id); 2680 if (spec.hasTag(Tags.WING_NO_DROP)) continue; 2681 loot.addItems(CargoItemType.FIGHTER_CHIP, id, 1); 2682 } 2683 2684 for (String slotId : variant.getModuleSlots()) { 2685 WeaponSlotAPI slot = variant.getSlot(slotId); 2686 if (slot.isStationModule()) { 2687 ShipVariantAPI module = variant.getModuleVariant(slotId); 2688 if (module == null) continue; 2689 lootWings(member, module, own, mult); 2690 } 2691 } 2692 } 2693 2694 protected void lootWeapons(FleetMemberAPI member, ShipVariantAPI variant, boolean own, float mult, boolean lootingModule) { 2695 if (variant == null) return; 2696 if (member.isFighterWing()) return; 2697 2698// if (own) { 2699// System.out.println("238034wefwef"); 2700// } 2701 //isUnremovable( 2702 if (own && !lootingModule && member.getCaptain() != null && 2703 member.getCaptain().getMemoryWithoutUpdate().contains("$aiCoreIdForRecovery") && 2704 //member.getCaptain().isAICore() && 2705 !Misc.isUnremovable(member.getCaptain())) { 2706 //loot.addItems(CargoItemType.RESOURCES, member.getCaptain().getAICoreId(), 1); 2707 loot.addItems(CargoItemType.RESOURCES, 2708 member.getCaptain().getMemoryWithoutUpdate().getString("$aiCoreIdForRecovery"), 1); 2709 } 2710 2711 if (own) { 2712 HullModItemManager.getInstance().giveBackAllItems(member, loot); 2713 } 2714 2715 2716 Random random = Misc.random; 2717 if (salvageRandom != null) random = salvageRandom; 2718 2719 String coreIdOverride = null; 2720 if (member.getCaptain() != null && 2721 member.getCaptain().getMemoryWithoutUpdate().contains("$aiCoreIdForPossibleRecovery")) { 2722 coreIdOverride = member.getCaptain().getMemoryWithoutUpdate().getString("$aiCoreIdForPossibleRecovery"); 2723 } 2724 if (!own && !lootingModule && 2725 (member.getCaptain().isAICore() || coreIdOverride != null) && 2726 !variant.hasTag(Tags.VARIANT_DO_NOT_DROP_AI_CORE_FROM_CAPTAIN)) { 2727 String cid = member.getCaptain().getAICoreId(); 2728 if (coreIdOverride != null) { 2729 cid = coreIdOverride; 2730 } 2731 if (cid != null) { 2732 CommoditySpecAPI spec = Global.getSettings().getCommoditySpec(cid); 2733 if (!spec.hasTag(Tags.NO_DROP)) { 2734 float prob = Global.getSettings().getFloat("drop_prob_officer_" + cid); 2735 if (member.isStation()) { 2736 prob *= Global.getSettings().getFloat("drop_prob_mult_ai_core_station"); 2737 } else if (member.isFrigate()) { 2738 prob *= Global.getSettings().getFloat("drop_prob_mult_ai_core_frigate"); 2739 } else if (member.isDestroyer()) { 2740 prob *= Global.getSettings().getFloat("drop_prob_mult_ai_core_destroyer"); 2741 } else if (member.isCruiser()) { 2742 prob *= Global.getSettings().getFloat("drop_prob_mult_ai_core_cruiser"); 2743 } else if (member.isCapital()) { 2744 prob *= Global.getSettings().getFloat("drop_prob_mult_ai_core_capital"); 2745 } 2746 if (prob > 0 && random.nextFloat() < prob) { 2747 loot.addItems(CargoItemType.RESOURCES, cid, 1); 2748 } 2749 } 2750 } 2751 2752 } 2753 2754 float p = Global.getSettings().getFloat("salvageWeaponProb"); 2755 if (own) { 2756 p = Global.getSettings().getFloat("salvageOwnWeaponProb"); 2757 p = Global.getSector().getPlayerFleet().getStats().getDynamic().getValue(Stats.OWN_WEAPON_RECOVERY_MOD, p); 2758 } else { 2759 p = Global.getSector().getPlayerFleet().getStats().getDynamic().getValue(Stats.ENEMY_WEAPON_RECOVERY_MOD, p); 2760 } 2761 boolean alreadyStripped = recoverableShips.contains(member); 2762 2763 2764 Set<String> remove = new HashSet<String>(); 2765 2766 // there's another failsafe for OMEGA specifically, see SalvageDefenderInteraction.postPlayerSalvageGeneration() 2767 if (variant.hasTag(Tags.VARIANT_CONSISTENT_WEAPON_DROPS)) { 2768 for (String slotId : variant.getNonBuiltInWeaponSlots()) { 2769 String weaponId = variant.getWeaponId(slotId); 2770 if (weaponId == null) continue; 2771 if (loot.getNumWeapons(weaponId) <= 0) { 2772 WeaponSpecAPI spec = Global.getSettings().getWeaponSpec(weaponId); 2773 if (spec.hasTag(Tags.NO_DROP)) continue; 2774 2775 loot.addWeapons(weaponId, 1); 2776 remove.add(slotId); 2777 } 2778 } 2779 } 2780 2781 for (String slotId : variant.getNonBuiltInWeaponSlots()) { 2782 if (remove.contains(slotId)) continue; 2783 //if ((float) Math.random() * mult > 0.75f) { 2784 if (!alreadyStripped) { 2785 if (random.nextFloat() > mult) continue; 2786 if (random.nextFloat() > p) continue; 2787 } 2788 2789 String weaponId = variant.getWeaponId(slotId); 2790 WeaponSpecAPI spec = Global.getSettings().getWeaponSpec(weaponId); 2791 if (spec.hasTag(Tags.NO_DROP)) continue; 2792 2793 loot.addItems(CargoAPI.CargoItemType.WEAPONS, weaponId, 1); 2794 remove.add(slotId); 2795 } 2796 2797 2798 for (String slotId : variant.getModuleSlots()) { 2799 WeaponSlotAPI slot = variant.getSlot(slotId); 2800 if (slot.isStationModule()) { 2801 ShipVariantAPI module = variant.getModuleVariant(slotId); 2802 if (module == null) continue; 2803 lootWeapons(member, module, own, mult, true); 2804 } 2805 } 2806 // DO NOT DO THIS - no point in removing them here since the ship is scrapped 2807 // and would need to clone the variant to do this right 2808// for (String slotId : remove) { 2809// variant.clearSlot(slotId); 2810// } 2811 //System.out.println("Cleared variant: " + variant.getHullVariantId()); 2812 } 2813 2814 public void autoLoot() { 2815 DataForEncounterSide winner = getWinnerData(); 2816 DataForEncounterSide loser = getLoserData(); 2817 if (winner == null || loser == null) return; 2818 2819 List<CampaignFleetAPI> winners = battle.getSideFor(winner.getFleet()); 2820 WeightedRandomPicker<CampaignFleetAPI> picker = new WeightedRandomPicker<CampaignFleetAPI>(); 2821 for (CampaignFleetAPI curr : winners) { 2822 picker.add(curr, curr.getFleetPoints()); 2823 } 2824 for (CargoStackAPI stack : loot.getStacksCopy()) { 2825 if (stack.isNull() || stack.isFuelStack()) continue; 2826 2827 CampaignFleetAPI pick = picker.pick(); 2828 if (pick == null) break; 2829 2830 CargoAPI winnerCargo = pick.getCargo(); 2831 float spaceLeft = winnerCargo.getSpaceLeft(); 2832 if (spaceLeft <= 0) { 2833 picker.remove(pick); 2834 continue; 2835 } 2836 2837 float spacePerUnit = stack.getCargoSpacePerUnit(); 2838 float maxUnits = (int) (spaceLeft / spacePerUnit); 2839 if (maxUnits > stack.getSize()) maxUnits = stack.getSize(); 2840 maxUnits = Math.round(maxUnits * (Math.random() * 0.5f + 0.5f)); 2841 winnerCargo.addItems(stack.getType(), stack.getData(), maxUnits); 2842 } 2843 2844 picker.clear(); 2845 for (CampaignFleetAPI curr : winners) { 2846 picker.add(curr, curr.getFleetPoints()); 2847 } 2848 for (CargoStackAPI stack : loot.getStacksCopy()) { 2849 if (stack.isNull() || !stack.isFuelStack()) continue; 2850 2851 CampaignFleetAPI pick = picker.pick(); 2852 if (pick == null) break; 2853 2854 CargoAPI winnerCargo = pick.getCargo(); 2855 float spaceLeft = winnerCargo.getMaxCapacity() - winnerCargo.getFuel(); 2856 if (spaceLeft <= 0) { 2857 picker.remove(pick); 2858 continue; 2859 } 2860 2861 float spacePerUnit = stack.getCargoSpacePerUnit(); 2862 float maxUnits = (int) (spaceLeft / spacePerUnit); 2863 if (maxUnits > stack.getSize()) maxUnits = stack.getSize(); 2864 maxUnits = Math.round(maxUnits * (Math.random() * 0.5f + 0.5f)); 2865 winnerCargo.addItems(stack.getType(), stack.getData(), maxUnits); 2866 } 2867 } 2868 2869 public boolean hasWinnerAndLoser() { 2870 return getWinner() != null && getLoser() != null; 2871 } 2872 2873 public CampaignFleetAPI getWinner() { 2874 return getWinnerData() != null ? getWinnerData().getFleet() : null; 2875 } 2876 public CampaignFleetAPI getLoser() { 2877 return getLoserData() != null ? getLoserData().getFleet() : null; 2878 } 2879 2880 public boolean canOutrunOtherFleet(CampaignFleetAPI fleet, CampaignFleetAPI other) { 2881 return fleet.getFleetData().getMinBurnLevel() >= other.getFleetData().getMaxBurnLevel() + 1f; 2882 } 2883 2884 2885 protected void applyResultToFleets(EngagementResultAPI result) { 2886// applyCrewAndShipLosses(result); 2887// fixFighters(result.getWinnerResult()); 2888// fixFighters(result.getLoserResult()); 2889 applyShipLosses(result); 2890 applyCrewLosses(result); 2891 } 2892 2893 2894 public void fixFighters(EngagementResultForFleetAPI result) { 2895 Set<CampaignFleetAPI> fleetsWithDecks = new HashSet<CampaignFleetAPI>(); 2896 for (FleetMemberAPI curr : result.getReserves()) { 2897 if (battle.getSourceFleet(curr) == null) continue; 2898 if (curr.isMothballed()) continue; 2899 if (curr.getNumFlightDecks() > 0) { 2900 fleetsWithDecks.add(battle.getSourceFleet(curr)); 2901 } 2902 } 2903 for (FleetMemberAPI curr : result.getDeployed()) { 2904 if (battle.getSourceFleet(curr) == null) continue; 2905 if (curr.isMothballed()) continue; 2906 if (curr.getNumFlightDecks() > 0) { 2907 fleetsWithDecks.add(battle.getSourceFleet(curr)); 2908 } 2909 } 2910 for (FleetMemberAPI curr : result.getRetreated()) { 2911 if (battle.getSourceFleet(curr) == null) continue; 2912 if (curr.isMothballed()) continue; 2913 if (curr.getNumFlightDecks() > 0) { 2914 fleetsWithDecks.add(battle.getSourceFleet(curr)); 2915 } 2916 } 2917 2918 List<FleetMemberAPI> saved = new ArrayList<FleetMemberAPI>(); 2919 for (FleetMemberAPI curr : result.getDestroyed()) { 2920 if (battle.getSourceFleet(curr) == null) continue; 2921 if (!fleetsWithDecks.contains(battle.getSourceFleet(curr))) continue; 2922 if (curr.isFighterWing()) { 2923 saved.add(curr); 2924 } 2925 } 2926 2927 result.getDestroyed().removeAll(saved); 2928 result.getRetreated().addAll(saved); 2929 2930 2931 List<FleetMemberAPI> toRepair = new ArrayList<FleetMemberAPI>(); 2932 toRepair.addAll(result.getDeployed()); 2933 toRepair.addAll(result.getRetreated()); 2934 for (FleetMemberAPI curr : toRepair) { 2935 if (battle.getSourceFleet(curr) == null) continue; 2936 if (curr.isFighterWing()) { 2937 if (fleetsWithDecks.contains(battle.getSourceFleet(curr))) { 2938 curr.getStatus().repairFully(); 2939 } else { 2940 curr.getStatus().repairFullyNoNewFighters(); 2941 } 2942 } 2943 } 2944 2945 } 2946 2947 protected void applyCrewLosses(EngagementResultAPI result) { 2948 EngagementResultForFleetAPI winner = result.getWinnerResult(); 2949 EngagementResultForFleetAPI loser = result.getLoserResult(); 2950 2951 //boolean playerInvolved = winner.getFleet().isPlayerFleet() || loser.getFleet().isPlayerFleet(); 2952 boolean playerInvolved = battle.isPlayerInvolved(); 2953 calculateAndApplyCrewLosses(winner, playerInvolved); 2954 calculateAndApplyCrewLosses(loser, playerInvolved); 2955 2956// applyCrewLosses(winner); 2957// applyCrewLosses(loser); 2958 } 2959 2960 protected void applyShipLosses(EngagementResultAPI result) { 2961 EngagementResultForFleetAPI winner = result.getWinnerResult(); 2962 EngagementResultForFleetAPI loser = result.getLoserResult(); 2963 2964 applyShipLosses(winner); 2965 applyShipLosses(loser); 2966 2967 applyCREffect(winner); 2968 applyCREffect(loser); 2969 } 2970 2971 protected Map<FleetMemberAPI, Float> preEngagementCRForWinner = new HashMap<FleetMemberAPI, Float>(); 2972 protected void applyCREffect(EngagementResultForFleetAPI result) { 2973 boolean wonBattle = result.isWinner(); 2974 if (wonBattle) { 2975 preEngagementCRForWinner.clear(); 2976 for (FleetMemberAPI member : result.getFleet().getFleetData().getMembersListCopy()) { 2977 preEngagementCRForWinner.put(member, member.getRepairTracker().getCR()); 2978 } 2979 } 2980 2981 List<FleetMemberAPI> applyDeployCostTo = new ArrayList<FleetMemberAPI>(result.getDeployed()); 2982 2983 for (FleetMemberAPI member : result.getDisabled()) { 2984 // does not work, needs more things changed to work 2985 //float mult = member.getStats().getDynamic().getValue(Stats.CR_LOSS_WHEN_DISABLED_MULT); 2986 float mult = 1f; 2987 if (mult > 0) { 2988 member.getRepairTracker().applyCREvent(-1f * mult, "disabled in combat"); 2989 } 2990 if (mult < 1) { 2991 applyDeployCostTo.add(member); 2992 } 2993 } 2994 for (FleetMemberAPI member : result.getDestroyed()) { 2995// if (member.getHullId().equals("vanguard_pirates")) { 2996// System.out.println("efwefwef"); 2997// } 2998 //float mult = member.getStats().getDynamic().getValue(Stats.CR_LOSS_WHEN_DISABLED_MULT); 2999 float mult = 1f; 3000 if (mult > 0) { 3001 member.getRepairTracker().applyCREvent(-1f * mult, "disabled in combat"); 3002 } 3003 if (mult < 1) { 3004 applyDeployCostTo.add(member); 3005 } 3006 } 3007 3008 for (FleetMemberAPI member : applyDeployCostTo) { 3009 float deployCost = getDeployCost(member); 3010 if (member.isFighterWing()) { 3011 member.getRepairTracker().applyCREvent(-deployCost, "wing deployed in combat"); 3012 } else { 3013 member.getRepairTracker().applyCREvent(-deployCost, "deployed in combat"); 3014 } 3015 3016 applyExtendedCRLossIfNeeded(result, member); 3017 } 3018 3019 //float retreatLossMult = StarfarerSettings.getCRLossMultForRetreatInLoss(); 3020 float retreatLossMult = Global.getSettings().getFloat("crLossMultForRetreatInLoss"); 3021 for (FleetMemberAPI member : result.getRetreated()) { 3022 float deployCost = getDeployCost(member); 3023 if (member.isFighterWing()) { 3024 member.getRepairTracker().applyCREvent(-deployCost, "wing deployed in combat"); 3025 } else { 3026 member.getRepairTracker().applyCREvent(-deployCost, "deployed in combat"); 3027 } 3028 3029 applyExtendedCRLossIfNeeded(result, member); 3030 3031 if (!wonBattle && result.getGoal() != FleetGoal.ESCAPE) { 3032 float retreatCost = deployCost * retreatLossMult; 3033 if (retreatCost > 0) { 3034 member.getRepairTracker().applyCREvent(-retreatCost, "retreated from lost engagement"); 3035 } 3036 } 3037 } 3038 3039// // important, so that in-combat Ship objects can be garbage collected. 3040// // Probably some combat engine references in there, too. 3041// // NOTE: moved this elsewhere in this class 3042// result.resetAllEverDeployed(); 3043// getDataFor(result.getFleet()).getMemberToDeployedMap().clear(); 3044 } 3045 3046 3047// protected void saveAmmoState(FleetMemberAPI member, ShipAPI ship) { 3048// if (ship == null) return; 3049// 3050// Map<String, Integer> ammo = member.getAmmoStateAtEndOfLastEngagement(); 3051// for (WeaponAPI w : ship.getAllWeapons()) { 3052// if (w.usesAmmo() && w.getAmmoPerSecond() <= 0) { 3053// ammo.put(w.getSlot().getId(), w.getAmmo()); 3054// } 3055// } 3056// } 3057 3058 /** 3059 * Only matters in non-auto-resolved battles. 3060 * @param member 3061 */ 3062 protected void applyExtendedCRLossIfNeeded(EngagementResultForFleetAPI result, FleetMemberAPI member) { 3063 DeployedFleetMemberAPI dfm = getDataFor(result.getFleet()).getMemberToDeployedMap().get(member); 3064 if (dfm == null) return; 3065 3066 if (battle != null && battle.getSourceFleet(member) == null) { 3067 return; 3068 } 3069 if (member.getFleetCommander() == null) { 3070 return; 3071 } 3072 3073 if (dfm.getMember() == member && dfm.isFighterWing()) { 3074 //float finalCR = dfm.getShip().getRemainingWingCR(); 3075 float cr = member.getRepairTracker().getBaseCR(); 3076 float finalCR = cr; 3077 if (cr > finalCR) { 3078 member.getRepairTracker().applyCREvent(-(cr - finalCR), "deployed replacement chassis in combat"); 3079 } 3080 return; 3081 } 3082 if (dfm.getMember() == member && !dfm.isFighterWing()) { 3083 float deployCost = getDeployCost(member); 3084 float endOfCombatCR = dfm.getShip().getCurrentCR() - deployCost; 3085 float cr = member.getRepairTracker().getCR(); 3086 if (cr > endOfCombatCR) { 3087 member.getRepairTracker().applyCREvent(-(cr - endOfCombatCR), "extended deployment"); 3088 } 3089 3090 ShipAPI ship = dfm.getShip(); 3091 if (dfm.getShip() != null && !dfm.isFighterWing()) { 3092 float wMult = Global.getSettings().getFloat("crLossMultForWeaponDisabled"); 3093 float eMult = Global.getSettings().getFloat("crLossMultForFlameout"); 3094 float mMult = Global.getSettings().getFloat("crLossMultForMissilesFired"); 3095 float hMult = Global.getSettings().getFloat("crLossMultForHullDamage"); 3096 3097 float hullDamageFraction = ship.getHullLevelAtDeployment() - ship.getLowestHullLevelReached(); 3098 float hullDamageCRLoss = hullDamageFraction * hMult; 3099 hullDamageCRLoss *= ship.getMutableStats().getDynamic().getValue(Stats.HULL_DAMAGE_CR_LOSS); 3100 if (hullDamageCRLoss > 0) { 3101 member.getRepairTracker().applyCREvent(-hullDamageCRLoss, "hull damage sustained"); 3102 } 3103 3104 member.getStatus().setHullFraction(ship.getLowestHullLevelReached()); 3105 3106 3107 float instaRepairFraction = member.getStats().getDynamic().getValue(Stats.INSTA_REPAIR_FRACTION, 0f); 3108 if (instaRepairFraction > 0) { 3109 float hullDamage = member.getStatus().getHullDamageTaken(); 3110 float armorDamage = member.getStatus().getArmorDamageTaken(); 3111 3112 member.getStatus().repairArmorAllCells(armorDamage * instaRepairFraction); 3113 member.getStatus().repairHullFraction(hullDamage * instaRepairFraction); 3114 } 3115 3116 3117 float totalDisabled = 0f; 3118 MutableCharacterStatsAPI stats = member.getFleetCommander().getStats(); 3119 float maxOP = ship.getVariant().getHullSpec().getOrdnancePoints(stats); 3120 if (maxOP <= 1) maxOP = 1; 3121 3122 for (WeaponAPI w : ship.getDisabledWeapons()) { 3123 totalDisabled += w.getSpec().getOrdnancePointCost(stats, ship.getVariant().getStatsForOpCosts()) * wMult; 3124 } 3125 if (ship.getNumFlameouts() > 0) { 3126 totalDisabled += maxOP * eMult; 3127 } 3128 3129 float damageBasedCRLoss = Math.min(1f, totalDisabled / maxOP); 3130 if (damageBasedCRLoss > 0) { 3131 member.getRepairTracker().applyCREvent(-damageBasedCRLoss, "weapon and engine damage sustained"); 3132 } 3133 3134 float missileReloadOP = 0f; 3135 for (WeaponAPI w : ship.getAllWeapons()) { 3136 if (w.getType() == WeaponType.MISSILE && w.usesAmmo()) { 3137 missileReloadOP += (1f - (float) w.getAmmo() / (float) w.getMaxAmmo()) * w.getSpec().getOrdnancePointCost(stats, ship.getVariant().getStatsForOpCosts()) * mMult; 3138 } 3139 } 3140 3141 float missileReloadLoss = Math.min(1f, missileReloadOP / maxOP); 3142 if (missileReloadLoss > 0) { 3143 member.getRepairTracker().applyCREvent(-missileReloadLoss, "missile weapons used in combat"); 3144 } 3145 } 3146 3147 return; 3148 } 3149 } 3150 3151 3152 protected void applyShipLosses(EngagementResultForFleetAPI result) { 3153 for (FleetMemberAPI member : result.getDestroyed()) { 3154 if (battle.getSourceFleet(member) == null) continue; 3155 battle.getSourceFleet(member).removeFleetMemberWithDestructionFlash(member); 3156 result.getFleet().getFleetData().removeFleetMember(member); 3157 } 3158 for (FleetMemberAPI member : result.getDisabled()) { 3159 if (battle.getSourceFleet(member) == null) continue; 3160 battle.getSourceFleet(member).removeFleetMemberWithDestructionFlash(member); 3161 result.getFleet().getFleetData().removeFleetMember(member); 3162 } 3163 } 3164 3165// protected void applyCrewLosses(EngagementResultForFleetAPI result) { 3166// CargoAPI cargo = result.getFleet().getCargo(); 3167// DataForEncounterSide data = getDataFor(result.getFleet()); 3168// CrewCompositionAPI crewLosses = data.getCrewLossesDuringLastEngagement(); 3169// 3170// cargo.removeItems(CargoAPI.CargoItemType.RESOURCES, CargoAPI.CrewXPLevel.GREEN.getId(), crewLosses.getGreen()); 3171// cargo.removeItems(CargoAPI.CargoItemType.RESOURCES, CargoAPI.CrewXPLevel.REGULAR.getId(), crewLosses.getRegular()); 3172// cargo.removeItems(CargoAPI.CargoItemType.RESOURCES, CargoAPI.CrewXPLevel.VETERAN.getId(), crewLosses.getVeteran()); 3173// cargo.removeItems(CargoAPI.CargoItemType.RESOURCES, CargoAPI.CrewXPLevel.ELITE.getId(), crewLosses.getElite()); 3174// 3175// cargo.removeMarines((int) crewLosses.getMarines()); 3176// } 3177 3178 protected float computeLossFraction(FleetMemberAPI member, EngagementResultForFleetAPI result, float hullFraction, float hullDamage) { 3179 if (member == null && hullFraction == 0) { 3180 return (0.75f + (float) Math.random() * 0.25f); 3181 } 3182 3183 //System.out.println("hullDamage: " + hullDamage); 3184 if (member.isFighterWing() && result != null) { 3185 //System.out.println("Fighter hullDamage: " + hullDamage); 3186 float extraLossMult = hullDamage; 3187 DeployedFleetMemberAPI dfm = getDataFor(result.getFleet()).getMemberToDeployedMap().get(member); 3188 if (dfm != null && dfm.getMember() == member) { 3189 //float finalCR = dfm.getShip().getRemainingWingCR(); 3190 float cr = member.getRepairTracker().getCR(); 3191 float finalCR = cr; 3192 if (cr > finalCR) { 3193 float crPer = dfm.getMember().getStats().getCRPerDeploymentPercent().computeEffective(dfm.getMember().getVariant().getHullSpec().getCRToDeploy()) / 100f; 3194 float extraCraftLost = (cr - finalCR) / crPer; 3195 float wingSize = dfm.getMember().getNumFightersInWing(); 3196 if (extraCraftLost >= 1) { 3197 extraLossMult = hullDamage + extraCraftLost / wingSize; 3198 } 3199 } 3200 } 3201 return (0.25f + (float) Math.random() * 0.75f * (float) Math.random()) * member.getStats().getCrewLossMult().getModifiedValue() * extraLossMult; 3202 } 3203 3204 3205 float extraFromFighters = 0f; 3206 if (!member.isFighterWing() && result != null) { 3207 DeployedFleetMemberAPI dfm = getDataFor(result.getFleet()).getMemberToDeployedMap().get(member); 3208 if (dfm != null && dfm.getMember() == member) { 3209 float craftCrewLoss = 0; 3210 for (FighterLaunchBayAPI bay : dfm.getShip().getLaunchBaysCopy()) { 3211 if (bay.getWing() == null || bay.getWing().getLeader() == null) continue; 3212 float baseCrew = bay.getWing().getLeader().getHullSpec().getMinCrew(); 3213 float perCraft = bay.getWing().getLeader().getMutableStats().getMinCrewMod().computeEffective(baseCrew); 3214 perCraft *= bay.getWing().getLeader().getMutableStats().getDynamic().getValue(Stats.FIGHTER_CREW_LOSS_MULT); 3215 craftCrewLoss += perCraft * bay.getNumLost(); 3216 } 3217 3218 float baseLossFraction = Global.getSettings().getFloat("fighterCrewLossBase"); 3219 craftCrewLoss *= baseLossFraction; 3220 3221 float memberCrew = member.getMinCrew(); 3222 if (memberCrew > 0) { 3223 float threshold = memberCrew * 0.33f; 3224 3225 float actualLost = 0f; 3226 float mult = 1f; 3227 do { 3228 float curr = Math.min(craftCrewLoss, threshold); 3229 craftCrewLoss -= curr; 3230 3231 curr *= mult; 3232 actualLost += curr; 3233 mult /= 2f; 3234 3235 } while (craftCrewLoss > 0); 3236 3237 extraFromFighters = actualLost / memberCrew; 3238 extraFromFighters *= member.getStats().getDynamic().getValue(Stats.FIGHTER_CREW_LOSS_MULT); 3239 } 3240 } 3241 } 3242 3243 if (hullFraction == 0) { 3244 return Math.min(1f, (0.75f + (float) Math.random() * 0.25f) * member.getStats().getCrewLossMult().getModifiedValue() + extraFromFighters); 3245 } 3246 return Math.min(1f, hullDamage * hullDamage * (0.5f + (float) Math.random() * 0.5f) * member.getStats().getCrewLossMult().getModifiedValue() + extraFromFighters); 3247 } 3248 3249 3250 3251 protected float computeRecoverableFraction(FleetMemberAPI member, EngagementResultForFleetAPI result, float hullFraction, float hullDamage) { 3252 float f = 1f - computeLossFraction(member, result, hullFraction, hullDamage); 3253 if (f < 0) f = 0; 3254 return f; 3255 } 3256 3257 public void calculateAndApplyCrewLosses(EngagementResultForFleetAPI result, boolean playerInvolved) { 3258 boolean wonBattle = result.isWinner(); 3259 3260 DataForEncounterSide data = getDataFor(result.getFleet()); 3261 CrewCompositionAPI recoverable = data.getRecoverableCrewLosses(); 3262 //recoverable.removeAllCrew(); 3263 3264 List<FleetMemberAPI> all = new ArrayList<FleetMemberAPI>(); 3265 all.addAll(result.getDisabled()); 3266 all.addAll(result.getDeployed()); 3267 all.addAll(result.getDestroyed()); 3268 all.addAll(result.getRetreated()); 3269 all.addAll(result.getReserves()); 3270 3271 for (FleetMemberAPI member : result.getReserves()) { 3272 member.getStatus().resetDamageTaken(); 3273 } 3274 3275 CrewCompositionAPI playerLosses = data.getCrewLossesDuringLastEngagement(); 3276 playerLosses.removeAllCrew(); 3277 3278 CrewCompositionAPI crewLosses = Global.getFactory().createCrewComposition(); 3279 crewLosses.removeAllCrew(); 3280 3281 CampaignFleetAPI playerFleet = null; 3282 //float maxExtraLoss = 0f; 3283 float playerCapacityLost = 0f; 3284 for (FleetMemberAPI member : all) { 3285 if (battle.getSourceFleet(member) == null) continue; 3286 boolean player = battle.getSourceFleet(member) != null && battle.getSourceFleet(member).isPlayerFleet(); 3287 CrewCompositionAPI c = member.getCrewComposition(); 3288 //float hull = member.getStatus().getHullFraction(); 3289 float hullDamage = member.getStatus().getHullDamageTaken(); 3290 float hullFraction = member.getStatus().getHullFraction(); 3291 member.getStatus().resetDamageTaken(); 3292 3293 //if (hullDamage > 0 && !result.getFleet().isPlayerFleet() && playerInvolved) { 3294// if (hullDamage > 0 && playerInvolved && 3295// !battle.getPlayerSide().contains(battle.getSourceFleet(member))) { 3296// playerDidSeriousDamage = true; 3297// } 3298// if (lostBattle) { 3299// System.out.println("HERE"); 3300// } 3301 3302 float f1 = computeLossFraction(member, result, hullFraction, hullDamage); 3303 3304 // ship is disabled or destroyed, lose all crew for now, but it may be recovered later 3305 if (result.getDisabled().contains(member) || result.getDestroyed().contains(member)) { 3306 if (playerInvolved && 3307 !battle.getPlayerSide().contains(battle.getSourceFleet(member))) { 3308 playerDidSeriousDamage = true; 3309 } 3310 if (player) { 3311 if (f1 < 1) { 3312 recoverable.addCrew((1f - f1) * c.getCrew()); 3313 } 3314 playerLosses.addCrew(c.getCrew() * 1f); 3315 playerCapacityLost += member.getMaxCrew(); 3316 } 3317 3318 crewLosses.addCrew(c.getCrew() * 1f); 3319 c.setCrew(0); 3320 //c.addCrew(-c.getCrew() * f1); 3321 // c should now be left with the appropriate crew composition (base minus losses) to use 3322 // as a starting point for boarding actions 3323 3324 } else { 3325 // the ship is still ok, only lose the non-recoverable casualties 3326 // for fighters, which can lose more than their actual max crew 3327 if (f1 > 1) { 3328 crewLosses.addCrew((f1 - 1) * c.getCrew()); 3329 } 3330 3331 float lost = c.getCrew() * f1; 3332 // both fighters and normal ships 3333 c.transfer(lost, crewLosses); 3334 3335 if (player) { 3336 playerLosses.addCrew(lost); 3337 playerCapacityLost += member.getMaxCrew() * f1; 3338 } 3339 } 3340 3341 if (battle.getSourceFleet(member).isPlayerFleet() && 3342 (crewLosses.getCrew() > 0 || crewLosses.getMarines() > 0)) { 3343 playerFleet = battle.getSourceFleet(member); 3344 } 3345 3346 CargoAPI cargo = battle.getSourceFleet(member).getCargo(); 3347 cargo.removeItems(CargoAPI.CargoItemType.RESOURCES, Commodities.CREW, (int)crewLosses.getCrew()); 3348 cargo.removeMarines((int) crewLosses.getMarines()); 3349 crewLosses.clear(); 3350 } 3351 3352 // lose over-capacity crew 3353 if (playerFleet != null) { 3354 playerFleet.getFleetData().updateCargoCapacities(); 3355 CargoAPI cargo = playerFleet.getCargo(); 3356 float maxCrew = cargo.getMaxPersonnel(); 3357 float totalCrew = cargo.getTotalCrew(); 3358 float marines = cargo.getMarines(); 3359 float recoverableTotal = recoverable.getCrew() + recoverable.getMarines(); 3360 3361 //recoverableTotal = 0f; // uncomment to let recoverable crew not be lost due to being over capacity 3362 3363 float total = totalCrew + marines + recoverableTotal; 3364 if (maxCrew + playerCapacityLost > 0) { 3365 total *= playerCapacityLost / (maxCrew + playerCapacityLost); 3366 } 3367// if (!wonBattle) { 3368// total = totalCrew + marines; 3369// recoverableTotal = 0f; 3370// } 3371 //if (total > playerOvercapLosses) total = playerOvercapLosses; 3372 if (total > maxCrew) { 3373 //float toLose = Math.min(maxExtraLoss, total - maxCrew); 3374 float toLose = total - maxCrew; 3375 if (toLose > 0) { 3376 //recoverable.clear(); 3377 recoverable.transfer(Math.min(recoverableTotal, toLose), null); 3378 toLose -= recoverableTotal; 3379 total -= recoverableTotal; 3380 3381 if (toLose > 0 && total > 0) { 3382 float crew = cargo.getCrew(); // this is 99% the same as totalCrew, but leaving as is for now 3383 3384 crewLosses.clear(); 3385 crewLosses.addCrew((int)Math.ceil(crew / total * toLose)); 3386 crewLosses.addMarines((int)Math.ceil(marines / total * toLose)); 3387 3388 playerLosses.addCrew(crewLosses.getCrew() * 1f); 3389 playerLosses.addMarines(crewLosses.getMarines() * 1f); 3390 3391 cargo.removeItems(CargoAPI.CargoItemType.RESOURCES, Commodities.CREW, (int)crewLosses.getCrew()); 3392 cargo.removeMarines((int) crewLosses.getMarines()); 3393 crewLosses.clear(); 3394 } 3395 } 3396 } 3397 } 3398 3399 } 3400 3401 public void recoverCrew(CampaignFleetAPI fleet) { 3402 if (battle.isPlayerSide(battle.getSideFor(fleet))) { 3403 DataForEncounterSide data = getDataFor(fleet); 3404 CargoAPI cargo = Global.getSector().getPlayerFleet().getCargo(); 3405 CrewCompositionAPI rec = data.getRecoverableCrewLosses(); 3406 3407 cargo.addItems(CargoAPI.CargoItemType.RESOURCES, Commodities.CREW, rec.getCrew()); 3408 3409 cargo.addMarines((int) rec.getMarines()); 3410 } 3411 } 3412 3413 3414 3415 public float getDifficulty() { 3416 return difficulty; 3417 } 3418 3419 public void setDifficulty(float difficulty) { 3420 this.difficulty = difficulty; 3421 } 3422 3423 public boolean isComputedDifficulty() { 3424 return computedDifficulty; 3425 } 3426 3427 public void setComputedDifficulty(boolean computedDifficulty) { 3428 this.computedDifficulty = computedDifficulty; 3429 } 3430 3431 public static float MAX_XP_MULT = 6f; 3432 3433 protected float difficulty = 1f; 3434 protected boolean computedDifficulty = false; 3435 public float computeBattleDifficulty() { 3436 if (computedDifficulty) return difficulty; 3437 3438 computedDifficulty = true; 3439 if (battle == null || !battle.isPlayerInvolved()) { 3440 difficulty = 1f; 3441 return difficulty; 3442 } 3443 3444 float scorePlayer = 0f; 3445 float scoreEnemy = 0f; 3446 3447 float officerBase = 30; 3448 float officerPerLevel = 15; 3449 //float baseMult = 0.2f; 3450 float baseMult = 2f; 3451 float dModMult = 0.9f; 3452 3453 for (FleetMemberAPI member : battle.getNonPlayerCombined().getFleetData().getMembersListCopy()) { 3454 if (member.isMothballed()) continue; 3455 float mult = baseMult; 3456 if (member.isStation()) mult *= 1f; 3457 else if (member.isCivilian()) mult *= 0.25f; 3458 if (member.getCaptain() != null && !member.getCaptain().isDefault()) { 3459 scoreEnemy += officerBase + officerPerLevel * Math.max(1f, member.getCaptain().getStats().getLevel()); 3460 } 3461 int dMods = DModManager.getNumDMods(member.getVariant()); 3462 for (int i = 0; i < dMods; i++) { 3463 mult *= dModMult; 3464 } 3465// String prefix = "battle_difficulty_mult_"; 3466// for (String tag : member.getHullSpec().getTags()) { 3467// if (tag.startsWith(prefix)) { 3468// tag = tag.replaceFirst(prefix, ""); 3469// mult *= Float.parseFloat(tag); 3470// break; 3471// } 3472// } 3473 //scoreEnemy += member.getUnmodifiedDeploymentPointsCost() * mult; 3474 scoreEnemy += member.getFleetPointCost() * mult; 3475 } 3476 scoreEnemy *= 0.6f; 3477 3478 float maxPlayserShipScore = 0f; 3479 3480 officerBase *= 0.5f; 3481 officerPerLevel *= 0.5f; 3482 Set<PersonAPI> seenOfficers = new HashSet<PersonAPI>(); 3483 int unofficeredShips = 0; 3484 for (FleetMemberAPI member : battle.getPlayerCombined().getFleetData().getMembersListCopy()) { 3485 if (member.isMothballed()) continue; 3486 float mult = baseMult; 3487 if (member.isStation()) mult *= 1f; 3488 else if (member.isCivilian()) mult *= 0.25f; 3489 if (member.getCaptain() != null && !member.getCaptain().isDefault()) { 3490 scorePlayer += officerBase + officerPerLevel * Math.max(1f, member.getCaptain().getStats().getLevel()); 3491 seenOfficers.add(member.getCaptain()); 3492 } else if (!member.isCivilian()) { 3493 unofficeredShips++; 3494 } 3495 int dMods = DModManager.getNumDMods(member.getVariant()); 3496 for (int i = 0; i < dMods; i++) { 3497 mult *= dModMult; 3498 } 3499 //float currShipBaseScore = member.getUnmodifiedDeploymentPointsCost() * mult; 3500 float currShipBaseScore = member.getFleetPointCost() * mult; 3501 scorePlayer += currShipBaseScore; 3502 if (battle.getSourceFleet(member) != null && battle.getSourceFleet(member).isPlayerFleet()) { 3503 maxPlayserShipScore = Math.max(maxPlayserShipScore, currShipBaseScore); 3504 } 3505 } 3506 3507 // so that removing officers from ships prior to a fight doesn't increase the XP gained 3508 // otherwise would usually be optimal to do this prior to every fight for any officers 3509 // on ships that aren't expected to be deployed 3510 for (OfficerDataAPI od : Global.getSector().getPlayerFleet().getFleetData().getOfficersCopy()) { 3511 if (seenOfficers.contains(od.getPerson())) continue; 3512 if (od.getPerson().isPlayer()) continue; 3513 if (unofficeredShips <= 0) break; 3514 unofficeredShips--; 3515 scorePlayer += officerBase + officerPerLevel * Math.max(1f, od.getPerson().getStats().getLevel()); 3516 } 3517 3518 scorePlayer = Math.max(scorePlayer, Math.min(scoreEnemy * 0.5f, maxPlayserShipScore * 6f)); 3519 3520 if (scorePlayer < 1) scorePlayer = 1; 3521 if (scoreEnemy < 1) scoreEnemy = 1; 3522 3523 3524// difficulty = scoreEnemy / (scorePlayer + scoreEnemy); 3525// if (difficulty > 1) difficulty = 1; 3526// if (scorePlayer < scoreEnemy) { 3527// difficulty *= MAX_XP_MULT; 3528// } 3529 //difficulty = scoreEnemy / (1f * scorePlayer); 3530 difficulty = scoreEnemy / scorePlayer; 3531 if (difficulty < 0) difficulty = 0; 3532 if (difficulty > MAX_XP_MULT) difficulty = MAX_XP_MULT; 3533 return difficulty; 3534 } 3535} 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546