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