001package com.fs.starfarer.api.impl.combat;
002
003import java.util.ArrayList;
004import java.util.Collection;
005import java.util.List;
006
007import com.fs.starfarer.api.Global;
008import com.fs.starfarer.api.campaign.FleetDataAPI;
009import com.fs.starfarer.api.combat.CombatReadinessPlugin;
010import com.fs.starfarer.api.combat.FighterLaunchBayAPI;
011import com.fs.starfarer.api.combat.MutableShipStatsAPI;
012import com.fs.starfarer.api.combat.MutableStat.StatMod;
013import com.fs.starfarer.api.combat.ShipAPI;
014import com.fs.starfarer.api.combat.ShipAPI.HullSize;
015import com.fs.starfarer.api.combat.ShipEngineControllerAPI.ShipEngineAPI;
016import com.fs.starfarer.api.combat.WeaponAPI;
017import com.fs.starfarer.api.combat.WeaponAPI.WeaponType;
018import com.fs.starfarer.api.fleet.FleetMemberAPI;
019import com.fs.starfarer.api.impl.campaign.ids.Stats;
020import com.fs.starfarer.api.loading.WeaponSpecAPI;
021
022public class CRPluginImpl implements CombatReadinessPlugin {
023
024        public static float NO_SYSTEM_THRESHOLD = 0.0f;
025        
026        public static float IMPROVE_START = 0.7f;
027        public static float DEGRADE_START = 0.5f;
028        public static float SHIELD_MALFUNCTION_START = 0.1f;
029        public static float CRITICAL_MALFUNCTION_START = 0.2f;
030        public static float MALFUNCTION_START = 0.4f;
031        public static float MISSILE_AMMO_REDUCTION_START = 0.4f;
032        
033        public static float MAX_MOVEMENT_CHANGE = 10f; // percent
034        public static float MAX_DAMAGE_TAKEN_CHANGE = 10f; // percent
035        //public static float MAX_ROF_CHANGE = 25f; // percent
036        public static float MAX_DAMAGE_CHANGE = 10f; // percent
037        public static float MAX_REFIT_CHANGE = 10f; // percent
038        
039        public static float MAX_SHIELD_MALFUNCTION_CHANCE = 5f; // percent
040        public static float MAX_CRITICAL_MALFUNCTION_CHANCE = 25f; // percent
041        public static float MAX_ENGINE_MALFUNCTION_CHANCE = 7.5f; // percent
042        public static float MAX_WEAPON_MALFUNCTION_CHANCE = 10f; // percent
043        
044        
045        public void applyMaxCRCrewModifiers(FleetMemberAPI member) {
046                if (member == null || member.getStats() == null) return;
047                //float maxCRBasedOnLevel = (40f + member.getCrewFraction() * 10f) / 100f;
048                float maxCRBasedOnLevel = 0.7f;
049                member.getStats().getMaxCombatReadiness().modifyFlat("crew skill bonus", maxCRBasedOnLevel, "Basic maintenance");
050                
051                float cf = member.getCrewFraction();
052                if (cf < 1) {
053                        float penalty = 0.5f * (1f - cf);
054                        //float penalty = (1f - cf);
055                        member.getStats().getMaxCombatReadiness().modifyFlat("crew understrength", -penalty, "Crew understrength");
056                } else {
057                        member.getStats().getMaxCombatReadiness().unmodifyFlat("crew understrength");
058                }
059        }
060        
061        
062        
063        public List<CRStatusItemData> getCRStatusDataForShip(ShipAPI ship) {
064                float startingCR = ship.getCRAtDeployment();
065                float cr = ship.getCurrentCR();
066                
067                List<CRStatusItemData> list = new ArrayList<CRStatusItemData>();
068                
069                String icon = null;
070                
071                if (cr > getImproveThreshold(ship.getMutableStats())) {
072                        icon = Global.getSettings().getSpriteName("ui", "icon_tactical_cr_bonus");
073                } else if (cr < getDegradeThreshold(ship.getMutableStats())) {
074                        icon = Global.getSettings().getSpriteName("ui", "icon_tactical_cr_penalty");
075                } else {
076                        icon = Global.getSettings().getSpriteName("ui", "icon_tactical_cr_neutral");
077                        //return list;
078                }
079                
080                String title = "Combat Readiness " + Math.round(cr * 100f) + "%";
081                
082                String malfStr = getMalfunctionString(ship.getMutableStats(), cr);
083                
084                if (cr <= NO_SYSTEM_THRESHOLD && ship.getShield() != null) {
085                        CRStatusItemData itemData = new CRStatusItemData(statusKeys[9], icon, title,
086                                        "Shields offline", true);
087                        list.add(itemData);
088                }
089                
090                if (cr <= NO_SYSTEM_THRESHOLD) {
091                        boolean hasWings = false;
092                        for (FighterLaunchBayAPI bay : ship.getLaunchBaysCopy()) {
093                                if (bay.getWing() != null) {
094                                        hasWings = true;
095                                        break;
096                                }
097                        }
098                        if (hasWings) {
099                                CRStatusItemData itemData = new CRStatusItemData(statusKeys[10], icon, title,
100                                                "Fighter bays offline", true);
101                                list.add(itemData);
102                        }
103                }
104                
105                if (cr <= NO_SYSTEM_THRESHOLD && ship.getPhaseCloak() != null) {
106                        CRStatusItemData itemData = new CRStatusItemData(statusKeys[8], icon, title,
107                                        ship.getPhaseCloak().getDisplayName() + " offline", true);
108                        list.add(itemData);
109                }
110                
111                if (cr <= NO_SYSTEM_THRESHOLD && ship.getSystem() != null) {
112                        CRStatusItemData itemData = new CRStatusItemData(statusKeys[7], icon, title,
113                                         ship.getSystem().getDisplayName() + " offline", true);
114                        list.add(itemData);
115                }
116                
117                if (cr < getMalfunctionThreshold(ship.getMutableStats())) {
118                        CRStatusItemData itemData = new CRStatusItemData(statusKeys[0], icon, title,
119                                         "malfunction risk: " + malfStr, true);
120                        list.add(itemData);
121                }
122                if (startingCR < getMissileAmmoReductionThreshold(ship.getMutableStats())) {
123                        CRStatusItemData itemData = new CRStatusItemData(statusKeys[2], icon, title,
124                                         "missiles not fully loaded", true);
125                        list.add(itemData);
126                }
127                
128                if (cr < getDegradeThreshold(ship.getMutableStats())) {
129                        CRStatusItemData itemData = new CRStatusItemData(statusKeys[3], icon, title,
130                                        "degraded performance", true);
131                        list.add(itemData);
132                } else if (cr > getImproveThreshold(ship.getMutableStats())) {
133                        CRStatusItemData itemData = new CRStatusItemData(statusKeys[4], icon, title,
134                                        "improved performance", false);
135                        list.add(itemData);
136                } else {
137                        CRStatusItemData itemData = new CRStatusItemData(statusKeys[8], icon, title,
138                                        "standard performance", false);
139                        list.add(itemData);
140                }
141                
142                if (ship.losesCRDuringCombat() && cr > 0) {
143                        //float noLossTime = ship.getHullSpec().getNoCRLossTime();
144                        float noLossTime = ship.getMutableStats().getPeakCRDuration().computeEffective(ship.getHullSpec().getNoCRLossTime());
145                        if (noLossTime > ship.getTimeDeployedForCRReduction()) {
146                                CRStatusItemData itemData = new CRStatusItemData(statusKeys[5], icon, "peak active performance",
147                                                "remaining time: " + (int) (noLossTime - ship.getTimeDeployedForCRReduction()) + " sec", false);
148                                list.add(itemData);
149                        } else {
150                                CRStatusItemData itemData = new CRStatusItemData(statusKeys[6], icon, "combat stresses",
151                                                "degrading readiness", true);
152                                list.add(itemData);
153                        }
154                }
155                
156                return list;
157        }
158        
159        
160        private float getWeaponMalfuctionPercent(MutableShipStatsAPI stats, float cr) {
161                return MAX_WEAPON_MALFUNCTION_CHANCE * (getMalfunctionThreshold(stats) - cr) / getMalfunctionThreshold(stats);
162        }
163        private float getEngineMalfuctionPercent(MutableShipStatsAPI stats, float cr) {
164                return MAX_ENGINE_MALFUNCTION_CHANCE * (getMalfunctionThreshold(stats)- cr) / getMalfunctionThreshold(stats);
165        }
166        private float getCriticalMalfuctionPercent(MutableShipStatsAPI stats, float cr) {
167                return MAX_CRITICAL_MALFUNCTION_CHANCE * (getCriticalMalfunctionThreshold(stats)- cr) / getCriticalMalfunctionThreshold(stats);
168        }
169        private float getShieldMalfuctionPercent(MutableShipStatsAPI stats, float cr) {
170                return MAX_SHIELD_MALFUNCTION_CHANCE * (getShieldMalfunctionThreshold(stats)- cr) / getShieldMalfunctionThreshold(stats);
171        }
172        
173        
174        private float getMovementChangePercent(MutableShipStatsAPI stats, float cr) {
175//              if (cr > 0) {
176//                      System.out.println("wefwefe");
177//              }
178                float movementChange = 0f;
179                float d = getDegradeThreshold(stats);
180                float i = getImproveThreshold(stats);
181                if (cr < d) {
182                        float f = (d - cr) / d;
183                        movementChange = -1f * f * MAX_MOVEMENT_CHANGE;
184                } else if (cr > i) {
185                        float f = (cr - i) / (1f - i);
186                        movementChange = 1f * f * MAX_MOVEMENT_CHANGE;
187                }
188                return movementChange;
189        }
190        
191        private float getDamageTakenChangePercent(MutableShipStatsAPI stats, float cr) {
192                float damageTakenChange = 0f;
193                float d = getDegradeThreshold(stats);
194                float i = getImproveThreshold(stats);
195                if (cr < d) {
196                        float f = (d - cr) / d;
197                        damageTakenChange = 1f * f * MAX_DAMAGE_TAKEN_CHANGE;
198                } else if (cr > i) {
199                        float f = (cr - i) / (1f - i);
200                        damageTakenChange = -1f * f * MAX_DAMAGE_TAKEN_CHANGE;
201                }
202                return damageTakenChange;
203        }
204        
205        private float getRefitTimeChangePercent(MutableShipStatsAPI stats, float cr) {
206                float refitTimeChange = 0f;
207                float d = getDegradeThreshold(stats);
208                float i = getImproveThreshold(stats);
209                if (cr < d) {
210                        float f = (d - cr) / d;
211                        refitTimeChange = 1f * f * MAX_REFIT_CHANGE;
212                } else if (cr > i) {
213                        float f = (cr - i) / (1f - i);
214                        refitTimeChange = -1f * f * MAX_REFIT_CHANGE;
215                }
216                return refitTimeChange;
217        }
218        
219        private float getDamageChangePercent(MutableShipStatsAPI stats, float cr) {
220                float damageChange = 0f;
221                float d = getDegradeThreshold(stats);
222                float i = getImproveThreshold(stats);
223                if (cr < d) {
224                        float f = (d - cr) / d;
225                        damageChange = -1f * f * MAX_DAMAGE_CHANGE;
226                } else if (cr > i) {
227                        float f = (cr - i) / (1f - i);
228                        damageChange = 1f * f * MAX_DAMAGE_CHANGE;
229                }
230                return damageChange;
231        }
232//      private float getRateOfFireChangePercent(float cr) {
233//              float rateOfFireChange = 0f;
234//              if (cr < DEGRADE_START) {
235//                      float f = (DEGRADE_START - cr) / DEGRADE_START;
236//                      rateOfFireChange = -1f * f * MAX_ROF_CHANGE;
237//              } else if (cr > IMPROVE_START) {
238//                      float f = (cr - IMPROVE_START) / (1f - IMPROVE_START);
239//                      rateOfFireChange = 1f * f * MAX_ROF_CHANGE;
240//              }
241//              return rateOfFireChange;
242//      }
243        
244        /**
245         * From negative whatever to best accuracy of 1.
246         * @param cr
247         * @return
248         */
249        private float getAimAccuracy(float cr) {
250                return cr * 1.5f - 0.5f;
251        }
252        
253        public void applyCRToStats(float cr, MutableShipStatsAPI stats, HullSize hullSize) {
254                String id = "cr_effect";
255                //System.out.println("CR: " + cr);
256                boolean fighter = hullSize == HullSize.FIGHTER;
257
258//              if (hullSize == HullSize.CAPITAL_SHIP) {
259//                      System.out.println("Applying CR value of " + cr + " to stats " + stats + "(" + hullSize.name() + ")");
260//                      //new RuntimeException().printStackTrace();
261//              }
262
263                if (!fighter) {
264                        if (cr < getMalfunctionThreshold(stats)) {
265                                stats.getWeaponMalfunctionChance().modifyFlat(id, 0.01f * getWeaponMalfuctionPercent(stats, cr));
266                                stats.getEngineMalfunctionChance().modifyFlat(id, 0.01f * getEngineMalfuctionPercent(stats, cr));
267                        } else {
268                                stats.getWeaponMalfunctionChance().unmodify(id);
269                                stats.getEngineMalfunctionChance().unmodify(id);
270                        }
271                }
272                
273                if (!fighter) {
274                        if (cr < getCriticalMalfunctionThreshold(stats)) {
275                                stats.getCriticalMalfunctionChance().modifyFlat(id, 0.01f * getCriticalMalfuctionPercent(stats, cr));
276                        } else {
277                                stats.getCriticalMalfunctionChance().unmodify(id);
278                        }
279                }
280                
281                if (!fighter) {
282                        if (cr < getShieldMalfunctionThreshold(stats)) {
283                                stats.getShieldMalfunctionChance().modifyFlat(id, 0.01f * getShieldMalfuctionPercent(stats, cr));
284                                stats.getShieldMalfunctionFluxLevel().modifyFlat(id, 0.75f);
285                        } else {
286                                stats.getShieldMalfunctionChance().unmodify(id);
287                                stats.getShieldMalfunctionFluxLevel().unmodify(id);
288                        }
289                }
290        
291                if (!fighter) {
292                        if (stats.getEntity() instanceof ShipAPI) {
293                                ShipAPI ship = (ShipAPI)stats.getEntity();
294                                if (cr <= NO_SYSTEM_THRESHOLD) {
295                                        ship.setShipSystemDisabled(true);
296                                        ship.setDefenseDisabled(true);
297                                } else {
298                                        ship.setShipSystemDisabled(false);
299                                        ship.setDefenseDisabled(false);
300                                }
301                        }
302                }
303                
304                float movementChange = getMovementChangePercent(stats, cr);
305                float damageTakenChange = getDamageTakenChangePercent(stats, cr);
306                float damageChange = getDamageChangePercent(stats, cr);
307                float refitTimeChange = getRefitTimeChangePercent(stats, cr);
308                
309                if (refitTimeChange != 0) {
310                        stats.getFighterRefitTimeMult().modifyPercent(id, refitTimeChange);
311                } else {
312                        stats.getFighterRefitTimeMult().unmodify(id);
313                }
314                
315                if (movementChange != 0) {
316                        stats.getMaxSpeed().modifyPercent(id, movementChange);
317                        stats.getAcceleration().modifyPercent(id, movementChange);
318                        stats.getDeceleration().modifyPercent(id, movementChange);
319                        stats.getTurnAcceleration().modifyPercent(id, movementChange);
320                        stats.getMaxTurnRate().modifyPercent(id, movementChange);
321                } else {
322                        stats.getMaxSpeed().unmodify(id);
323                        stats.getAcceleration().unmodify(id);
324                        stats.getDeceleration().unmodify(id);
325                        stats.getTurnAcceleration().unmodify(id);
326                        stats.getMaxTurnRate().unmodify(id);
327                }
328                
329                if (damageTakenChange != 0) {
330                        stats.getArmorDamageTakenMult().modifyPercent(id, damageTakenChange);
331                        stats.getHullDamageTakenMult().modifyPercent(id, damageTakenChange);
332                        stats.getShieldDamageTakenMult().modifyPercent(id, damageTakenChange);
333                } else {
334                        stats.getArmorDamageTakenMult().unmodify(id);
335                        stats.getHullDamageTakenMult().unmodify(id);
336                        stats.getShieldDamageTakenMult().unmodify(id);
337                }
338                
339                if (damageChange != 0) {
340                        stats.getBallisticWeaponDamageMult().modifyPercent(id, damageChange);
341                        stats.getEnergyWeaponDamageMult().modifyPercent(id, damageChange);
342                        stats.getMissileWeaponDamageMult().modifyPercent(id, damageChange);
343                } else {
344                        stats.getBallisticWeaponDamageMult().unmodify(id);
345                        stats.getEnergyWeaponDamageMult().unmodify(id);
346                        stats.getMissileWeaponDamageMult().unmodify(id);
347                }
348                
349                float aimAccuracy = getAimAccuracy(cr);
350                stats.getAutofireAimAccuracy().modifyFlat(id, aimAccuracy);
351        }
352        
353        public void applyCRToShip(float cr, ShipAPI ship) {
354                if (!ship.isFighter() && cr < getMissileAmmoReductionThreshold(ship.getMutableStats())) {
355                        for (WeaponAPI weapon : ship.getAllWeapons()) {
356                                if (weapon.getType() == WeaponType.MISSILE) {
357                                        float ammo = (float) weapon.getMaxAmmo() * getMissileLoadedFraction(ship.getMutableStats(), cr);
358                                        if (ammo < 0) ammo = 0;
359                                        weapon.setAmmo(Math.round(ammo));
360                                }
361                        }
362                }
363                ship.setCRAtDeployment(cr);
364                
365                float c = getCriticalMalfunctionThreshold(ship.getMutableStats());
366                if (cr < c && !ship.controlsLocked() && !ship.isFighter()) {
367                        float severity = (c - cr) / (c);
368                        if (Global.getCombatEngine() != null) { // can be null if coming from refit on a fresh app start in campaign
369                                
370                                float criticalMult = 1f;
371                                for (StatMod mod : ship.getMutableStats().getCriticalMalfunctionChance().getMultMods().values()) { 
372                                        criticalMult *= mod.getValue();
373                                }
374                                severity *= criticalMult;
375                                Global.getCombatEngine().addPlugin(new LowCRShipDamageSequence(ship, severity));
376                        }
377                }
378        }
379        
380        public float getMissileLoadedFraction(MutableShipStatsAPI stats, float cr) {
381                if (true) return 1f;
382                float test = Global.getSettings().getFloat("noDeployCRPercent") * 0.01f;
383                float f = (cr - test) / (getMissileAmmoReductionThreshold(stats) - test);
384                if (f > 1) f = 1;
385                if (f < 0) f = 0;
386                return f;
387        }
388        
389        
390        public float getMalfunctionThreshold(MutableShipStatsAPI stats) {
391                float mult = 1f;
392                if (stats != null) mult *= stats.getDynamic().getStat(Stats.CR_MALFUNCION_RANGE).getModifiedValue();
393                return MALFUNCTION_START * mult - 0.001f;
394        }
395        public float getCriticalMalfunctionThreshold(MutableShipStatsAPI stats) {
396                float mult = 1f;
397                if (stats != null) mult *= stats.getDynamic().getStat(Stats.CR_MALFUNCION_RANGE).getModifiedValue();
398                return CRITICAL_MALFUNCTION_START * mult - 0.001f;
399        }
400        
401        public float getShieldMalfunctionThreshold(MutableShipStatsAPI stats) {
402                float mult = 1f;
403                if (stats != null) mult *= stats.getDynamic().getStat(Stats.CR_MALFUNCION_RANGE).getModifiedValue();
404                return SHIELD_MALFUNCTION_START * mult;
405        }
406        
407        public float getMissileAmmoReductionThreshold(MutableShipStatsAPI stats) {
408                float mult = 1f;
409                if (stats != null) mult *= stats.getDynamic().getStat(Stats.CR_MALFUNCION_RANGE).getModifiedValue();
410                return MISSILE_AMMO_REDUCTION_START * mult - 0.001f;
411        }
412        
413        public float getDegradeThreshold(MutableShipStatsAPI stats) {
414                float mult = 1f;
415                if (stats != null) mult *= stats.getDynamic().getStat(Stats.CR_MALFUNCION_RANGE).getModifiedValue();
416                return DEGRADE_START * mult;
417        }
418        
419        public float getImproveThreshold(MutableShipStatsAPI stats) {
420                float mult = 1f;
421                //if (stats != null) mult *= stats.getDynamic().getStat(Stats.CR_MALFUNCION_RANGE).getModifiedValue();
422                return IMPROVE_START * mult;
423        }
424        
425        
426        /**
427         * @param cr from 0 to 1
428         * @param shipOrWing "ship" or "fighter wing".
429         * @return
430         */
431        public CREffectDescriptionForTooltip getCREffectDescription(float cr, String shipOrWing, FleetMemberAPI member) {
432                CREffectDescriptionForTooltip result = new CREffectDescriptionForTooltip();
433                
434                List<CREffectDetail> details = getCREffectDetails(cr, member);
435                boolean hasPositive = false;
436                boolean hasNegative = false;
437                for (CREffectDetail detail : details) {
438                        if (detail.getType() == CREffectDetailType.BONUS) hasPositive = true;
439                        if (detail.getType() == CREffectDetailType.PENALTY) hasNegative = true;
440                }
441                
442                float noDeploy = Global.getSettings().getFloat("noDeployCRPercent") * 0.01f;
443                String crStr = (int)(cr * 100f) + "%";
444                String str;
445                if (cr < noDeploy) {
446                        str = String.format("The %s is not ready for combat and can not be deployed in battle.", shipOrWing);
447                } else if (cr < getCriticalMalfunctionThreshold(member.getStats())) {
448                        str = String.format("The %s suffers from degraded performance and runs the risk of permanent and damaging malfunctions if deployed.", shipOrWing);
449                } else if (cr < getMalfunctionThreshold(member.getStats())) {
450                        str = String.format("The %s suffers from degraded performance and runs the risk of weapon and engine malfunctions during combat.", shipOrWing);
451                } else if (cr < getDegradeThreshold(member.getStats()) && hasNegative) {
452                        str = String.format("The %s suffers from degraded performance during combat.", shipOrWing);
453                } else if (cr < getImproveThreshold(member.getStats()) || !hasPositive) {
454                        str = String.format("The %s has standard combat performance.", shipOrWing);
455                } else {
456                        str = String.format("The %s benefits from improved combat performance.", shipOrWing);
457                }
458                
459                //result.getHighlights().add(crStr);
460                
461
462                if (member.isFighterWing()) {
463                        boolean canReplaceFighters = false;
464                        FleetDataAPI data = member.getFleetData();
465                        if (data != null) {
466                                for (FleetMemberAPI curr : data.getMembersListCopy()) {
467                                        if (curr.isMothballed()) continue;
468                                        if (curr.getNumFlightDecks() > 0) {
469                                                canReplaceFighters = true;
470                                                break;
471                                        }
472                                }
473                        }
474                        if (canReplaceFighters) {
475                                details.add(new CREffectDetail("", "", CREffectDetailType.NEUTRAL));
476                                float costPer = member.getStats().getCRPerDeploymentPercent().computeEffective(member.getVariant().getHullSpec().getCRToDeploy()) / 100f;
477                                String numStr = "" + (int) Math.ceil((float)((int) (cr * 100f)) / (costPer * 100f));
478                                
479                                str += " " + numStr + " fighter chassis are ready to replace combat losses.";
480                                result.getHighlights().add(numStr);
481                                
482                        } else {
483                                details.add(new CREffectDetail("Replacement chassis", "None", CREffectDetailType.PENALTY));
484                                str += " " + "Fighter losses can not be replaced due to the lack of a ship with proper facilities (i.e. a flight deck).";
485                        }
486                }
487                
488                
489                result.setString(str);
490                
491                return result;
492        }
493        
494        private String getMalfunctionString(MutableShipStatsAPI stats, float cr) {
495                String malfStr = "None";
496                if (cr < getCriticalMalfunctionThreshold(stats)) {
497                        malfStr = "Critical";
498                } else if (cr < 0.3f) {
499                        malfStr = "Serious";
500                } else if (cr < getMalfunctionThreshold(stats)) {
501                        malfStr = "Low";
502                }
503                return malfStr;
504        }
505        
506        public List<CREffectDetail> getCREffectDetails(float cr, FleetMemberAPI member) {
507                List<CREffectDetail> result = new ArrayList<CREffectDetail>();
508                
509                int engine = (int) getEngineMalfuctionPercent(member.getStats(), cr);
510                int weapon = (int) getWeaponMalfuctionPercent(member.getStats(), cr);
511                
512                int speed = (int) Math.round(getMovementChangePercent(member.getStats(), cr));
513                int damage = (int) Math.round(getDamageTakenChangePercent(member.getStats(), cr));
514                int damageDealt = (int) Math.round(getDamageChangePercent(member.getStats(), cr));
515                int refit = (int) Math.round(getRefitTimeChangePercent(member.getStats(), cr));
516                
517                float acc = getAimAccuracy(cr);
518                
519                String malfStr = getMalfunctionString(member.getStats(), cr);
520                
521                String accString;
522                CREffectDetailType accType;
523                if (acc < 0) {
524                        accString = "Very poor";
525                        accType = CREffectDetailType.PENALTY;
526                } else if (acc < 0.25f) {
527                        accString = "Poor";
528                        accType = CREffectDetailType.PENALTY;
529                } else if (acc < 0.67) {
530                        accString = "Standard";
531                        accType = CREffectDetailType.NEUTRAL;
532                } else {
533                        accString = "Excellent";
534                        accType = CREffectDetailType.BONUS;
535                }
536                
537                String speedStr = speed + "%";
538                if (speed >= 0) {
539                        speedStr = "+" + speedStr;
540                }
541                String damageStr = damage + "%";
542                if (damage >= 0) {
543                        damageStr = "+" + damageStr;
544                }
545                String rofStr = damageDealt + "%";
546                if (damageDealt >= 0) {
547                        rofStr = "+" + rofStr;
548                }
549                
550                String refitStr = refit + "%";
551                if (refit >= 0) {
552                        refitStr = "+" + refitStr;
553                }
554                
555                result.add(new CREffectDetail("Speed & maneuverability", speedStr, getTypeFor(speed, false)));
556                result.add(new CREffectDetail("Damage taken", damageStr, getTypeFor(damage, true)));
557                result.add(new CREffectDetail("Damage dealt", rofStr, getTypeFor(damageDealt, false)));
558                
559                if (member.getNumFlightDecks() > 0) {
560                        result.add(new CREffectDetail("Fighter refit time", refitStr, getTypeFor(refit, true)));
561                }
562                
563                result.add(new CREffectDetail("Autofire accuracy", accString, accType));
564                
565
566                
567                CREffectDetailType malfType = CREffectDetailType.NEUTRAL;
568                if (getWeaponMalfuctionPercent(member.getStats(), cr) > 0) {
569                        malfType = CREffectDetailType.PENALTY;
570                }
571
572                result.add(new CREffectDetail("Malfunction risk", malfStr, malfType));
573                
574                Collection<String> slots = member.getVariant().getFittedWeaponSlots();
575                boolean hasMissiles = false;
576                for (String slotId : slots) {
577                        WeaponSpecAPI w = member.getVariant().getWeaponSpec(slotId);
578                        if (w.getType() == WeaponType.MISSILE) {
579                                hasMissiles = true;
580                                break;
581                        }
582                }
583                if (hasMissiles) {
584                        float missileFraction = getMissileLoadedFraction(member.getStats(), cr);
585                        if (missileFraction < 0) missileFraction = 0;
586                        String missileStr = (int)(missileFraction * 100f) + "%";
587                        
588                        if (missileFraction < 1f) {
589                                result.add(new CREffectDetail("Missile magazines", missileStr, missileFraction < 1 ? CREffectDetailType.PENALTY : CREffectDetailType.NEUTRAL));
590                        }
591                }
592                
593                return result;
594        }
595        
596        private CREffectDetailType getTypeFor(int val, boolean invert) {
597                if (invert) {
598                        if (val < 0) return CREffectDetailType.BONUS;
599                        else if (val > 0) return CREffectDetailType.PENALTY;
600                        return CREffectDetailType.NEUTRAL;
601                } else {
602                        if (val > 0) return CREffectDetailType.BONUS;
603                        else if (val < 0) return CREffectDetailType.PENALTY;
604                        return CREffectDetailType.NEUTRAL;
605                }
606        }
607        
608        protected static Object [] statusKeys  = new Object [] {
609                        new Object(),
610                        new Object(),
611                        new Object(),
612                        new Object(),
613                        new Object(),
614                        new Object(),
615                        new Object(),
616                        new Object(),
617                        new Object(),
618                        new Object(),
619                        new Object(),
620                        new Object(),
621                        new Object(),
622                        new Object(),
623                        new Object(),
624                        new Object(),
625                };
626
627        
628        
629        public boolean isOkToPermanentlyDisable(ShipAPI ship, Object module) {
630                return isOkToPermanentlyDisableStatic(ship, module);
631        }
632        
633        public static boolean isOkToPermanentlyDisableStatic(ShipAPI ship, Object module) {
634                if (module instanceof ShipEngineAPI) {
635                        float fractionIfDisabled = ((ShipEngineAPI) module).getContribution() + ship.getEngineFractionPermanentlyDisabled();
636                        if (fractionIfDisabled > 0.66f) {
637                                return false;
638                        } else {
639                                return true;
640                        }
641                }
642                
643                if (module instanceof WeaponAPI) {
644                        WeaponType type = ((WeaponAPI)module).getType();
645                        if (type == WeaponType.DECORATIVE || type == WeaponType.LAUNCH_BAY || type == WeaponType.SYSTEM) {
646                                return false;
647                        }
648                        
649                        if (ship.getCurrentCR() <= 0) {
650                                return true;
651                        }
652                        
653                        List<Object> usableWeapons = new ArrayList<Object>();
654                        for (WeaponAPI weapon : ship.getAllWeapons()) {
655                                if (weapon.isPermanentlyDisabled()) continue;
656                                if (weapon.isDecorative()) continue;
657                                if (weapon.getSlot().isSystemSlot()) continue;
658                                if (weapon.getSlot().isDecorative()) continue;
659                                if (weapon.getSlot().isStationModule()) continue;
660                                if (weapon.getAmmo() > 0 && (weapon.getMaxAmmo() > 20 || weapon.getSpec().getAmmoPerSecond() > 0)) {
661                                        usableWeapons.add(weapon);
662                                }
663                        }
664                        usableWeapons.remove(module);
665                        
666                        return usableWeapons.size() >= 1;
667                }
668                return false;
669        }
670}
671
672