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