001package com.fs.starfarer.api.impl.campaign.abilities; 002 003import java.util.ArrayList; 004import java.util.List; 005 006import java.awt.Color; 007 008import org.lwjgl.util.vector.Vector2f; 009 010import com.fs.starfarer.api.EveryFrameScript; 011import com.fs.starfarer.api.Global; 012import com.fs.starfarer.api.campaign.BattleAPI; 013import com.fs.starfarer.api.campaign.CampaignFleetAPI; 014import com.fs.starfarer.api.campaign.JumpPointAPI.JumpDestination; 015import com.fs.starfarer.api.campaign.NascentGravityWellAPI; 016import com.fs.starfarer.api.campaign.SectorEntityToken; 017import com.fs.starfarer.api.campaign.StarSystemAPI; 018import com.fs.starfarer.api.campaign.econ.MarketAPI; 019import com.fs.starfarer.api.fleet.FleetMemberAPI; 020import com.fs.starfarer.api.impl.campaign.ids.Pings; 021import com.fs.starfarer.api.impl.campaign.ids.Stats; 022import com.fs.starfarer.api.impl.campaign.intel.BaseIntelPlugin; 023import com.fs.starfarer.api.impl.campaign.tutorial.TutorialMissionIntel; 024import com.fs.starfarer.api.ui.LabelAPI; 025import com.fs.starfarer.api.ui.TooltipMakerAPI; 026import com.fs.starfarer.api.util.Misc; 027import com.fs.starfarer.api.util.Misc.FleetMemberDamageLevel; 028 029public class FractureJumpAbility extends BaseDurationAbility { 030 031 public static float CR_COST_MULT = 0.1f; 032 public static float FUEL_USE_MULT = 1f; 033 034 035 public static float NASCENT_JUMP_DIST = 50f; 036 037 protected boolean canUseToJumpToHyper() { 038 return true; 039 } 040 041 protected boolean canUseToJumpToSystem() { 042 return true; 043 } 044 045 protected Boolean primed = null; 046 protected NascentGravityWellAPI well = null; 047 protected EveryFrameScript ping = null; 048 049 @Override 050 protected void activateImpl() { 051 //if (Global.getSector().isPaused()) return; 052 053 CampaignFleetAPI fleet = getFleet(); 054 if (fleet == null) return; 055 056 if (fleet.isInHyperspaceTransition()) return; 057 058 if (fleet.isInHyperspace() && canUseToJumpToSystem()) { 059 NascentGravityWellAPI well = getNearestWell(NASCENT_JUMP_DIST); 060 if (well == null || well.getTarget() == null) return; 061 062 this.well = well; 063 ping = Global.getSector().addPing(fleet, Pings.TRANSVERSE_JUMP); 064 primed = true; 065 } else if (!fleet.isInHyperspace() && canUseToJumpToHyper() && 066 fleet.getContainingLocation() instanceof StarSystemAPI) { 067 ping = Global.getSector().addPing(fleet, Pings.TRANSVERSE_JUMP); 068 primed = true; 069 } else { 070 deactivate(); 071 } 072 } 073 074 @Override 075 public void deactivate() { 076 if (ping != null) { 077 Global.getSector().removeScript(ping); 078 ping = null; 079 } 080 super.deactivate(); 081 } 082 083 @Override 084 protected void applyEffect(float amount, float level) { 085 CampaignFleetAPI fleet = getFleet(); 086 if (fleet == null) return; 087 088 if (level > 0 && level < 1 && amount > 0) { 089 float activateSeconds = getActivationDays() * Global.getSector().getClock().getSecondsPerDay(); 090 float speed = fleet.getVelocity().length(); 091 float acc = Math.max(speed, 200f)/activateSeconds + fleet.getAcceleration(); 092 float ds = acc * amount; 093 if (ds > speed) ds = speed; 094 Vector2f dv = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(fleet.getVelocity())); 095 dv.scale(ds); 096 fleet.setVelocity(fleet.getVelocity().x - dv.x, fleet.getVelocity().y - dv.y); 097 return; 098 } 099 100 if (level == 1 && primed != null) { 101 float dist = Float.MAX_VALUE; 102 if (well != null) { 103 dist = Misc.getDistance(fleet, well) - well.getRadius() - fleet.getRadius(); 104 } 105 if (well != null && fleet.isInHyperspace() && canUseToJumpToSystem() && dist < 500f) { 106 SectorEntityToken planet = well.getTarget(); 107 Vector2f loc = Misc.getPointAtRadius(planet.getLocation(), planet.getRadius() + 200f + fleet.getRadius()); 108 SectorEntityToken token = planet.getContainingLocation().createToken(loc.x, loc.y); 109 110 JumpDestination dest = new JumpDestination(token, null); 111 Global.getSector().doHyperspaceTransition(fleet, fleet, dest); 112 } else if (!fleet.isInHyperspace() && canUseToJumpToHyper() && 113 fleet.getContainingLocation() instanceof StarSystemAPI) { 114 float crCostFleetMult = getCRCostMult(fleet); 115 if (crCostFleetMult > 0) { 116 for (FleetMemberAPI member : getNonReadyShips()) { 117 if ((float) Math.random() < EmergencyBurnAbility.ACTIVATION_DAMAGE_PROB) { 118 Misc.applyDamage(member, null, FleetMemberDamageLevel.LOW, false, null, null, 119 true, null, member.getShipName() + " suffers damage from Transverse Jump activation"); 120 } 121 } 122 for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) { 123 float crLoss = member.getDeployCost() * CR_COST_MULT * crCostFleetMult; 124 member.getRepairTracker().applyCREvent(-crLoss, "Transverse jump"); 125 } 126 String key = "$makeTranverseJumpCostMoreCROnce"; 127 fleet.getMemoryWithoutUpdate().unset(key); 128 } 129 130 float cost = computeFuelCost(); 131 fleet.getCargo().removeFuel(cost); 132 133 134 StarSystemAPI system = (StarSystemAPI) fleet.getContainingLocation(); 135 136 Vector2f offset = Vector2f.sub(fleet.getLocation(), system.getCenter().getLocation(), new Vector2f()); 137 float maxInSystem = 20000f; 138 float maxInHyper = 2000f; 139 float f = offset.length() / maxInSystem; 140 //if (f > 1) f = 1; 141 if (f > 0.5f) f = 0.5f; 142 143 float angle = Misc.getAngleInDegreesStrict(offset); 144 145 Vector2f destOffset = Misc.getUnitVectorAtDegreeAngle(angle); 146 destOffset.scale(f * maxInHyper); 147 148 Vector2f.add(system.getLocation(), destOffset, destOffset); 149 SectorEntityToken token = Global.getSector().getHyperspace().createToken(destOffset.x, destOffset.y); 150 151 JumpDestination dest = new JumpDestination(token, null); 152 Global.getSector().doHyperspaceTransition(fleet, fleet, dest); 153 } 154 155 primed = null; 156 well = null; 157 } 158 } 159 160 @Override 161 protected String getActivationText() { 162 return super.getActivationText(); 163 //return "Initiating jump"; 164 } 165 166 167 @Override 168 protected void deactivateImpl() { 169 cleanupImpl(); 170 } 171 172 @Override 173 protected void cleanupImpl() { 174 CampaignFleetAPI fleet = getFleet(); 175 if (fleet == null) return; 176 } 177 178 @Override 179 public boolean isUsable() { 180 if (!super.isUsable()) return false; 181 if (getFleet() == null) return false; 182 183 CampaignFleetAPI fleet = getFleet(); 184 185 if (fleet.isInHyperspaceTransition()) return false; 186 187 if (TutorialMissionIntel.isTutorialInProgress()) return false; 188 189 if (canUseToJumpToSystem() && fleet.isInHyperspace() && getNearestWell(NASCENT_JUMP_DIST) != null) { 190 return true; 191 } 192 193 if (canUseToJumpToHyper() && !fleet.isInHyperspace()) { 194 //if (getNonReadyShips().isEmpty() && 195 if ((getFleet().isAIMode() || computeFuelCost() <= getFleet().getCargo().getFuel())) { 196 return true; 197 } 198 } 199 200 return false; 201 } 202 203 public NascentGravityWellAPI getNearestWell(float maxDist) { 204 CampaignFleetAPI fleet = getFleet(); 205 if (fleet == null) return null; 206 if (!fleet.isInHyperspace()) return null; 207 208 float minDist = Float.MAX_VALUE; 209 NascentGravityWellAPI closest = null; 210 List<Object> wells = fleet.getContainingLocation().getEntities(NascentGravityWellAPI.class); 211 for (Object o : wells) { 212 NascentGravityWellAPI well = (NascentGravityWellAPI) o; 213 float dist = Misc.getDistance(well.getLocation(), fleet.getLocation()); 214 dist -= well.getRadius() + fleet.getRadius(); 215 if (dist > maxDist) continue; 216 if (dist < minDist) { 217 minDist = dist; 218 closest = well; 219 } 220 } 221 return closest; 222 } 223 224 225 @Override 226 public void createTooltip(TooltipMakerAPI tooltip, boolean expanded) { 227 CampaignFleetAPI fleet = getFleet(); 228 if (fleet == null) return; 229 230 Color gray = Misc.getGrayColor(); 231 Color highlight = Misc.getHighlightColor(); 232 Color fuel = Global.getSettings().getColor("progressBarFuelColor"); 233 Color bad = Misc.getNegativeHighlightColor(); 234 235 if (!Global.CODEX_TOOLTIP_MODE) { 236 LabelAPI title = tooltip.addTitle("Transverse Jump"); 237 } else { 238 tooltip.addSpacer(-10f); 239 } 240 241 float pad = 10f; 242 243 NascentGravityWellAPI well = getNearestWell(NASCENT_JUMP_DIST); 244 245 tooltip.addPara("Jump into hyperspace without the use of a jump-point, or " + 246 "jump into a star system across the hyperspace boundary near a nascent gravity well, " + 247 "emerging near the entity corresponding to the gravity well.", pad); 248 249 250 if (Global.CODEX_TOOLTIP_MODE) { 251 String years = "year's"; 252 if (FUEL_USE_MULT != 1) years = "years'"; 253 tooltip.addPara("Jumping into hyperspace consumes %s light " + years + " worth of fuel and reduces the combat readiness " 254 + "of all ships by %s of a combat deployment. " + 255 "Jumping into a star system is free.", pad, 256 highlight, 257 "" + Misc.getRoundedValue(FUEL_USE_MULT), 258 "" + (int) Math.round(CR_COST_MULT * 100f) + "%"); 259 260 tooltip.addPara("Ships with insufficient combat readiness may suffer damage when the ability is activated.", pad); 261 } else { 262 float fuelCost = computeFuelCost(); 263 float supplyCost = computeSupplyCost(); 264 265 if (supplyCost > 0) { 266 tooltip.addPara("Jumping into hyperspace consumes %s fuel and slightly reduces the combat readiness" + 267 " of all ships, costing up to %s supplies to recover. Jumping into a star system is free.", pad, 268 highlight, 269 Misc.getRoundedValueMaxOneAfterDecimal(fuelCost), 270 Misc.getRoundedValueMaxOneAfterDecimal(supplyCost)); 271 } else { 272 tooltip.addPara("Jumping into hyperspace consumes %s fuel. Jumping into a star system is free.", pad, 273 highlight, 274 Misc.getRoundedValueMaxOneAfterDecimal(fuelCost)); 275 } 276 277 278 if (TutorialMissionIntel.isTutorialInProgress()) { 279 tooltip.addPara("Can not be used right now.", bad, pad); 280 } 281 282 if (!fleet.isInHyperspace()) { 283 if (fuelCost > fleet.getCargo().getFuel()) { 284 tooltip.addPara("Not enough fuel.", bad, pad); 285 } 286 287 List<FleetMemberAPI> nonReady = getNonReadyShips(); 288 if (!nonReady.isEmpty()) { 289 //tooltip.addPara("Not all ships have enough combat readiness to initiate an emergency burn. Ships that require higher CR:", pad); 290 tooltip.addPara("Some ships don't have enough combat readiness to safely initiate a transverse jump " + 291 "and may suffer damage if the ability is activated:", pad, 292 Misc.getNegativeHighlightColor(), "may suffer damage"); 293 int j = 0; 294 int max = 4; 295 float initPad = 5f; 296 for (FleetMemberAPI member : nonReady) { 297 if (j >= max) { 298 if (nonReady.size() > max + 1) { 299 tooltip.addPara(BaseIntelPlugin.INDENT + "... and several other ships", initPad); 300 break; 301 } 302 } 303 String str = ""; 304 if (!member.isFighterWing()) { 305 str += member.getShipName() + ", "; 306 str += member.getHullSpec().getHullNameWithDashClass(); 307 } else { 308 str += member.getVariant().getFullDesignationWithHullName(); 309 } 310 311 tooltip.addPara(BaseIntelPlugin.INDENT + str, initPad); 312 initPad = 0f; 313 j++; 314 } 315 } 316 317 // List<FleetMemberAPI> nonReady = getNonReadyShips(); 318 // if (!nonReady.isEmpty()) { 319 // tooltip.addPara("Not all ships have enough combat readiness to initiate a transverse jump. Ships that require higher CR:", pad); 320 // tooltip.beginGridFlipped(getTooltipWidth(), 1, 30, pad); 321 // //tooltip.setGridLabelColor(bad); 322 // int j = 0; 323 // int max = 7; 324 // for (FleetMemberAPI member : nonReady) { 325 // if (j >= max) { 326 // if (nonReady.size() > max + 1) { 327 // tooltip.addToGrid(0, j++, "... and several other ships", "", bad); 328 // break; 329 // } 330 // } 331 // float crLoss = member.getDeployCost() * CR_COST_MULT; 332 // String cost = "" + Math.round(crLoss * 100) + "%"; 333 // String str = ""; 334 // if (!member.isFighterWing()) { 335 // str += member.getShipName() + ", "; 336 // str += member.getHullSpec().getHullNameWithDashClass(); 337 // } else { 338 // str += member.getVariant().getFullDesignationWithHullName(); 339 // } 340 // tooltip.addToGrid(0, j++, str, cost, bad); 341 // } 342 // tooltip.addGrid(3f); 343 // } 344 } 345 346 if (fleet.isInHyperspace()) { 347 if (well == null) { 348 tooltip.addPara("Must be near a nascent gravity well.", bad, pad); 349 } 350 } 351 } 352 353 addIncompatibleToTooltip(tooltip, expanded); 354 } 355 356 public boolean hasTooltip() { 357 return true; 358 } 359 360 @Override 361 public void fleetLeftBattle(BattleAPI battle, boolean engagedInHostilities) { 362 if (engagedInHostilities) { 363 deactivate(); 364 } 365 } 366 367 @Override 368 public void fleetOpenedMarket(MarketAPI market) { 369 deactivate(); 370 } 371 372 373 protected List<FleetMemberAPI> getNonReadyShips() { 374 List<FleetMemberAPI> result = new ArrayList<FleetMemberAPI>(); 375 CampaignFleetAPI fleet = getFleet(); 376 if (fleet == null) return result; 377 378 //float crCostFleetMult = fleet.getStats().getDynamic().getValue(Stats.EMERGENCY_BURN_CR_MULT); 379 //float crCostFleetMult = 1f; 380 float crCostFleetMult = getCRCostMult(fleet); 381 for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) { 382 //if (member.isMothballed()) continue; 383 float crLoss = member.getDeployCost() * CR_COST_MULT * crCostFleetMult; 384 if (Math.round(member.getRepairTracker().getCR() * 100) < Math.round(crLoss * 100)) { 385 result.add(member); 386 } 387 } 388 return result; 389 } 390 391 protected float computeFuelCost() { 392 CampaignFleetAPI fleet = getFleet(); 393 if (fleet == null) return 0f; 394 395 float cost = fleet.getLogistics().getFuelCostPerLightYear() * FUEL_USE_MULT; 396 return cost; 397 } 398 399 protected float getCRCostMult(CampaignFleetAPI fleet) { 400 float crCostFleetMult = fleet.getStats().getDynamic().getValue(Stats.DIRECT_JUMP_CR_MULT); 401 String key = "$makeTranverseJumpCostMoreCROnce"; 402 if (fleet.getMemoryWithoutUpdate().contains(key)) { 403 crCostFleetMult = 20f; 404 } 405 return crCostFleetMult; 406 } 407 408 protected float computeSupplyCost() { 409 CampaignFleetAPI fleet = getFleet(); 410 if (fleet == null) return 0f; 411 412 //float crCostFleetMult = fleet.getStats().getDynamic().getValue(Stats.EMERGENCY_BURN_CR_MULT); 413 //float crCostFleetMult = 1f; 414 float crCostFleetMult = getCRCostMult(fleet); 415 416 float cost = 0f; 417 for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) { 418 cost += member.getDeploymentCostSupplies() * CR_COST_MULT * crCostFleetMult; 419 } 420 return cost; 421 } 422 423 424 425 protected boolean showAlarm() { 426 if (getFleet() != null && getFleet().isInHyperspace()) return false; 427 return !getNonReadyShips().isEmpty() && !isOnCooldown() && !isActiveOrInProgress() && isUsable(); 428 } 429 430// @Override 431// public boolean isUsable() { 432// return super.isUsable() && 433// getFleet() != null && 434// //getNonReadyShips().isEmpty() && 435// (getFleet().isAIMode() || computeFuelCost() <= getFleet().getCargo().getFuel()); 436// } 437 438 @Override 439 public float getCooldownFraction() { 440 if (showAlarm()) { 441 return 0f; 442 } 443 return super.getCooldownFraction(); 444 } 445 @Override 446 public boolean showCooldownIndicator() { 447 return super.showCooldownIndicator(); 448 } 449 @Override 450 public boolean isOnCooldown() { 451 return super.getCooldownFraction() < 1f; 452 } 453 454 @Override 455 public Color getCooldownColor() { 456 if (showAlarm()) { 457 Color color = Misc.getNegativeHighlightColor(); 458 return Misc.scaleAlpha(color, Global.getSector().getCampaignUI().getSharedFader().getBrightness() * 0.5f); 459 } 460 return super.getCooldownColor(); 461 } 462 463 @Override 464 public boolean isCooldownRenderingAdditive() { 465 if (showAlarm()) { 466 return true; 467 } 468 return false; 469 } 470} 471 472 473 474 475