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