001package com.fs.starfarer.api.impl.campaign.skills;
002
003import java.awt.Color;
004import java.util.ArrayList;
005import java.util.Collections;
006import java.util.Comparator;
007import java.util.HashSet;
008import java.util.List;
009import java.util.Set;
010
011import com.fs.starfarer.api.GameState;
012import com.fs.starfarer.api.Global;
013import com.fs.starfarer.api.campaign.AICoreOfficerPlugin;
014import com.fs.starfarer.api.campaign.CampaignFleetAPI;
015import com.fs.starfarer.api.campaign.FleetDataAPI;
016import com.fs.starfarer.api.characters.CustomSkillDescription;
017import com.fs.starfarer.api.characters.FleetTotalItem;
018import com.fs.starfarer.api.characters.MutableCharacterStatsAPI;
019import com.fs.starfarer.api.characters.MutableCharacterStatsAPI.SkillLevelAPI;
020import com.fs.starfarer.api.characters.PersonAPI;
021import com.fs.starfarer.api.characters.SkillSpecAPI;
022import com.fs.starfarer.api.combat.MutableShipStatsAPI;
023import com.fs.starfarer.api.combat.ShipHullSpecAPI.ShipTypeHints;
024import com.fs.starfarer.api.combat.ShipVariantAPI;
025import com.fs.starfarer.api.fleet.FleetMemberAPI;
026import com.fs.starfarer.api.impl.campaign.ids.HullMods;
027import com.fs.starfarer.api.impl.campaign.intel.BaseIntelPlugin;
028import com.fs.starfarer.api.impl.hullmods.Automated;
029import com.fs.starfarer.api.ui.BaseTooltipCreator;
030import com.fs.starfarer.api.ui.TooltipMakerAPI;
031import com.fs.starfarer.api.ui.TooltipMakerAPI.TooltipCreator;
032import com.fs.starfarer.api.util.Misc;
033
034public class BaseSkillEffectDescription implements CustomSkillDescription {
035
036        public static float TOOLTIP_WIDTH = 450;
037        
038        public static float FIGHTER_BAYS_THRESHOLD = 8;
039        public static float OP_THRESHOLD = 1000;
040        public static float OP_LOW_THRESHOLD = 500;
041        public static float OP_ALL_LOW_THRESHOLD = 500;
042        public static float OP_ALL_THRESHOLD = 1000;
043        public static float PHASE_OP_THRESHOLD = 150;
044        public static float MILITARIZED_OP_THRESHOLD = 50;
045        
046        public static float AUTOMATED_POINTS_THRESHOLD = 200;
047        
048        public static boolean USE_RECOVERY_COST = true;
049        
050        //public static String RECOVERY_COST = "recovery cost";
051        public static String RECOVERY_COST = "deployment point cost";
052        
053        static {
054                if (USE_RECOVERY_COST) {
055                        // working off 60% of the default battle size of 300 for these, which is 180
056                        OP_THRESHOLD = 240;
057                        OP_LOW_THRESHOLD = 120;
058                        OP_ALL_LOW_THRESHOLD = 120;
059                        OP_ALL_THRESHOLD = 240;
060//                      OP_THRESHOLD = 150;
061//                      OP_LOW_THRESHOLD = 75;
062//                      OP_ALL_LOW_THRESHOLD = 50;
063                        
064                        
065                        PHASE_OP_THRESHOLD = 40;
066                        MILITARIZED_OP_THRESHOLD = 5;
067                        AUTOMATED_POINTS_THRESHOLD = 120;
068                }
069        }
070        
071        
072        public static enum ThresholdBonusType {
073                OP,
074                OP_LOW,
075                OP_ALL,
076                OP_ALL_LOW,
077                MILITARIZED_OP,
078                PHASE_OP,
079                FIGHTER_BAYS,
080                AUTOMATED_POINTS,
081        }
082        
083        protected Color tc;
084        protected Color dtc;
085        protected Color hc;
086        protected Color dhc;
087        protected String indent;
088        protected int alpha;
089
090        public void init(MutableCharacterStatsAPI stats, SkillSpecAPI skill) {
091                indent = BaseIntelPlugin.BULLET;
092                tc = Misc.getTextColor();
093                hc = Misc.getHighlightColor();
094                dhc = Misc.setAlpha(hc, 155);
095                alpha = 255;
096                float level = stats.getSkillLevel(skill.getId());
097                if (level <= 0) {
098                        tc = Misc.getGrayColor();
099                        hc = dhc;
100                        alpha = 155;
101                }
102                dtc = Misc.getGrayColor();
103        }
104        
105        public void initElite(MutableCharacterStatsAPI stats, SkillSpecAPI skill) {
106                indent = BaseIntelPlugin.BULLET;
107                tc = Misc.getTextColor();
108                hc = Misc.getHighlightColor();
109                dhc = Misc.setAlpha(hc, 155);
110                alpha = 255;
111                float level = stats.getSkillLevel(skill.getId());
112                if (level <= 1) {
113                        tc = Misc.getGrayColor();
114                        hc = dhc;
115                        alpha = 155;
116                }
117                dtc = Misc.getGrayColor();
118        }
119        
120        public void addFighterBayThresholdInfo(TooltipMakerAPI info, FleetDataAPI data) {
121                if (isInCampaign()) {
122                        int bays = Math.round(getNumFighterBays(data));
123                        String baysStr = "fighter bays";
124                        if (bays == 1) baysStr = "fighter bay";
125                        //baysStr = "";
126                        info.addPara(indent + "Maximum at %s or less fighter bays in fleet, your fleet has %s " + baysStr,
127                                        0f, tc, hc, 
128                                        "" + (int) FIGHTER_BAYS_THRESHOLD,
129                                        "" + bays);
130                } else {
131                        info.addPara(indent + "Maximum at %s or less fighter bays in fleet",
132                                        0f, tc, hc, 
133                                        "" + (int) FIGHTER_BAYS_THRESHOLD);
134                }
135        }
136        
137        public void addOPThresholdInfo(TooltipMakerAPI info, FleetDataAPI data, MutableCharacterStatsAPI cStats) {
138                addOPThresholdInfo(info, data, cStats, OP_THRESHOLD);
139        }
140        public void addOPThresholdInfo(TooltipMakerAPI info, FleetDataAPI data, MutableCharacterStatsAPI cStats, float threshold) {
141                if (USE_RECOVERY_COST) {
142                        if (isInCampaign()) {
143                                float op = getTotalCombatOP(data, cStats);
144                                info.addPara(indent + "Maximum at %s or less total combat ship " + RECOVERY_COST + ", your fleet's total is %s",
145                                                0f, tc, hc, 
146                                                "" + (int) threshold,
147                                                "" + (int)Math.round(op));
148                        } else {
149                                info.addPara(indent + "Maximum at %s or less total combat ship " + RECOVERY_COST + " for fleet",
150                                                0f, tc, hc, 
151                                                "" + (int) threshold);
152                        }
153                        return;
154                }
155                if (isInCampaign()) {
156                        float op = getTotalCombatOP(data, cStats);
157                        String opStr = "points";
158                        if (op == 1) opStr = "point";
159                        info.addPara(indent + "Maximum at %s or less total combat ship ordnance points in fleet, your fleet has %s " + opStr,
160                                        0f, tc, hc, 
161                                        "" + (int) threshold,
162                                        "" + (int)Math.round(op));
163                } else {
164                        info.addPara(indent + "Maximum at %s or less total combat ship ordnance points in fleet",
165                                        0f, tc, hc, 
166                                        "" + (int) threshold);
167                }
168        }
169        
170        public void addOPThresholdAll(TooltipMakerAPI info, FleetDataAPI data, MutableCharacterStatsAPI cStats, float threshold) {
171                if (USE_RECOVERY_COST) {
172                        if (isInCampaign()) {
173                                float op = getTotalOP(data, cStats);
174                                info.addPara(indent + "Maximum at %s or less total " + RECOVERY_COST + ", your fleet's total is %s",
175                                                0f, tc, hc, 
176                                                "" + (int) threshold,
177                                                "" + (int)Math.round(op));
178                        } else {
179                                info.addPara(indent + "Maximum at %s or less total " + RECOVERY_COST + " for fleet",
180                                                0f, tc, hc, 
181                                                "" + (int) threshold);
182                        }
183                        return;
184                }
185                if (isInCampaign()) {
186                        float op = getTotalOP(data, cStats);
187//                      String opStr = "combat ship ordnance points";
188//                      if (op == 1) opStr = "combat ship ordnance point";
189                        String opStr = "points";
190                        if (op == 1) opStr = "point";
191                        info.addPara(indent + "Maximum at %s or less total ordnance points in fleet, your fleet has %s " + opStr,
192                                        0f, tc, hc, 
193                                        "" + (int) threshold,
194                                        "" + (int)Math.round(op));
195                } else {
196                        info.addPara(indent + "Maximum at %s or less total ordnance points in fleet",
197                                        0f, tc, hc, 
198                                        "" + (int) threshold);
199                }
200        }
201        
202        public void addPhaseOPThresholdInfo(TooltipMakerAPI info, FleetDataAPI data, MutableCharacterStatsAPI cStats) {
203                if (USE_RECOVERY_COST) {
204                        if (isInCampaign()) {
205                                float op = getPhaseOP(data, cStats);
206                                info.addPara(indent + "Maximum at %s or less total combat phase ship " + RECOVERY_COST + ", your fleet's total is %s",
207                                                0f, tc, hc, 
208                                                "" + (int) PHASE_OP_THRESHOLD,
209                                                "" + (int)Math.round(op));
210                        } else {
211                                info.addPara(indent + "Maximum at %s or less total combat phase ship " + RECOVERY_COST + " for fleet",
212                                                0f, tc, hc, 
213                                                "" + (int) PHASE_OP_THRESHOLD);
214                        }
215                        
216                        return;
217                }
218                if (isInCampaign()) {
219                        float op = getPhaseOP(data, cStats);
220                        String opStr = "points";
221                        if (op == 1) opStr = "point";
222                        info.addPara(indent + "Maximum at %s or less total combat phase ship ordnance points in fleet, your fleet has %s " + opStr,
223                                        0f, tc, hc, 
224                                        "" + (int) PHASE_OP_THRESHOLD,
225                                        "" + (int)Math.round(op));
226                } else {
227                        info.addPara(indent + "Maximum at %s or less total combat phase ship ordnance points in fleet",
228                                        0f, tc, hc, 
229                                        "" + (int) PHASE_OP_THRESHOLD);
230                }
231        }
232        
233        public void addAutomatedThresholdInfo(TooltipMakerAPI info, FleetDataAPI data, MutableCharacterStatsAPI cStats) {
234                if (USE_RECOVERY_COST) {
235                        if (isInCampaign()) {
236                                float op = getAutomatedPoints(data, cStats);
237                                info.addPara(indent + "Maximum at %s or less total automated ship points*, your fleet's total is %s ",
238                                                0f, tc, hc, 
239                                                "" + (int) AUTOMATED_POINTS_THRESHOLD,
240                                                "" + (int)Math.round(op));
241                        } else {
242                                info.addPara(indent + "Maximum at %s or less total automated ship points* for fleet",
243                                                0f, tc, hc, 
244                                                "" + (int) AUTOMATED_POINTS_THRESHOLD);
245                        }
246                        return;
247                }
248                if (isInCampaign()) {
249                        float op = getAutomatedPoints(data, cStats);
250                        String opStr = "points";
251                        if (op == 1) opStr = "point";
252                        info.addPara(indent + "Maximum at %s or less total automated ship points* in fleet, your fleet has %s " + opStr,
253                                        0f, tc, hc, 
254                                        "" + (int) AUTOMATED_POINTS_THRESHOLD,
255                                        "" + (int)Math.round(op));
256                } else {
257                        info.addPara(indent + "Maximum at %s or less total automated ship points* in fleet",
258                                        0f, tc, hc, 
259                                        "" + (int) AUTOMATED_POINTS_THRESHOLD);
260                }
261        }
262        
263        public void addMilitarizedOPThresholdInfo(TooltipMakerAPI info, FleetDataAPI data, MutableCharacterStatsAPI cStats) {
264                if (USE_RECOVERY_COST) {
265                        if (isInCampaign()) {
266                                float op = getMilitarizedOP(data, cStats);
267                                info.addPara(indent + "Maximum at %s or less total " + RECOVERY_COST + " for ships with Militarized Subsystems, your fleet's total is %s",
268                                                0f, tc, hc, 
269                                                "" + (int) MILITARIZED_OP_THRESHOLD,
270                                                "" + (int)Math.round(op));
271                        } else {
272                                info.addPara(indent + "Maximum at %s or less total " + RECOVERY_COST + " for ships with Militarized Subsystems for fleet",
273                                                0f, tc, hc, 
274                                                "" + (int) MILITARIZED_OP_THRESHOLD);
275                        }
276                        return;
277                } else {
278                        if (isInCampaign()) {
279                                float op = getMilitarizedOP(data, cStats);
280                                String opStr = "points";
281                                if (op == 1) opStr = "point";
282                                info.addPara(indent + "Maximum at %s or less total ordnance points for ships with Militarized Subsystems, your fleet has %s " + opStr,
283                                                0f, tc, hc, 
284                                                "" + (int) MILITARIZED_OP_THRESHOLD,
285                                                "" + (int)Math.round(op));
286                        } else {
287                                info.addPara(indent + "Maximum at %s or less total ordnance points for ships with Militarized Subsystems",
288                                                0f, tc, hc, 
289                                                "" + (int) MILITARIZED_OP_THRESHOLD);
290                        }
291                }
292        }
293        
294        protected float computeAndCacheThresholdBonus(MutableShipStatsAPI stats,
295                        String key, float maxBonus, ThresholdBonusType type) {
296                FleetDataAPI data = getFleetData(stats);
297                MutableCharacterStatsAPI cStats = getCommanderStats(stats);
298                return computeAndCacheThresholdBonus(data, cStats, key, maxBonus, type);
299        }
300        protected float computeAndCacheThresholdBonus(FleetDataAPI data, MutableCharacterStatsAPI cStats,
301                        String key, float maxBonus, ThresholdBonusType type) {
302//              if (key.equals("pc_peak")) {
303//                      System.out.println("efwfwefwe");
304//              }
305                if (data == null) return maxBonus;
306                if (cStats.getFleet() == null) return maxBonus;
307
308                Float bonus = (Float) data.getCacheClearedOnSync().get(key);
309                if (bonus != null) return bonus;
310
311                float currValue = 0f;
312                float threshold = 1f;
313                
314                if (type == ThresholdBonusType.FIGHTER_BAYS) {
315                        currValue = getNumFighterBays(data);
316                        threshold = FIGHTER_BAYS_THRESHOLD;
317                } else if (type == ThresholdBonusType.OP) {
318                        currValue = getTotalCombatOP(data, cStats);
319                        threshold = OP_THRESHOLD;
320                } else if (type == ThresholdBonusType.OP_LOW) {
321                        currValue = getTotalCombatOP(data, cStats);
322                        threshold = OP_LOW_THRESHOLD;
323                } else if (type == ThresholdBonusType.OP_ALL_LOW) {
324                        currValue = getTotalOP(data, cStats);
325                        threshold = OP_ALL_LOW_THRESHOLD;
326                } else if (type == ThresholdBonusType.OP_ALL) {
327                        currValue = getTotalOP(data, cStats);
328                        threshold = OP_ALL_THRESHOLD;
329                } else if (type == ThresholdBonusType.MILITARIZED_OP) {
330                        currValue = getMilitarizedOP(data, cStats);
331                        threshold = MILITARIZED_OP_THRESHOLD;
332                } else if (type == ThresholdBonusType.PHASE_OP) {
333                        currValue = getPhaseOP(data, cStats);
334                        threshold = PHASE_OP_THRESHOLD;
335                } else if (type == ThresholdBonusType.AUTOMATED_POINTS) {
336                        currValue = getAutomatedPoints(data, cStats);
337                        threshold = AUTOMATED_POINTS_THRESHOLD;
338                }
339                
340                bonus = getThresholdBasedRoundedBonus(maxBonus, currValue, threshold);
341
342                data.getCacheClearedOnSync().put(key, bonus);
343                return bonus;
344        }
345//      refitData.addOrUpdate(0, 0, indent + "Combat ship ordnance points", "" + (int)totalOP);
346//      refitData.addOrUpdate(0, 1, indent + "Phase ship ordnance points", "" + (int)phaseOP);
347//      refitData.addOrUpdate(0, 2, indent + "Fighter bays", "" + (int)bays);
348//      float totalOP = BaseSkillEffectDescription.getTotalOP(fleet.getFleetData(), Global.getSector().getPlayerStats());
349//      float phaseOP = BaseSkillEffectDescription.getPhaseOP(fleet.getFleetData(), Global.getSector().getPlayerStats());
350        
351        public FleetTotalItem getOPTotal() {
352                final CampaignFleetAPI fleet = Global.getSector().getPlayerFleet();
353                final MutableCharacterStatsAPI stats = Global.getSector().getPlayerStats();
354                FleetTotalItem item = new FleetTotalItem();
355                item.label = "Total ordnance points";
356                if (USE_RECOVERY_COST) {
357                        item.label = "All ships";
358                }
359                item.value = "" + (int) getTotalOP(fleet.getFleetData(), stats);
360                item.sortOrder = 50;
361                
362                item.tooltipCreator = getTooltipCreator(new TooltipCreatorSkillEffectPlugin() {
363                        public void addDescription(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) {
364                                float opad = 10f;
365                                tooltip.addPara("The total deployment points of all the ships in your fleet.", 0f);
366                        }
367                        public List<FleetMemberPointContrib> getContributors() {
368                                return getTotalOPDetail(fleet.getFleetData(), stats);
369                        }
370                });
371                
372                return item;
373        }
374        
375        public FleetTotalItem getCombatOPTotal() {
376                final CampaignFleetAPI fleet = Global.getSector().getPlayerFleet();
377                final MutableCharacterStatsAPI stats = Global.getSector().getPlayerStats();
378                FleetTotalItem item = new FleetTotalItem();
379                item.label = "Combat ship ordnance points";
380                if (USE_RECOVERY_COST) {
381                        item.label = "Combat ships";
382                }
383                item.value = "" + (int) BaseSkillEffectDescription.getTotalCombatOP(fleet.getFleetData(), stats);
384                item.sortOrder = 100;
385                
386                item.tooltipCreator = getTooltipCreator(new TooltipCreatorSkillEffectPlugin() {
387                        public void addDescription(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) {
388                                float opad = 10f;
389                                tooltip.addPara("The total deployment points of all the combat ships in your fleet.", 0f);
390                        }
391                        public List<FleetMemberPointContrib> getContributors() {
392                                return getTotalCombatOPDetail(fleet.getFleetData(), stats);
393                        }
394                });
395                
396                return item;
397        }
398        
399        public FleetTotalItem getAutomatedPointsTotal() {
400                final CampaignFleetAPI fleet = Global.getSector().getPlayerFleet();
401                final MutableCharacterStatsAPI stats = Global.getSector().getPlayerStats();
402                FleetTotalItem item = new FleetTotalItem();
403                item.label = "Automated ships";
404                item.value = "" + (int) BaseSkillEffectDescription.getAutomatedPoints(fleet.getFleetData(), stats);
405                item.sortOrder = 350;
406                
407                item.tooltipCreator = getTooltipCreator(new TooltipCreatorSkillEffectPlugin() {
408                        public void addDescription(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) {
409                                float opad = 10f;
410                                tooltip.addPara("The total deployment points of all the automated ships in your fleet, "
411                                                + "with additional points for ships controlled by AI cores.", 0f);
412                        }
413                        public List<FleetMemberPointContrib> getContributors() {
414                                return getAutomatedPointsDetail(fleet.getFleetData(), stats);
415                        }
416                });
417                
418                return item;
419        }
420        
421        public FleetTotalItem getPhaseOPTotal() {
422                final CampaignFleetAPI fleet = Global.getSector().getPlayerFleet();
423                final MutableCharacterStatsAPI stats = Global.getSector().getPlayerStats();
424                FleetTotalItem item = new FleetTotalItem();
425                item.label = "Phase ship ordnance points";
426                if (USE_RECOVERY_COST) {
427                        item.label = "Phase ships";
428                }
429                item.value = "" + (int) BaseSkillEffectDescription.getPhaseOP(fleet.getFleetData(), stats);
430                item.sortOrder = 200;
431                
432                item.tooltipCreator = getTooltipCreator(new TooltipCreatorSkillEffectPlugin() {
433                        public void addDescription(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) {
434                                float opad = 10f;
435                                tooltip.addPara("The total deployment points of all the non-civilian phase ships in your fleet.", 0f);
436                        }
437                        public List<FleetMemberPointContrib> getContributors() {
438                                return getPhaseOPDetail(fleet.getFleetData(), stats);
439                        }
440                });
441                return item;
442        }
443        
444        public FleetTotalItem getMilitarizedOPTotal() {
445                final CampaignFleetAPI fleet = Global.getSector().getPlayerFleet();
446                final MutableCharacterStatsAPI stats = Global.getSector().getPlayerStats();
447                FleetTotalItem item = new FleetTotalItem();
448                item.label = "Militarized ship ordnance points";
449                if (USE_RECOVERY_COST) {
450                        item.label = "Militarized ships";
451                }
452                item.value = "" + (int) BaseSkillEffectDescription.getMilitarizedOP(fleet.getFleetData(), stats);
453                item.sortOrder = 300;
454                
455                item.tooltipCreator = getTooltipCreator(new TooltipCreatorSkillEffectPlugin() {
456                        public void addDescription(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) {
457                                float opad = 10f;
458                                tooltip.addPara("The total deployment points of all the ships in your fleet that have "
459                                                + "the \"Militarized Subsystems\" hullmod.", 0f);
460                        }
461                        public List<FleetMemberPointContrib> getContributors() {
462                                return getMilitarizedOPDetail(fleet.getFleetData(), stats);
463                        }
464                });
465                
466                return item;
467        }
468        
469        public FleetTotalItem getFighterBaysTotal() {
470                final CampaignFleetAPI fleet = Global.getSector().getPlayerFleet();
471                FleetTotalItem item = new FleetTotalItem();
472                item.label = "Fighter bays";
473                item.value = "" + (int) BaseSkillEffectDescription.getNumFighterBays(fleet.getFleetData());
474                item.sortOrder = 400;
475                
476                item.tooltipCreator = getTooltipCreator(new TooltipCreatorSkillEffectPlugin() {
477                        public void addDescription(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) {
478                                float opad = 10f;
479                                tooltip.addPara("The total number of fighter bays in your fleet. Both empty and filled "
480                                                + "fighter bays are counted. Built-in fighter bays can be removed from a ship by "
481                                                + "installing the \"Converted Fighter Bay\" hullmod.", 0f);
482                        }
483                        public List<FleetMemberPointContrib> getContributors() {
484                                return getNumFighterBaysDetail(fleet.getFleetData());
485                        }
486                });
487                
488                return item;
489        }
490        
491        public interface TooltipCreatorSkillEffectPlugin {
492                void addDescription(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam);
493                List<FleetMemberPointContrib> getContributors();
494        }
495        public TooltipCreator getTooltipCreator(final TooltipCreatorSkillEffectPlugin plugin) {
496                return new BaseTooltipCreator() {
497                        public float getTooltipWidth(Object tooltipParam) {
498                                return TOOLTIP_WIDTH;
499                        }
500                        @SuppressWarnings("unchecked")
501                        public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) {
502                                float pad = 3f;
503                                float opad = 10f;
504                                
505                                plugin.addDescription(tooltip, expanded, tooltipParam);
506                                
507                                tooltip.addPara("Certain skills will have reduced effects if this value exceeds a "
508                                                + "skill-specific threshold; see the specific skill for details. Mothballed "
509                                                + "ships are not counted towards the total.", opad);
510                                
511                                tooltip.addPara("Skills using this value:", opad);
512                                tooltip.setBulletedListMode(BaseIntelPlugin.INDENT);
513                                List<SkillLevelAPI> skills = (List<SkillLevelAPI>) tooltipParam;
514                                Set<SkillLevelAPI> seen = new HashSet<MutableCharacterStatsAPI.SkillLevelAPI>();
515                                
516                                float initPad = opad;
517                                //initPad = pad;
518                                for (SkillLevelAPI skill : skills) {
519                                        if (seen.contains(skill)) continue;
520                                        seen.add(skill);
521                                        String label = skill.getSkill().getName() + " (" + skill.getSkill().getGoverningAptitudeName() + ")";
522                                        label = skill.getSkill().getGoverningAptitudeName() + " - " + skill.getSkill().getName();
523                                        tooltip.addPara(label, 
524                                                        skill.getSkill().getGoverningAptitudeColor(), initPad);
525                                        initPad = pad;
526                                }
527                                tooltip.setBulletedListMode(null);
528                                
529                                List<FleetMemberPointContrib> members = plugin.getContributors();
530                                Collections.sort(members, new Comparator<FleetMemberPointContrib>() {
531                                        public int compare(FleetMemberPointContrib o1, FleetMemberPointContrib o2) {
532                                                return o2.points - o1.points;
533                                        }
534                                });
535                                
536                                tooltip.addPara("Ships contributing to this value:", opad);
537                                initPad = opad;
538                                //initPad = pad;
539                                if (members.isEmpty()) {
540                                        tooltip.setBulletedListMode(BaseIntelPlugin.INDENT);
541                                        tooltip.addPara("None", initPad);
542                                        tooltip.setBulletedListMode(null);
543                                }
544                                float gridWidth = 450f;
545                                //tooltip.beginGrid(gridWidth, 1);
546                                tooltip.beginGridFlipped(gridWidth, 1, 40f, opad);
547                                int i = 0;
548                                int max = 20;
549                                if (members.size() == max + 1) {
550                                        max = members.size();
551                                }
552                                for (FleetMemberPointContrib member : members) {
553//                                      String label = tooltip.shortenString(BaseIntelPlugin.INDENT + getValueLabelForMember(member.member),
554//                                                                                                      gridWidth);
555                                        String label = tooltip.shortenString(getValueLabelForMember(member.member),
556                                                        gridWidth - 60f);
557                                        tooltip.addToGrid(0, i++, label, "+" + member.points);
558                                        if (i >= max) break;
559                                }
560                                tooltip.addGrid(opad);
561                                if (members.size() > max) {
562                                        tooltip.addPara("And %s other ships with a smaller contribution.", opad, 
563                                                        Misc.getHighlightColor(), "" + (members.size() - max));
564                                }
565                                
566                        }
567                };
568        }
569        
570        public static String getValueLabelForMember(FleetMemberAPI member) {
571                String str = "";
572                if (!member.isFighterWing()) {
573                        str += member.getShipName() + ", ";
574                        str += member.getHullSpec().getHullNameWithDashClass();
575                } else {
576                        str += member.getVariant().getFullDesignationWithHullName();
577                }
578                if (member.isMothballed()) {
579                        str += " (mothballed)";
580                }
581                return str;
582        }
583        
584        
585        public float getThresholdBasedBonus(float maxBonus, float value, float threshold) {
586                float bonus = maxBonus * threshold / Math.max(value, threshold);
587                return bonus;
588        }
589        public float getThresholdBasedRoundedBonus(float maxBonus, float value, float threshold) {
590                float bonus = maxBonus * threshold / Math.max(value, threshold);
591                if (bonus > 0 && bonus < 1) bonus = 1;
592                if (maxBonus > 1f) {
593                        if (bonus < maxBonus) {
594                                bonus = Math.min(bonus, maxBonus - 1f);
595                        }
596                        bonus = (float) Math.round(bonus);
597                }
598                return bonus;
599        }
600        
601        public static boolean isMilitarized(MutableShipStatsAPI stats) {
602                if (stats == null || stats.getFleetMember() == null) return false;
603                return isMilitarized(stats.getFleetMember());
604        }
605        public static boolean isMilitarized(FleetMemberAPI member) {
606                if (member == null) return false;
607                MutableShipStatsAPI stats = member.getStats();
608                return stats != null && stats.getVariant() != null && 
609                           stats.getVariant().hasHullMod(HullMods.MILITARIZED_SUBSYSTEMS);
610        }
611        
612        public static boolean isCivilian(MutableShipStatsAPI stats) {
613                if (stats == null || stats.getFleetMember() == null) return false;
614                return isCivilian(stats.getFleetMember());
615        }
616        
617        public static boolean isCivilian(FleetMemberAPI member) {
618                if (member == null) return false;
619                MutableShipStatsAPI stats = member.getStats();
620                return stats != null && stats.getVariant() != null && 
621                                ((stats.getVariant().hasHullMod(HullMods.CIVGRADE) && 
622                                !stats.getVariant().hasHullMod(HullMods.MILITARIZED_SUBSYSTEMS)) ||
623                                (!stats.getVariant().hasHullMod(HullMods.CIVGRADE) &&
624                                                stats.getVariant().getHullSpec().getHints().contains(ShipTypeHints.CIVILIAN)));// &&
625                                //!stats.getVariant().getHullSpec().getHints().contains(ShipTypeHints.CARRIER);
626        }
627        
628        public static boolean hasFighterBays(MutableShipStatsAPI stats) {
629                if (stats == null || stats.getFleetMember() == null) return false;
630                return hasFighterBays(stats.getFleetMember());
631        }
632        
633        public static boolean hasFighterBays(FleetMemberAPI member) {
634                if (member == null) return false;
635                MutableShipStatsAPI stats = member.getStats();
636                return stats != null && stats.getNumFighterBays().getModifiedInt() > 0;
637        }
638        
639//      public static float getTotalOP(MutableShipStatsAPI stats) {
640//              FleetDataAPI data = getFleetData(stats);
641//              MutableCharacterStatsAPI cStats = getCommanderStats(stats);
642//              if (data == null) return 0;
643//              return getTotalOP(data, cStats);
644//      }
645        
646        protected static float getPoints(FleetMemberAPI member, MutableCharacterStatsAPI stats) {
647                if (USE_RECOVERY_COST) {
648                        return member.getDeploymentPointsCost();
649                }
650                return member.getHullSpec().getOrdnancePoints(stats);
651        }
652        
653        public static class FleetMemberPointContrib {
654                public FleetMemberAPI member;
655                public int points;
656                public FleetMemberPointContrib(FleetMemberAPI member, int points) {
657                        super();
658                        this.member = member;
659                        this.points = points;
660                }
661        }
662        
663        
664        public static float getTotalOP(FleetDataAPI data, MutableCharacterStatsAPI stats) {
665                float op = 0;
666                for (FleetMemberAPI curr : data.getMembersListCopy()) {
667                        if (curr.isMothballed()) continue;
668                        op += getPoints(curr, stats);
669                }
670                return Math.round(op);
671        }
672        
673        public static List<FleetMemberPointContrib> getTotalOPDetail(FleetDataAPI data, MutableCharacterStatsAPI stats) {
674                List<FleetMemberPointContrib> result = new ArrayList<BaseSkillEffectDescription.FleetMemberPointContrib>();
675                for (FleetMemberAPI curr : data.getMembersListCopy()) {
676                        if (curr.isMothballed()) continue;
677                        int pts = (int) Math.round(getPoints(curr, stats));
678                        result.add(new FleetMemberPointContrib(curr, pts));
679                }
680                return result;
681        }
682        
683        public static float getTotalCombatOP(FleetDataAPI data, MutableCharacterStatsAPI stats) {
684                float op = 0;
685                for (FleetMemberAPI curr : data.getMembersListCopy()) {
686                        if (curr.isMothballed()) continue;
687                        if (isCivilian(curr)) continue;
688                        op += getPoints(curr, stats);
689                }
690                return Math.round(op);
691        }
692        
693        public static List<FleetMemberPointContrib> getTotalCombatOPDetail(FleetDataAPI data, MutableCharacterStatsAPI stats) {
694                List<FleetMemberPointContrib> result = new ArrayList<BaseSkillEffectDescription.FleetMemberPointContrib>();
695                for (FleetMemberAPI curr : data.getMembersListCopy()) {
696                        if (curr.isMothballed()) continue;
697                        if (isCivilian(curr)) continue;
698                        int pts = (int) Math.round(getPoints(curr, stats));
699                        result.add(new FleetMemberPointContrib(curr, pts));
700                }
701                return result;
702        }
703        
704        public static float getPhaseOP(FleetDataAPI data, MutableCharacterStatsAPI stats) {
705                float op = 0;
706                for (FleetMemberAPI curr : data.getMembersListCopy()) {
707                        if (curr.isMothballed()) continue;
708                        if (curr.isPhaseShip()) {
709                                if (isCivilian(curr)) continue;
710                                op += getPoints(curr, stats);
711                        }
712                }
713                return Math.round(op);
714        }
715        
716        public static List<FleetMemberPointContrib> getPhaseOPDetail(FleetDataAPI data, MutableCharacterStatsAPI stats) {
717                List<FleetMemberPointContrib> result = new ArrayList<BaseSkillEffectDescription.FleetMemberPointContrib>();
718                for (FleetMemberAPI curr : data.getMembersListCopy()) {
719                        if (curr.isMothballed()) continue;
720                        if (curr.isPhaseShip()) {
721                                if (isCivilian(curr)) continue;
722                                int pts = (int) Math.round(getPoints(curr, stats));
723                                result.add(new FleetMemberPointContrib(curr, pts));
724                        }
725                }
726                return result;
727        }
728        
729        public static float getMilitarizedOP(FleetDataAPI data, MutableCharacterStatsAPI stats) {
730                float op = 0;
731                for (FleetMemberAPI curr : data.getMembersListCopy()) {
732                        if (curr.isMothballed()) continue;
733                        if (!isMilitarized(curr)) continue;
734                        op += getPoints(curr, stats);
735                }
736                return Math.round(op);
737        }
738        
739        public static List<FleetMemberPointContrib> getMilitarizedOPDetail(FleetDataAPI data, MutableCharacterStatsAPI stats) {
740                List<FleetMemberPointContrib> result = new ArrayList<BaseSkillEffectDescription.FleetMemberPointContrib>();
741                for (FleetMemberAPI curr : data.getMembersListCopy()) {
742                        if (curr.isMothballed()) continue;
743                        if (!isMilitarized(curr)) continue;
744                        int pts = (int) Math.round(getPoints(curr, stats));
745                        result.add(new FleetMemberPointContrib(curr, pts));
746                }
747                return result;
748        }
749        
750        public static float getNumFighterBays(FleetDataAPI data) {
751                if (data ==  null) return FIGHTER_BAYS_THRESHOLD;
752                float bays = 0;
753                for (FleetMemberAPI curr : data.getMembersListCopy()) {
754                        if (curr.isMothballed()) continue;
755                        bays += getNumBaysIncludingModules(curr);
756                        //bays += curr.getNumFlightDecks();
757                }
758                return bays;
759        }
760        
761        public static float getNumBaysIncludingModules(FleetMemberAPI member) {
762                float bays = 0f;
763                bays += member.getNumFlightDecks();
764                if (member.getVariant().getModuleSlots() != null) {
765                        for (String slotId : member.getVariant().getModuleSlots()) {
766                                if (slotId == null) continue;
767                                ShipVariantAPI variant = member.getVariant().getModuleVariant(slotId);
768                                if (variant == null) continue;
769                                bays += variant.getHullSpec().getFighterBays();
770                        }
771                }
772                return bays;
773        }
774        
775        public static List<FleetMemberPointContrib> getNumFighterBaysDetail(FleetDataAPI data) {
776                List<FleetMemberPointContrib> result = new ArrayList<BaseSkillEffectDescription.FleetMemberPointContrib>();
777                for (FleetMemberAPI curr : data.getMembersListCopy()) {
778                        if (curr.isMothballed()) continue;
779                        //int pts = curr.getNumFlightDecks();
780                        int pts = (int)Math.round(getNumBaysIncludingModules(curr));
781                        if (pts <= 0) continue;
782                        result.add(new FleetMemberPointContrib(curr, pts));
783                }
784                return result;
785        }
786        
787        public static float getAutomatedPoints(FleetDataAPI data, MutableCharacterStatsAPI stats) {
788                float points = 0;
789                for (FleetMemberAPI curr : data.getMembersListCopy()) {
790                        if (curr.isMothballed()) continue;
791                        if (!Misc.isAutomated(curr)) continue;
792                        if (Automated.isAutomatedNoPenalty(curr)) continue;
793                        float mult = 1f;
794                        //if (curr.getCaptain().isAICore()) {
795                                points += curr.getCaptain().getMemoryWithoutUpdate().getFloat(AICoreOfficerPlugin.AUTOMATED_POINTS_VALUE);
796                                mult = curr.getCaptain().getMemoryWithoutUpdate().getFloat(AICoreOfficerPlugin.AUTOMATED_POINTS_MULT);
797                                if (mult == 0) mult = 1;
798                        //}
799                        points += Math.round(getPoints(curr, stats) * mult);
800                }
801                return Math.round(points);
802        }
803        
804        public static List<FleetMemberPointContrib> getAutomatedPointsDetail(FleetDataAPI data, MutableCharacterStatsAPI stats) {
805                List<FleetMemberPointContrib> result = new ArrayList<BaseSkillEffectDescription.FleetMemberPointContrib>();
806                for (FleetMemberAPI curr : data.getMembersListCopy()) {
807                        if (curr.isMothballed()) continue;
808                        if (!Misc.isAutomated(curr)) continue;
809                        if (Automated.isAutomatedNoPenalty(curr)) continue;
810                        float mult = 1f;
811                        int pts = (int) Math.round(getPoints(curr, stats));
812                        //if (curr.getCaptain().isAICore()) {
813                                pts += (int) Math.round(curr.getCaptain().getMemoryWithoutUpdate().getFloat(AICoreOfficerPlugin.AUTOMATED_POINTS_VALUE));
814                                mult = curr.getCaptain().getMemoryWithoutUpdate().getFloat(AICoreOfficerPlugin.AUTOMATED_POINTS_MULT);
815                                if (mult == 0) mult = 1;
816                        //}
817                        result.add(new FleetMemberPointContrib(curr, Math.round(pts * mult)));
818                }
819                return result;
820        }
821        
822        public static boolean isInCampaign() {
823                return Global.getCurrentState() == GameState.CAMPAIGN && 
824                           Global.getSector() != null && 
825                           Global.getSector().getPlayerFleet() != null;
826        }
827
828        public static MutableCharacterStatsAPI getCommanderStats(MutableShipStatsAPI stats) {
829                if (stats == null) {
830                        if (isInCampaign()) {
831                                return Global.getSector().getPlayerStats();
832                        }
833                        return null;
834                }
835                
836                FleetMemberAPI member = stats.getFleetMember();
837                if (member == null) return null;
838                PersonAPI commander = member.getFleetCommanderForStats();
839                if (commander == null) {
840                        boolean orig = false;
841                        if (member.getFleetData() != null) {
842                                orig = member.getFleetData().isForceNoSync();
843                                member.getFleetData().setForceNoSync(true);
844                        }
845                        commander = member.getFleetCommander();
846                        if (member.getFleetData() != null) {
847                                member.getFleetData().setForceNoSync(orig);
848                        }
849                }
850                if (commander != null) {
851                        return commander.getStats();
852                }
853                return null;
854        }
855        
856        public static FleetDataAPI getFleetData(MutableShipStatsAPI stats) {
857                if (stats == null) {
858                        if (isInCampaign()) {
859                                return Global.getSector().getPlayerFleet().getFleetData();
860                        }
861                        return null;
862                }
863                FleetMemberAPI member = stats.getFleetMember();
864                if (member == null) return null;
865                FleetDataAPI data = member.getFleetDataForStats();
866                if (data == null) data = member.getFleetData();
867                return data;
868        }
869        
870        
871        public boolean hasCustomDescription() {
872                return true;
873        }
874        
875        public void createCustomDescription(MutableCharacterStatsAPI stats, SkillSpecAPI skill, 
876                                                                                TooltipMakerAPI info, float width) {
877        }
878
879        public String getEffectDescription(float level) {
880                return null;
881        }
882
883        public String getEffectPerLevelDescription() {
884                return null;
885        }
886
887        public ScopeDescription getScopeDescription() {
888                return null;
889        }
890
891}