001package com.fs.starfarer.api.impl.combat.threat; 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.Global; 011import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin; 012import com.fs.starfarer.api.combat.CombatEngineAPI; 013import com.fs.starfarer.api.combat.DamageType; 014import com.fs.starfarer.api.combat.EmpArcEntityAPI; 015import com.fs.starfarer.api.combat.EmpArcEntityAPI.EmpArcParams; 016import com.fs.starfarer.api.combat.MutableShipStatsAPI; 017import com.fs.starfarer.api.combat.ShipAPI; 018import com.fs.starfarer.api.combat.ShipAPI.HullSize; 019import com.fs.starfarer.api.combat.ShipSystemAPI; 020import com.fs.starfarer.api.combat.ShipSystemAPI.SystemState; 021import com.fs.starfarer.api.combat.ShipwideAIFlags.AIFlags; 022import com.fs.starfarer.api.combat.WeaponAPI.WeaponSize; 023import com.fs.starfarer.api.impl.combat.BaseShipSystemScript; 024import com.fs.starfarer.api.input.InputEventAPI; 025import com.fs.starfarer.api.loading.WeaponSlotAPI; 026import com.fs.starfarer.api.util.Misc; 027import com.fs.starfarer.api.util.Misc.FindShipFilter; 028 029public class EnergyLashSystemScript extends BaseShipSystemScript { 030 031 public static float MAX_LASH_RANGE = 1500f; 032 033 public static float DAMAGE = 0; 034 public static float EMP_DAMAGE = 1500; 035 036 public static float MIN_COOLDOWN = 2f; 037 public static float MAX_COOLDOWN = 10f; 038 public static float COOLDOWN_DP_MULT = 0.33f; 039 040 public static float MIN_HIT_ENEMY_COOLDOWN = 2f; 041 public static float MAX_HIT_ENEMY_COOLDOWN = 5f; 042 public static float HIT_PHASE_ENEMY_COOLDOWN_MULT = 2f; 043 044 public static float SWARM_TIMEOUT = 10f; 045 046 public static float PHASE_OVERLOAD_DUR = 1f; 047 048 049 050 public static class DelayedCombatActionPlugin extends BaseEveryFrameCombatPlugin { 051 float elapsed = 0f; 052 float delay; 053 Runnable r; 054 055 public DelayedCombatActionPlugin(float delay, Runnable r) { 056 this.delay = delay; 057 this.r = r; 058 } 059 060 @Override 061 public void advance(float amount, List<InputEventAPI> events) { 062 if (Global.getCombatEngine().isPaused()) return; 063 064 elapsed += amount; 065 if (elapsed < delay) return; 066 067 r.run(); 068 069 CombatEngineAPI engine = Global.getCombatEngine(); 070 engine.removePlugin(this); 071 } 072 } 073 074 075 076 protected WeaponSlotAPI mainSlot; 077 protected List<WeaponSlotAPI> slots; 078 protected boolean readyToFire = true; 079 protected float sinceSwarmTargeted = SWARM_TIMEOUT; 080 protected float cooldownToSet = -1f; 081 082 protected void findSlots(ShipAPI ship) { 083 if (slots != null) return; 084 slots = new ArrayList<>(); 085 for (WeaponSlotAPI slot : ship.getHullSpec().getAllWeaponSlotsCopy()) { 086 if (slot.isSystemSlot()) { 087 slots.add(slot); 088 if (slot.getSlotSize() == WeaponSize.MEDIUM) { 089 mainSlot = slot; 090 } 091 } 092 } 093 } 094 095 public void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel) { 096 ShipAPI ship = null; 097 //boolean player = false; 098 if (stats.getEntity() instanceof ShipAPI) { 099 ship = (ShipAPI) stats.getEntity(); 100 //player = ship == Global.getCombatEngine().getPlayerShip(); 101 } else { 102 return; 103 } 104 105 sinceSwarmTargeted += Global.getCombatEngine().getElapsedInLastFrame(); 106 107 if ((state == State.COOLDOWN || state == State.IDLE) && cooldownToSet >= 0f) { 108 ship.getSystem().setCooldown(cooldownToSet); 109 ship.getSystem().setCooldownRemaining(cooldownToSet); 110 cooldownToSet = -1f; 111 112 } 113 114 if (state == State.IDLE || state == State.COOLDOWN || effectLevel <= 0f) { 115 readyToFire = true; 116 } 117 118 if (state == State.IN || state == State.OUT) { 119 float jitterLevel = effectLevel; 120 121 float maxRangeBonus = 150f; 122 //float jitterRangeBonus = jitterLevel * maxRangeBonus; 123 float jitterRangeBonus = (1f - effectLevel * effectLevel) * maxRangeBonus; 124 125 float brightness = 0f; 126 float threshold = 0.1f; 127 if (effectLevel < threshold) { 128 brightness = effectLevel / threshold; 129 } else { 130 brightness = 1f - (effectLevel - threshold) / (1f - threshold); 131 } 132 if (brightness < 0) brightness = 0; 133 if (brightness > 1) brightness = 1; 134 if (state == State.OUT) { 135 jitterRangeBonus = 0f; 136 brightness = effectLevel * effectLevel; 137 } 138 Color color = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR; 139 //color = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR_BRIGHT; 140 //ship.setJitterUnder(this, color, jitterLevel, 21, 0f, 3f + jitterRangeBonus); 141 //ship.setJitter(this, JITTER_COLOR, jitterLevel, 4, 0f, 0 + jitterRangeBonus * 0.67f); 142 //ship.setJitter(this, color, jitterLevel, 1, 0f, 3f); 143 ship.setJitter(this, color, jitterLevel, 5, 0f, 3f + jitterRangeBonus); 144 } 145 146 if (effectLevel == 1 && readyToFire) { 147 ShipAPI target = findTarget(ship); 148 readyToFire = false; 149 if (target != null) { 150 CombatEngineAPI engine = Global.getCombatEngine(); 151 findSlots(ship); 152 153 Vector2f slotLoc = mainSlot.computePosition(ship); 154 155 EmpArcParams params = new EmpArcParams(); 156 params.segmentLengthMult = 8f; 157 params.zigZagReductionFactor = 0.15f; 158 params.fadeOutDist = 500f; 159 params.minFadeOutMult = 2f; 160 params.flickerRateMult = 0.7f; 161 162 //params.movementDurMax = 0.1f; 163// params.movementDurMin = 0.25f; 164// params.movementDurMax = 0.25f; 165 166 167 if (ship.getOwner() == target.getOwner()) { 168 //params.flickerRateMult = 0.6f; 169 params.flickerRateMult = 0.3f; 170 171 Color color = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR; 172 if (ThreatSwarmAI.isAttackSwarm(target)) { 173 color = VoltaicDischargeOnFireEffect.PHASE_FRINGE_COLOR; 174 } 175 float emp = 0; 176 float dam = 0; 177 EmpArcEntityAPI arc = (EmpArcEntityAPI)engine.spawnEmpArcPierceShields(ship, slotLoc, ship, target, 178 DamageType.ENERGY, 179 dam, 180 emp, // emp 181 100000f, // max range 182 "energy_lash_friendly_impact", 183 100f, // thickness 184 //new Color(100,165,255,255), 185 color, 186 new Color(255,255,255,255), 187 params 188 ); 189 arc.setTargetToShipCenter(slotLoc, target); 190 arc.setCoreWidthOverride(50f); 191 192 arc.setSingleFlickerMode(true); 193 //arc.setFadedOutAtStart(true); 194 Global.getSoundPlayer().playSound("energy_lash_fire", 1f, 1f, ship.getLocation(), ship.getVelocity()); 195 } else { 196 params.flickerRateMult = 0.4f; 197 198 int numArcs = slots.size(); 199 //numArcs = 1; 200 201 float emp = EMP_DAMAGE; 202 float dam = DAMAGE; 203 204 for (int i = 0; i < numArcs; i++) { 205 float delay = 0.03f * i; 206 //delay = 0f; 207 208// EmpArcParams params2 = new EmpArcParams(); 209// params2.segmentLengthMult = 8f; 210// params2.zigZagReductionFactor = 0.15f; 211// params2.fadeOutDist = 500f; 212// params2.minFadeOutMult = 2f; 213// params2.flickerRateMult = 0.8f - i * 0.1f; 214// params2.flickerRateMult = 0.8f; 215 216 int index = i; 217 ShipAPI ship2 = ship; 218 Runnable r = new Runnable() { 219 @Override 220 public void run() { 221 Vector2f slotLoc = slots.get(index).computePosition(ship2); 222 Color color = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR; 223 Color core = new Color(255,255,255,255); 224 if (target.isPhased()) { 225 color = VoltaicDischargeOnFireEffect.PHASE_FRINGE_COLOR; 226 core = VoltaicDischargeOnFireEffect.PHASE_CORE_COLOR; 227 } 228 //color = Misc.interpolateColor(color, new Color(255,0,255), 0.25f); 229 EmpArcEntityAPI arc = (EmpArcEntityAPI)engine.spawnEmpArc(ship2, slotLoc, ship2, target, 230 DamageType.ENERGY, 231 dam, 232 emp, // emp 233 100000f, // max range 234 "energy_lash_enemy_impact", 235 60f, // thickness 236 //new Color(100,165,255,255), 237 color, 238 core, 239 params 240 ); 241 arc.setCoreWidthOverride(40f); 242 arc.setSingleFlickerMode(true); 243 } 244 }; 245 if (delay <= 0f) { 246 r.run(); 247 } else { 248 Global.getCombatEngine().addPlugin(new DelayedCombatActionPlugin(delay, r)); 249 } 250 251 Global.getSoundPlayer().playSound("energy_lash_fire_at_enemy", 1f, 1f, ship.getLocation(), ship.getVelocity()); 252 253// arc.setFadedOutAtStart(true); 254// arc.setRenderGlowAtStart(false); 255 } 256 } 257 258 applyEffectToTarget(ship, target); 259 } 260 } 261 } 262 263 264 265 266 protected void applyEffectToTarget(ShipAPI ship, ShipAPI target) { 267 boolean isSwarm = ThreatSwarmAI.isAttackSwarm(target); 268 if (!isSwarm) { 269 if (target == null || target.getSystem() == null || target.isHulk()) return; 270 } 271 if (ship == null || ship.getSystem() == null || ship.isHulk()) return; 272 273 if (ship.getOwner() == target.getOwner()) { 274 if (target.getSystem() != null && target.getSystem().getScript() instanceof EnergyLashActivatedSystem) { 275 EnergyLashActivatedSystem script = (EnergyLashActivatedSystem) target.getSystem().getScript(); 276 script.hitWithEnergyLash(ship, target); 277 } else if (isSwarm) { 278 VoltaicDischargeOnFireEffect.setSwarmPhaseMode(target); 279 sinceSwarmTargeted = 0f; 280 } 281 282 float cooldown = target.getHullSpec().getSuppliesToRecover(); 283 //float cooldown = target.getMutableStats().getSuppliesToRecover().getBaseValue(); 284 //cooldown = (int)Math.round(target.getMutableStats().getDynamic().getMod(Stats.DEPLOYMENT_POINTS_MOD).computeEffective(cooldown)); 285 286 cooldown = MIN_COOLDOWN + cooldown * COOLDOWN_DP_MULT; 287 if (cooldown > MAX_COOLDOWN) cooldown = MAX_COOLDOWN; 288 if (target.isFighter()) cooldown = MIN_COOLDOWN; 289// ship.getSystem().setCooldown(cooldown); 290// ship.getSystem().setCooldownRemaining(cooldown); 291 cooldownToSet = cooldown; 292 } else { 293 boolean hitPhase = false; 294 if (target.isPhased()) { 295 target.setOverloadColor(VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR_BRIGHT); 296 target.getFluxTracker().beginOverloadWithTotalBaseDuration(PHASE_OVERLOAD_DUR); 297 if (target.getFluxTracker().showFloaty() || 298 ship == Global.getCombatEngine().getPlayerShip() || 299 target == Global.getCombatEngine().getPlayerShip()) { 300 target.getFluxTracker().playOverloadSound(); 301 target.getFluxTracker().showOverloadFloatyIfNeeded("Phase Field Disruption!", 302 VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR, 4f, true); 303 } 304 305 Global.getCombatEngine().addPlugin(new BaseEveryFrameCombatPlugin() { 306 @Override 307 public void advance(float amount, List<InputEventAPI> events) { 308 if (!target.getFluxTracker().isOverloadedOrVenting()) { 309 target.resetOverloadColor(); 310 Global.getCombatEngine().removePlugin(this); 311 } 312 } 313 }); 314 315 hitPhase = true; 316 } 317 318 float cooldown = MIN_HIT_ENEMY_COOLDOWN + 319 (MAX_HIT_ENEMY_COOLDOWN - MIN_HIT_ENEMY_COOLDOWN) * (float) Math.random(); 320 if (hitPhase) { 321 cooldown *= HIT_PHASE_ENEMY_COOLDOWN_MULT; 322 } 323 if (cooldown > MAX_COOLDOWN) cooldown = MAX_COOLDOWN; 324// ship.getSystem().setCooldown(cooldown); 325// ship.getSystem().setCooldownRemaining(cooldown); 326 cooldownToSet = cooldown; 327 } 328 329// ship.getSystem().setCooldown(0.2f); 330// ship.getSystem().setCooldownRemaining(0.2f); 331 } 332 333 public void unapply(MutableShipStatsAPI stats, String id) { 334 } 335 336 public StatusData getStatusData(int index, State state, float effectLevel) { 337 return null; 338 } 339 340 @Override 341 public String getInfoText(ShipSystemAPI system, ShipAPI ship) { 342 if (system.isOutOfAmmo()) return null; 343 if (system.getState() != SystemState.IDLE) return null; 344 345 ShipAPI target = findTarget(ship); 346 if (target != null && target != ship) { 347 return "READY"; 348 } 349 if ((target == null || target == ship) && ship.getShipTarget() != null) { 350 return "OUT OF RANGE"; 351 } 352 return "NO TARGET"; 353 } 354 355 public boolean isInRange(ShipAPI ship, ShipAPI target) { 356 float range = getRange(ship); 357 float dist = Misc.getDistance(ship.getLocation(), target.getLocation()); 358 float radSum = ship.getCollisionRadius() + target.getCollisionRadius(); 359 return dist <= range + radSum; 360 } 361 362 public boolean isValidLashTarget(ShipAPI ship, ShipAPI other) { 363 if (other == null) return false; 364 if (other.isHulk() || other.getOwner() == 100) return false; 365 if (other.isShuttlePod()) return false; 366 if (other.hasTag(ThreatShipConstructionScript.SHIP_UNDER_CONSTRUCTION)) return false; 367 if (other.isFighter() && other.getOwner() == ship.getOwner()) { 368 return ThreatSwarmAI.isAttackSwarm(other) && sinceSwarmTargeted > SWARM_TIMEOUT; 369 } 370 371 if (other.isFighter()) return false; 372 if (other.getOwner() == ship.getOwner()) { 373 if (other.getSystem() == null) return false; 374 if (!(other.getSystem().getScript() instanceof EnergyLashActivatedSystem)) return false; 375 if (other.getSystem().getCooldownRemaining() > 0) return false; 376 if (other.getSystem().isActive()) return false; 377 if (other.getFluxTracker().isOverloadedOrVenting()) return false; 378 } 379 return true; 380 //return !other.isFighter(); 381 } 382 383 384 protected ShipAPI findTarget(ShipAPI ship) { 385 float range = getRange(ship); 386 boolean player = ship == Global.getCombatEngine().getPlayerShip(); 387 ShipAPI target = ship.getShipTarget(); 388 389 float extraRange = 0f; 390 if (ship.getShipAI() != null && ship.getAIFlags().hasFlag(AIFlags.CUSTOM1)){ 391 target = (ShipAPI) ship.getAIFlags().getCustom(AIFlags.CUSTOM1); 392 extraRange += 500f; 393 } 394 395 396 if (target != null) { 397 float dist = Misc.getDistance(ship.getLocation(), target.getLocation()); 398 float radSum = ship.getCollisionRadius() + target.getCollisionRadius(); 399 if (dist > range + radSum + extraRange) target = null; 400 } else { 401 FindShipFilter filter = s -> isValidLashTarget(ship, s); 402 403 if (target == null || target.getOwner() == ship.getOwner()) { 404 if (player) { 405 target = Misc.findClosestShipTo(ship, ship.getMouseTarget(), HullSize.FIGHTER, range, true, false, filter); 406 } else { 407 Object test = ship.getAIFlags().getCustom(AIFlags.MANEUVER_TARGET); 408 if (test instanceof ShipAPI) { 409 target = (ShipAPI) test; 410 float dist = Misc.getDistance(ship.getLocation(), target.getLocation()); 411 float radSum = ship.getCollisionRadius() + target.getCollisionRadius(); 412 if (dist > range + radSum) target = null; 413 } 414 } 415 } 416 if (target == null) { 417 target = Misc.findClosestShipTo(ship, ship.getLocation(), HullSize.FIGHTER, range, true, false, filter); 418 } 419 } 420 421 return target; 422 } 423 424 @Override 425 public boolean isUsable(ShipSystemAPI system, ShipAPI ship) { 426 ShipAPI target = findTarget(ship); 427 return target != null && target != ship; 428 //return super.isUsable(system, ship); 429 } 430 431 public static float getRange(ShipAPI ship) { 432 if (ship == null) return MAX_LASH_RANGE; 433 return ship.getMutableStats().getSystemRangeBonus().computeEffective(MAX_LASH_RANGE); 434 } 435 436} 437 438 439 440 441 442 443 444