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