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