001package com.fs.starfarer.api.impl.hullmods;
002
003import java.util.ArrayList;
004import java.util.LinkedHashSet;
005import java.util.List;
006import java.util.Set;
007
008import java.awt.Color;
009
010import com.fs.starfarer.api.Global;
011import com.fs.starfarer.api.combat.BaseHullMod;
012import com.fs.starfarer.api.combat.MutableShipStatsAPI;
013import com.fs.starfarer.api.combat.ShipAPI;
014import com.fs.starfarer.api.combat.ShipAPI.HullSize;
015import com.fs.starfarer.api.graphics.SpriteAPI;
016import com.fs.starfarer.api.impl.campaign.ids.HullMods;
017import com.fs.starfarer.api.impl.campaign.ids.Stats;
018import com.fs.starfarer.api.impl.campaign.ids.Strings;
019import com.fs.starfarer.api.loading.FighterWingSpecAPI;
020import com.fs.starfarer.api.ui.Alignment;
021import com.fs.starfarer.api.ui.TooltipMakerAPI;
022import com.fs.starfarer.api.util.Misc;
023
024public class ConvertedHangar extends BaseHullMod {
025
026        public static float FIGHTER_OP_PER_DP = 5;
027        public static int MIN_DP = 1;
028        public static float REPLACEMENT_TIME_MULT = 1.5f;
029        public static int CREW_REQ = 20;
030        
031        //public static float EXTRA_REARM_TIME = 5f;
032        public static float REARM_TIME_FRACTION = 0.4f;
033        
034        public static float SMOD_CRUISER = 10f;
035        public static float SMOD_CAPITAL = 25f;
036        
037        public static float CR_THRESHOLD_UNINSTALLABLE = 70;
038        
039        
040        
041        //public static final int CARGO_REQ = 80;
042//      public static final int ALL_FIGHTER_COST_PERCENT = 50;
043//      public static final int BOMBER_COST_PERCENT = 100;
044        
045//      private static Map mag = new HashMap();
046//      static {
047//              mag.put(HullSize.FRIGATE, 0f);
048//              mag.put(HullSize.DESTROYER, 75f);
049//              mag.put(HullSize.CRUISER, 50f);
050//              mag.put(HullSize.CAPITAL_SHIP, 25f);
051//      }
052        
053        public static int computeDPModifier(float fighterOPCost) {
054                int mod = (int) Math.ceil(fighterOPCost / FIGHTER_OP_PER_DP);
055                if (mod < MIN_DP) mod = MIN_DP;
056                return mod;
057        }
058        
059        public static float getFighterOPCost(MutableShipStatsAPI stats) {
060                float cost = 0;
061                for (String wingId : getFighterWings(stats)) {
062                        FighterWingSpecAPI spec = Global.getSettings().getFighterWingSpec(wingId);
063                        cost += spec.getOpCost(stats);
064                }
065                return cost;
066        }
067        
068        public static List<String> getFighterWings(MutableShipStatsAPI stats) {
069                if (stats.getVariant() != null) {
070                        int baseBays = (int) Math.round(stats.getNumFighterBays().getBaseValue());
071                        if (baseBays <= 0) {
072                                return stats.getVariant().getFittedWings();
073                        } else {
074                                List<String> result = new ArrayList<>();
075                                for (String wingId : stats.getVariant().getFittedWings()) {
076                                        if (baseBays > 0) {
077                                                baseBays--;
078                                                continue;
079                                        }
080                                        result.add(wingId);
081                                }
082                                return result;
083                        }
084                }
085                return new ArrayList<String>();
086//              if (stats.getEntity() instanceof ShipAPI) {
087//                      ShipAPI ship = (ShipAPI) stats.getEntity();
088//              } else {
089//                      FleetMemberAPI member = stats.getFleetMember();
090//              }
091        }
092        
093        public float computeCRMult(float suppliesPerDep, float dpMod) {
094                return 1f + dpMod / suppliesPerDep;
095        }
096        
097        public void applyEffectsBeforeShipCreation(HullSize hullSize, MutableShipStatsAPI stats, String id) {
098                //stats.getFighterRefitTimeMult().modifyPercent(id, ((Float) mag.get(hullSize)));
099                float numBays = 1f;
100                numBays += stats.getDynamic().getMod(Stats.CONVERTED_HANGAR_MOD).computeEffective(0f);
101                stats.getNumFighterBays().modifyFlat(id, numBays);
102                
103                boolean sMod = isSMod(stats);
104                if (sMod) {
105                        float bonus = 0f;
106                        if (hullSize == HullSize.CRUISER) bonus = SMOD_CRUISER;
107                        else if (hullSize == HullSize.CAPITAL_SHIP) bonus = SMOD_CAPITAL;
108                        if (bonus != 0) {
109                                stats.getDynamic().getStat(Stats.REPLACEMENT_RATE_INCREASE_MULT).modifyPercent(id, bonus);
110                        }
111                }
112                
113                boolean crewIncrease = stats.getDynamic().getMod(Stats.CONVERTED_HANGAR_NO_CREW_INCREASE).computeEffective(0f) <= 0;
114                boolean rearmIncrease = stats.getDynamic().getMod(Stats.CONVERTED_HANGAR_NO_REARM_INCREASE).computeEffective(0f) <= 0;
115                boolean dpIncrease = stats.getDynamic().getMod(Stats.CONVERTED_HANGAR_NO_DP_INCREASE).computeEffective(0f) <= 0;
116                boolean refitPenalty = stats.getDynamic().getMod(Stats.CONVERTED_HANGAR_NO_REFIT_PENALTY).computeEffective(0f) <= 0;
117                
118                if (refitPenalty) {
119                        stats.getFighterRefitTimeMult().modifyMult(id, REPLACEMENT_TIME_MULT);
120                        stats.getDynamic().getStat(Stats.REPLACEMENT_RATE_DECREASE_MULT).modifyMult(id, 1f / REPLACEMENT_TIME_MULT);
121                        stats.getDynamic().getStat(Stats.REPLACEMENT_RATE_INCREASE_MULT).modifyMult(id, 1f / REPLACEMENT_TIME_MULT);
122                }
123                
124                if (rearmIncrease) {
125                        //stats.getDynamic().getMod(Stats.FIGHTER_REARM_TIME_EXTRA_FLAT_MOD).modifyFlat(id, EXTRA_REARM_TIME);
126                        stats.getDynamic().getMod(Stats.FIGHTER_REARM_TIME_EXTRA_FRACTION_OF_BASE_REFIT_TIME_MOD).modifyFlat(id, REARM_TIME_FRACTION);
127                }
128                
129                if (dpIncrease) {
130                        float dpMod = computeDPModifier(getFighterOPCost(stats));
131                        if (dpMod > 0) {
132                                stats.getDynamic().getMod(Stats.DEPLOYMENT_POINTS_MOD).modifyFlat(id, dpMod);
133                                
134                                if (stats.getFleetMember() != null) {
135                                        float perDep = stats.getFleetMember().getHullSpec().getSuppliesToRecover();
136                                        float mult = computeCRMult(perDep, dpMod);
137                                        stats.getCRPerDeploymentPercent().modifyMult(id, mult);
138                                }
139                                
140                                stats.getSuppliesToRecover().modifyFlat(id, dpMod);
141                        }
142                }
143                
144                if (crewIncrease) {
145                        stats.getMinCrewMod().modifyFlat(id, CREW_REQ);
146                }
147                
148                
149//              boolean costIncrease = stats.getDynamic().getMod(Stats.CONVERTED_HANGAR_NO_COST_INCREASE).computeEffective(0f) <= 0;
150//              //costIncrease = false;
151//              if (costIncrease) {
152//                      stats.getMinCrewMod().modifyFlat(id, CREW_REQ);
153//                      //stats.getDynamic().getMod(Stats.ALL_FIGHTER_COST_MOD).modifyPercent(id, ALL_FIGHTER_COST_PERCENT);
154//                      stats.getDynamic().getMod(Stats.BOMBER_COST_MOD).modifyPercent(id, BOMBER_COST_PERCENT);
155//                      stats.getDynamic().getMod(Stats.FIGHTER_COST_MOD).modifyPercent(id, ALL_FIGHTER_COST_PERCENT);
156//                      stats.getDynamic().getMod(Stats.INTERCEPTOR_COST_MOD).modifyPercent(id, ALL_FIGHTER_COST_PERCENT);
157//                      stats.getDynamic().getMod(Stats.SUPPORT_COST_MOD).modifyPercent(id, ALL_FIGHTER_COST_PERCENT);
158//              }
159                //stats.getCargoMod().modifyFlat(id, -CARGO_REQ);
160        }
161        
162        public boolean isApplicableToShip(ShipAPI ship) {
163                if (ship != null && ship.getMutableStats().getDynamic().getValue(Stats.FORCE_ALLOW_CONVERTED_HANGAR, 0f) > 0f) {
164                        return true;
165                }
166                //if (ship.getMutableStats().getCargoMod().computeEffective(ship.getHullSpec().getCargo()) < CARGO_REQ) return false;
167                if (ship != null && ship.getHullSpec().getCRToDeploy() > CR_THRESHOLD_UNINSTALLABLE) {
168                        return false;
169                }
170                return ship != null && !ship.isFrigate() && ship.getHullSpec().getFighterBays() <= 0 &&
171                                                                //ship.getNumFighterBays() <= 0 &&
172                                                                !ship.getVariant().hasHullMod(HullMods.CONVERTED_BAY) &&
173                                                                !ship.getHullSpec().isPhase();
174        }
175        
176        public String getUnapplicableReason(ShipAPI ship) {
177                if (ship != null && ship.getHullSpec().getCRToDeploy() > CR_THRESHOLD_UNINSTALLABLE) {
178                        return "Ship's combat readiness lost per deployment is too high";
179                }
180                if (ship != null && ship.isFrigate()) return "Can not be installed on a frigate";
181                if (ship != null && ship.getHullSpec().getFighterBays() > 0) return "Ship has standard fighter bays";
182                if (ship != null && ship.getVariant().hasHullMod(HullMods.CONVERTED_BAY)) return "Ship has fighter bays";
183                //if (ship != null && ship.getNumFighterBays() > 0) return "Ship has fighter bays";
184                return "Can not be installed on a phase ship";
185        }
186        
187        public void applyEffectsToFighterSpawnedByShip(ShipAPI fighter, ShipAPI ship, String id) {
188                //setFighterSkin(fighter, ship);
189//              boolean statsPenalty = ship.getMutableStats().getDynamic().getMod(Stats.CONVERTED_HANGAR_NO_PERFORMANCE_PENALTY).computeEffective(0f) <= 0;
190//              boolean sMod = isSMod(ship);
191//              if (statsPenalty && !sMod) {
192//                      new DefectiveManufactory().applyEffectsToFighterSpawnedByShip(fighter, ship, id);
193//              }
194        }
195        
196        
197        public static void setFighterSkin(ShipAPI fighter, ShipAPI carrier) {
198                SpriteAPI sprite = getFighterSkin(fighter, carrier);
199                if (sprite != null) {
200                        fighter.setSprite(sprite);
201                }
202        }
203        
204        public static SpriteAPI getFighterSkin(ShipAPI fighter, ShipAPI carrier) {
205                if (carrier.getHullStyleId().equals(fighter.getHullStyleId())) {
206                        return null;
207                }
208                String cat = null;
209                SpriteAPI skin = null;
210                if (carrier.getOwner() == 0 || carrier.getOriginalOwner() == 0) {
211                        cat = "fighterSkinsPlayerOnly";
212                        skin = getFighterSkin(cat, fighter, carrier);
213                }
214                if (skin != null) return skin;
215                
216                cat = "fighterSkinsPlayerAndNPC";
217                skin = getFighterSkin(cat, fighter, carrier);
218                return skin;
219        }
220        
221        
222        public static SpriteAPI getFighterSkin(String cat, ShipAPI fighter, ShipAPI carrier) {
223                
224                String exclude = "fighterSkinsExcludeFromSharing";
225                String id = fighter.getHullSpec().getHullId();
226                String style = carrier.getHullStyleId();
227                
228                List<String> skins = Global.getSettings().getSpriteKeys(cat);
229                Set<String> noSharing = new LinkedHashSet<String>(Global.getSettings().getSpriteKeys(exclude));
230                
231                List<SpriteAPI> matching = new ArrayList<SpriteAPI>();
232                for (String key : skins) {
233                        if (key.equals(id + "_" + style)) {
234                                return Global.getSettings().getSprite(cat, key);
235                        }
236                        if (key.startsWith(id) && !noSharing.contains(key)) {
237                                matching.add(Global.getSettings().getSprite(cat, key));
238                        }
239                }
240                
241                if (!matching.isEmpty()) {
242                        SpriteAPI best = null;
243                        float minDist = Float.MAX_VALUE;
244                        
245                        for (SpriteAPI curr : matching) {
246                                float dist = Misc.getColorDist(carrier.getSpriteAPI().getAverageBrightColor(), curr.getAverageBrightColor());
247                                if (dist < minDist) {
248                                        best = curr;
249                                        minDist = dist;
250                                }
251                        }
252                        return best;
253                }
254                return null;
255        }
256        
257        
258//      public String getDescriptionParam(int index, HullSize hullSize, ShipAPI ship) {
259//              if (index == 2) return "" + CREW_REQ;
260//              if (index == 3) return "" + BOMBER_COST_PERCENT + "%";
261//              if (index == 4) return "" + ALL_FIGHTER_COST_PERCENT + "%";
262//              return new DefectiveManufactory().getDescriptionParam(index, hullSize, ship);
263////            if (index == 0) return "" + ((Float) mag.get(HullSize.DESTROYER)).intValue() + "%";
264////            if (index == 1) return "" + ((Float) mag.get(HullSize.CRUISER)).intValue() + "%";
265////            if (index == 2) return "" + ((Float) mag.get(HullSize.CAPITAL_SHIP)).intValue() + "%";
266////            if (index == 3) return "" + CREW_REQ;
267////            return null;
268//              //if (index == 0) return "" + ((Float) mag.get(hullSize)).intValue();
269//              //return null;
270//      }
271        
272//      @Override
273//      public boolean affectsOPCosts() {
274//              return true;
275//      }
276        
277        @Override
278        public boolean shouldAddDescriptionToTooltip(HullSize hullSize, ShipAPI ship, boolean isForModSpec) {
279                return false;
280        }
281
282        @Override
283        public void addPostDescriptionSection(TooltipMakerAPI tooltip, HullSize hullSize, final ShipAPI ship, float width, boolean isForModSpec) {
284                float pad = 3f;
285                float opad = 10f;
286                Color h = Misc.getHighlightColor();
287                Color bad = Misc.getNegativeHighlightColor();
288                
289                
290                tooltip.addPara("Converts the ship's standard shuttle hangar to house a fighter bay. "
291                                + "The improvised flight deck, its crew, and the related machinery all function "
292                                + "at a pace below that of a dedicated carrier.", opad);
293
294
295//              tooltip.addPara("Increases fighter refit time by %s, "
296//                              + "and the fighter replacement rate both decays and recovers %s more slowly. "
297//                              + "In addition, bombers returning to rearm take %s seconds longer to relaunch. "
298//                              + "Increases the minimum crew by %s to account for pilots and flight crews.", opad, h,
299//                              "" + Misc.getRoundedValueMaxOneAfterDecimal(REPLACEMENT_TIME_MULT) + Strings.X,
300//                              "" + Misc.getRoundedValueMaxOneAfterDecimal(REPLACEMENT_TIME_MULT) + Strings.X,
301//                              "" + (int) EXTRA_REARM_TIME,
302//                              "" + (int) CREW_REQ);
303                tooltip.addPara("Increases fighter refit time by %s, "
304                                + "and the fighter replacement rate both decays and recovers %s more slowly. "
305                                + "In addition, bombers returning to rearm (or fighters returning for repairs) "
306                                + "take %s of their base refit time to relaunch, "
307                                + "where normally it takes under a second. "
308                                + "", opad, h,
309                                "" + Misc.getRoundedValueMaxOneAfterDecimal(REPLACEMENT_TIME_MULT) + Strings.X,
310                                "" + Misc.getRoundedValueMaxOneAfterDecimal(REPLACEMENT_TIME_MULT) + Strings.X,
311                                "" + (int) Math.round(REARM_TIME_FRACTION * 100f) + "%");
312                
313                
314                tooltip.addPara("Increases the minimum crew by %s to account for pilots and flight crews. "
315                                + "Increases the ship's deployment points and supply cost to recover "
316                                + "from deployment by %s for every %s ordnance points spent on "
317                                + "fighters, or by at least %s point. This comes with a proportional increase "
318                                + "in combat readiness lost per deployment.", opad, h,
319                                "" + (int) CREW_REQ,
320                                "1",
321                                "" + (int) + FIGHTER_OP_PER_DP,
322                                "" + (int) + MIN_DP);
323                
324                if (isForModSpec || ship == null || ship.getMutableStats() == null) return;
325                
326                MutableShipStatsAPI stats = ship.getMutableStats();
327                boolean crewIncrease = stats.getDynamic().getMod(Stats.CONVERTED_HANGAR_NO_CREW_INCREASE).computeEffective(0f) <= 0;
328                boolean rearmIncrease = stats.getDynamic().getMod(Stats.CONVERTED_HANGAR_NO_REARM_INCREASE).computeEffective(0f) <= 0;
329                boolean dpIncrease = stats.getDynamic().getMod(Stats.CONVERTED_HANGAR_NO_DP_INCREASE).computeEffective(0f) <= 0;
330                boolean refitPenalty = stats.getDynamic().getMod(Stats.CONVERTED_HANGAR_NO_REFIT_PENALTY).computeEffective(0f) <= 0;
331                
332                int dpMod = computeDPModifier(getFighterOPCost(stats));
333                if (dpMod > 0) {
334                        //tooltip.addSectionHeading("Fighter wings", Alignment.MID, opad);
335                        //tooltip.addPara("%s points for the currently installed fighter wing.", opad, h, "+" + dpMod);
336                        if (dpIncrease) {
337//                              float perDep = stats.getFleetMember().getHullSpec().getCRToDeploy();
338//                              1f + dpMod / perDep);
339                                tooltip.addPara("Deployment cost: %s", opad, h, "+" + dpMod);
340                        }
341
342                        float numW = 160f;
343                        float sizeW = width - numW - 10f;
344                        
345                        if (!getFighterWings(stats).isEmpty() && rearmIncrease) {
346                                tooltip.beginTable(Misc.getBasePlayerColor(), Misc.getDarkPlayerColor(), Misc.getBrightPlayerColor(),
347                                                   20f, true, true, 
348                                                   new Object [] {"Wing", sizeW, "Seconds to relaunch", numW});
349                                
350                                for (String wingId : getFighterWings(stats)) {
351                                        FighterWingSpecAPI spec = Global.getSettings().getFighterWingSpec(wingId);
352                                        float refitPortion = spec.getRefitTime() * 
353                                                        ship.getMutableStats().getDynamic().getValue(Stats.FIGHTER_REARM_TIME_EXTRA_FRACTION_OF_BASE_REFIT_TIME_MOD, 0f);
354        
355                                        Color c = Misc.getTextColor();
356                                        //c = Misc.getHighlightColor();
357                                        tooltip.addRow(Alignment.MID, c, spec.getWingName(),
358                                                                   Alignment.MID, h, Misc.getRoundedValueOneAfterDecimalIfNotWhole(refitPortion)
359                                                                   );
360                                }
361                                tooltip.addTable("", 0, opad);
362                        }
363                        
364                }
365                
366//              boolean crewIncrease = stats.getDynamic().getMod(Stats.CONVERTED_HANGAR_NO_CREW_INCREASE).computeEffective(0f) <= 0;
367//              boolean rearmIncrease = stats.getDynamic().getMod(Stats.CONVERTED_HANGAR_NO_REARM_INCREASE).computeEffective(0f) <= 0;
368//              boolean dpIncrease = stats.getDynamic().getMod(Stats.CONVERTED_HANGAR_NO_DP_INCREASE).computeEffective(0f) <= 0;
369//              boolean refitPenalty = stats.getDynamic().getMod(Stats.CONVERTED_HANGAR_NO_REFIT_PENALTY).computeEffective(0f) <= 0;
370                
371                List<String> negated = new ArrayList<String>();
372                if (!refitPenalty) negated.add("refit time and rate recovery modifiers");
373                if (!rearmIncrease) negated.add("relaunch delay");
374                if (!crewIncrease) negated.add("increased crew requirement");
375                if (!dpIncrease) negated.add("deployment cost increase");
376                
377                if (!negated.isEmpty()) {
378                        Color c = Misc.getPositiveHighlightColor();
379                        String isOrAre = "is";
380                        if (negated.size() > 1) isOrAre = "are";
381                        if (negated.size() >= 4) isOrAre += " all";
382                        tooltip.addPara("The " + Misc.getAndJoined(negated) + " " + isOrAre + " negated on this ship.", c, opad);
383                }
384                
385                //tooltip.setBgAlpha(0.9f);
386                
387
388        }
389        
390        @Override
391        public String getSModDescriptionParam(int index, HullSize hullSize, ShipAPI ship) {
392                if (index == 0) return "" + (int) SMOD_CRUISER + "%";
393                if (index == 1) return "" + (int) SMOD_CAPITAL + "%";
394                return null;
395        }
396        
397}
398
399
400