001package com.fs.starfarer.api.impl.combat; 002 003import java.util.Iterator; 004import java.util.List; 005 006import java.awt.Color; 007 008import org.lwjgl.util.vector.Vector2f; 009 010import com.fs.starfarer.api.GameState; 011import com.fs.starfarer.api.Global; 012import com.fs.starfarer.api.combat.CollisionClass; 013import com.fs.starfarer.api.combat.CombatEngineAPI; 014import com.fs.starfarer.api.combat.CombatEngineLayers; 015import com.fs.starfarer.api.combat.CombatEntityAPI; 016import com.fs.starfarer.api.combat.DamageType; 017import com.fs.starfarer.api.combat.DamagingProjectileAPI; 018import com.fs.starfarer.api.combat.EmpArcEntityAPI; 019import com.fs.starfarer.api.combat.MissileAPI; 020import com.fs.starfarer.api.combat.ShipAPI; 021import com.fs.starfarer.api.combat.ViewportAPI; 022import com.fs.starfarer.api.combat.WeaponAPI; 023import com.fs.starfarer.api.combat.listeners.AdvanceableListener; 024import com.fs.starfarer.api.util.IntervalUtil; 025import com.fs.starfarer.api.util.Misc; 026 027/** 028 * IMPORTANT: will be multiple instances of this, as this doubles as the every frame effect and the on fire effect (same instance) 029 * But also as the visual for each individual shot (created via onFire, using the non-default constructor) 030 */ 031public class RealityDisruptorChargeGlow extends CombatEntityPluginWithParticles { 032 033 public static enum EMPArcHitType { 034 SOURCE, 035 DEST, 036 DEST_NO_TARGET, 037 INIMICAL_EMANATION, 038 } 039 040 public static float ARC_RATE_MULT = 2f; 041 042 //public static int MAX_ARC_RANGE = 300; 043 public static int MAX_ARC_RANGE = 600; 044 //public static int ARCS_ON_HIT = 15; 045 046 public static float REPAIR_RATE_MULT = 0.5f; 047 public static float REPAIR_RATE_DEBUFF_DUR = 5f; 048 049 public static Color UNDERCOLOR = RiftCascadeEffect.EXPLOSION_UNDERCOLOR; 050 public static Color RIFT_COLOR = RiftCascadeEffect.STANDARD_RIFT_COLOR; 051 052 053 public static Object STATUS_KEY = new Object(); 054 055 056 public static class RDRepairRateDebuff implements AdvanceableListener { 057 public static String DEBUFF_ID = "reality_disruptor_repair_debuff"; 058 059 public ShipAPI ship; 060 public float dur = REPAIR_RATE_DEBUFF_DUR; 061 public RDRepairRateDebuff(ShipAPI ship, float dur) { 062 this.ship = ship; 063 this.dur = dur; 064 065 ship.getMutableStats().getCombatEngineRepairTimeMult().modifyMult(DEBUFF_ID, 1f/REPAIR_RATE_MULT); 066 ship.getMutableStats().getCombatWeaponRepairTimeMult().modifyMult(DEBUFF_ID, 1f/REPAIR_RATE_MULT); 067 } 068 069 public void resetDur(float dur) { 070 //dur = REPAIR_RATE_DEBUFF_DUR; 071 this.dur = Math.max(this.dur, dur); 072 } 073 074 public void advance(float amount) { 075 dur -= amount; 076 077 if (Global.getCurrentState() == GameState.COMBAT && 078 Global.getCombatEngine() != null && Global.getCombatEngine().getPlayerShip() == ship) { 079 Global.getCombatEngine().maintainStatusForPlayerShip(STATUS_KEY, 080 Global.getSettings().getSpriteName("ui", "icon_tactical_reality_disruptor"), 081 "REALITY DISRUPTED", "SLOWER REPAIRS: " + (int)Math.max(1, Math.round(dur)) + " SEC", true); 082 } 083 084 if (dur <= 0) { 085 ship.removeListener(this); 086 ship.getMutableStats().getCombatEngineRepairTimeMult().unmodify(DEBUFF_ID); 087 ship.getMutableStats().getCombatWeaponRepairTimeMult().unmodify(DEBUFF_ID); 088 } 089 } 090 } 091 092 093 094 protected WeaponAPI weapon; 095 protected DamagingProjectileAPI proj; 096 protected IntervalUtil interval = new IntervalUtil(0.1f, 0.2f); 097 protected IntervalUtil arcInterval = new IntervalUtil(0.17f, 0.23f); 098 protected float delay = 1f; 099 100 public RealityDisruptorChargeGlow(WeaponAPI weapon) { 101 super(); 102 this.weapon = weapon; 103 arcInterval = new IntervalUtil(0.17f, 0.23f); 104 delay = 0.5f; 105 setSpriteSheetKey("fx_particles2"); 106 } 107 108 public void attachToProjectile(DamagingProjectileAPI proj) { 109 this.proj = proj; 110 } 111 112 public void advance(float amount) { 113 if (Global.getCombatEngine().isPaused()) return; 114 if (proj != null) { 115 entity.getLocation().set(proj.getLocation()); 116 } else { 117 entity.getLocation().set(weapon.getFirePoint(0)); 118 } 119 super.advance(amount); 120 121 boolean keepSpawningParticles = isWeaponCharging(weapon) || 122 (proj != null && !isProjectileExpired(proj) && !proj.isFading()); 123 if (keepSpawningParticles) { 124 interval.advance(amount); 125 if (interval.intervalElapsed()) { 126 addChargingParticles(weapon); 127 } 128 } 129 130 if (proj != null && !isProjectileExpired(proj) && !proj.isFading()) { 131 delay -= amount; 132 if (delay <= 0) { 133 arcInterval.advance(amount * ARC_RATE_MULT); 134 if (arcInterval.intervalElapsed()) { 135 spawnArc(); 136 } 137 } 138 } 139 if (proj != null) { 140 Global.getSoundPlayer().playLoop("realitydisruptor_loop", proj, 1f, 1f * proj.getBrightness(), 141 proj.getLocation(), proj.getVelocity()); 142 } 143 144// if (proj != null) { 145// proj.setFacing(proj.getFacing() + 30f * amount); 146// } 147 } 148 149 @Override 150 public void render(CombatEngineLayers layer, ViewportAPI viewport) { 151 // pass in proj as last argument to have particles rotate 152 super.render(layer, viewport, null); 153 } 154 155 public boolean isExpired() { 156 boolean keepSpawningParticles = isWeaponCharging(weapon) || 157 (proj != null && !isProjectileExpired(proj) && !proj.isFading()); 158 return super.isExpired() && (!keepSpawningParticles || (!weapon.getShip().isAlive() && proj == null)); 159 } 160 161 162 public float getRenderRadius() { 163 return 500f; 164 } 165 166 167 @Override 168 protected float getGlobalAlphaMult() { 169 if (proj != null && proj.isFading()) { 170 return proj.getBrightness(); 171 } 172 return super.getGlobalAlphaMult(); 173 } 174 175 176 177 public void spawnArc() { 178 CombatEngineAPI engine = Global.getCombatEngine(); 179 180 float emp = proj.getEmpAmount(); 181 float dam = proj.getDamageAmount(); 182 183 CombatEntityAPI target = findTarget(proj, weapon, engine); 184 float thickness = 20f; 185 float coreWidthMult = 0.67f; 186 Color color = weapon.getSpec().getGlowColor(); 187 //color = new Color(255,100,100,255); 188 if (target != null) { 189 EmpArcEntityAPI arc = engine.spawnEmpArc(proj.getSource(), proj.getLocation(), null, 190 target, 191 DamageType.ENERGY, 192 dam, 193 emp, // emp 194 100000f, // max range 195 "realitydisruptor_emp_impact", 196 thickness, // thickness 197 color, 198 new Color(255,255,255,255) 199 ); 200 arc.setCoreWidthOverride(thickness * coreWidthMult); 201 202 spawnEMPParticles(EMPArcHitType.SOURCE, proj, proj.getLocation(), null); 203 spawnEMPParticles(EMPArcHitType.DEST, proj, arc.getTargetLocation(), target); 204 205 if (target instanceof ShipAPI) { 206 ShipAPI s = (ShipAPI) target; 207 List<RDRepairRateDebuff> listeners = s.getListeners(RDRepairRateDebuff.class); 208 if (listeners.isEmpty()) { 209 s.addListener(new RDRepairRateDebuff(s, REPAIR_RATE_DEBUFF_DUR)); 210 } else { 211 listeners.get(0).resetDur(REPAIR_RATE_DEBUFF_DUR); 212 } 213 } 214 215 } else { 216 Vector2f from = new Vector2f(proj.getLocation()); 217 Vector2f to = pickNoTargetDest(proj, weapon, engine); 218 EmpArcEntityAPI arc = engine.spawnEmpArcVisual(from, null, to, null, thickness, color, Color.white); 219 arc.setCoreWidthOverride(thickness * coreWidthMult); 220 Global.getSoundPlayer().playSound("realitydisruptor_emp_impact", 1f, 1f, to, new Vector2f()); 221 222 spawnEMPParticles(EMPArcHitType.SOURCE, proj, from, null); 223 spawnEMPParticles(EMPArcHitType.DEST_NO_TARGET, proj, to, null); 224 } 225 } 226 227 228 229 public Vector2f pickNoTargetDest(DamagingProjectileAPI projectile, WeaponAPI weapon, CombatEngineAPI engine) { 230 float range = 200f; 231 Vector2f from = projectile.getLocation(); 232 Vector2f dir = Misc.getUnitVectorAtDegreeAngle((float) Math.random() * 360f); 233 dir.scale(range); 234 Vector2f.add(from, dir, dir); 235 dir = Misc.getPointWithinRadius(dir, range * 0.25f); 236 return dir; 237 } 238 239 public CombatEntityAPI findTarget(DamagingProjectileAPI projectile, WeaponAPI weapon, CombatEngineAPI engine) { 240 float range = MAX_ARC_RANGE; 241 Vector2f from = projectile.getLocation(); 242 243 Iterator<Object> iter = Global.getCombatEngine().getAllObjectGrid().getCheckIterator(from, 244 range * 2f, range * 2f); 245 int owner = weapon.getShip().getOwner(); 246 CombatEntityAPI best = null; 247 float minScore = Float.MAX_VALUE; 248 while (iter.hasNext()) { 249 Object o = iter.next(); 250 if (!(o instanceof MissileAPI) && 251 //!(o instanceof CombatAsteroidAPI) && 252 !(o instanceof ShipAPI)) continue; 253 CombatEntityAPI other = (CombatEntityAPI) o; 254 if (other.getOwner() == owner) continue; 255 256 if (other instanceof ShipAPI) { 257 ShipAPI otherShip = (ShipAPI) other; 258 if (otherShip.isHulk()) continue; 259 if (otherShip.isPhased()) continue; 260 if (!otherShip.isTargetable()) continue; 261 } 262 if (other.getCollisionClass() == CollisionClass.NONE) continue; 263 264 float radius = Misc.getTargetingRadius(from, other, false); 265 float dist = Misc.getDistance(from, other.getLocation()) - radius - 50f; 266 if (dist > range) continue; 267 268 //float angleTo = Misc.getAngleInDegrees(from, other.getLocation()); 269 //float score = Misc.getAngleDiff(weapon.getCurrAngle(), angleTo); 270 float score = dist; 271 272 if (score < minScore) { 273 minScore = score; 274 best = other; 275 } 276 } 277 return best; 278 } 279 280 public void addChargingParticles(WeaponAPI weapon) { 281 //CombatEngineAPI engine = Global.getCombatEngine(); 282 Color color = RiftLanceEffect.getColorForDarkening(RIFT_COLOR); 283 284// float b = 1f; 285// color = Misc.scaleAlpha(color, b); 286 //undercolor = Misc.scaleAlpha(undercolor, b); 287 288 float size = 50f; 289 float underSize = 75f; 290 //underSize = 100f; 291 292 float in = 0.25f; 293 float out = 0.75f; 294 295 out *= 3f; 296 297 float velMult = 0.2f; 298 299 if (isWeaponCharging(weapon)) { 300 size *= 0.25f + weapon.getChargeLevel() * 0.75f; 301 } 302 303 addDarkParticle(size, in, out, 1f, size * 0.5f * velMult, 0f, color); 304 randomizePrevParticleLocation(size * 0.33f); 305 306 if (proj != null) { 307 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(proj.getFacing() + 180f); 308 //size = 40f; 309 if (proj.getElapsed() > 0.2f) { 310 addDarkParticle(size, in, out, 1.5f, size * 0.5f * velMult, 0f, color); 311 Vector2f offset = new Vector2f(dir); 312 offset.scale(size * 0.6f + (float) Math.random() * 0.2f); 313 Vector2f.add(prev.offset, offset, prev.offset); 314 } 315 if (proj.getElapsed() > 0.4f) { 316 addDarkParticle(size * 1f, in, out, 1.3f, size * 0.5f * velMult, 0f, color); 317 Vector2f offset = new Vector2f(dir); 318 offset.scale(size * 1.2f + (float) Math.random() * 0.2f); 319 Vector2f.add(prev.offset, offset, prev.offset); 320 } 321 if (proj.getElapsed() > 0.6f) { 322 addDarkParticle(size * .8f, in, out, 1.1f, size * 0.5f * velMult, 0f, color); 323 Vector2f offset = new Vector2f(dir); 324 offset.scale(size * 1.6f + (float) Math.random() * 0.2f); 325 Vector2f.add(prev.offset, offset, prev.offset); 326 } 327 328 if (proj.getElapsed() > 0.8f) { 329 addDarkParticle(size * .8f, in, out, 1.1f, size * 0.5f * velMult, 0f, color); 330 Vector2f offset = new Vector2f(dir); 331 offset.scale(size * 2.0f + (float) Math.random() * 0.2f); 332 Vector2f.add(prev.offset, offset, prev.offset); 333 } 334// int num = (int) Math.round(proj.getElapsed() / 0.5f * 10f); 335// if (num > 15) num = 15; 336// for (int i = 0; i < num; i++) { 337// addDarkParticle(size, in, out, 1f, size * 0.5f, 0f, color); 338// Vector2f offset = new Vector2f(dir); 339// offset.scale(size * 0.1f * i); 340// Vector2f.add(prev.offset, offset, prev.offset); 341// } 342 } 343 344// UNDERCOLOR = new Color(100, 0, 100, 100); 345// UNDERCOLOR = NSProjEffect.EXPLOSION_UNDERCOLOR; 346 347 // defaults: 348 //public static Color EXPLOSION_UNDERCOLOR = new Color(100, 0, 25, 100); 349 //public static Color STANDARD_RIFT_COLOR = new Color(100,60,255,255); 350 351 //"glowColor":[100,200,255,255], #ion cannon 352// RIFT_COLOR = new Color(100, 200, 255, 255); 353 354// UNDERCOLOR = NSProjEffect.EXPLOSION_UNDERCOLOR; 355// RIFT_COLOR = NSProjEffect.STANDARD_RIFT_COLOR; 356 357// UNDERCOLOR = new Color(255, 0, 25, 100); 358// UNDERCOLOR = new Color(100, 0, 25, 100); 359 360 addParticle(underSize * 0.5f, in, out, 1.5f * 3f, 0f, 0f, UNDERCOLOR); 361 randomizePrevParticleLocation(underSize * 0.67f); 362 addParticle(underSize * 0.5f, in, out, 1.5f * 3f, 0f, 0f, UNDERCOLOR); 363 randomizePrevParticleLocation(underSize * 0.67f); 364 365// float facing = weapon.getCurrAngle(); 366// if (proj != null) facing = proj.getFacing(); 367// Vector2f dir = Misc.getUnitVectorAtDegreeAngle(facing + 210f * ((float) Math.random() - 0.5f)); 368// dir.scale(underSize * 0.25f * (float) Math.random()); 369// Vector2f.add(prev.offset, dir, prev.offset); 370 } 371 372 public static void spawnEMPParticles(EMPArcHitType type, DamagingProjectileAPI proj, Vector2f point, CombatEntityAPI target) { 373 CombatEngineAPI engine = Global.getCombatEngine(); 374 375 Color color = RiftLanceEffect.getColorForDarkening(RIFT_COLOR); 376 377 float size = 30f; 378 float baseDuration = 1.5f; 379 Vector2f vel = new Vector2f(); 380 int numNegative = 5; 381 int numSwirly = 7; 382 switch (type) { 383 case DEST: 384 size = 50f; 385 vel.set(target.getVelocity()); 386 if (vel.length() > 100f) { 387 vel.scale(100f / vel.length()); 388 } 389 break; 390 case DEST_NO_TARGET: 391 break; 392 case INIMICAL_EMANATION: 393 numNegative = 5; 394 numSwirly = 7; 395 //numSwirly = 12; 396 size = 25; 397 color = RiftLanceEffect.getColorForDarkening(UNDERCOLOR); 398 baseDuration = 1f; 399 if (target != null && !(target instanceof MissileAPI)) { 400 vel.set(target.getVelocity()); 401// if (vel.length() > 100f) { 402// vel.scale(100f / vel.length()); 403// } 404 } 405 break; 406 case SOURCE: 407 size = 40f; 408 numNegative = 10; 409 break; 410 } 411 boolean inimical = type == EMPArcHitType.INIMICAL_EMANATION; 412 //dir.negate(); 413 //numNegative = 0; 414 for (int i = 0; i < numNegative; i++) { 415 float dur = baseDuration + baseDuration * (float) Math.random(); 416 //float nSize = size * (1f + 0.0f * (float) Math.random()); 417 //float nSize = size * (0.75f + 0.5f * (float) Math.random()); 418 float nSize = size; 419 if (type == EMPArcHitType.SOURCE) { 420 nSize *= 1.5f; 421 } 422 float scatterMult = 1f; 423 if (inimical) scatterMult = 0.25f; 424 Vector2f pt = Misc.getPointWithinRadius(point, nSize * 0.5f * scatterMult); 425 Vector2f v = Misc.getUnitVectorAtDegreeAngle((float) Math.random() * 360f); 426 v.scale(nSize + nSize * (float) Math.random() * 0.5f); 427 v.scale(0.2f); 428 429 float endSizeMult = 2f; 430 if (type == EMPArcHitType.SOURCE) { 431 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(proj.getFacing() + 180f); 432 pt = Misc.getPointWithinRadius(point, nSize * 0f); 433 Vector2f offset = new Vector2f(dir); 434 offset.scale(size * 0.2f * i); 435 Vector2f.add(pt, offset, pt); 436 endSizeMult = 1.5f; 437 v.scale(0.5f); 438 } 439 Vector2f.add(vel, v, v); 440 441 float maxSpeed = nSize * 1.5f * 0.2f; 442 float minSpeed = nSize * 1f * 0.2f; 443 float overMin = v.length() - minSpeed; 444 if (overMin > 0) { 445 float durMult = 1f - overMin / (maxSpeed - minSpeed); 446 if (durMult < 0.1f) durMult = 0.1f; 447 dur *= 0.5f + 0.5f * durMult; 448 } 449 450// if (type == EMPArcHitType.DEST || type == EMPArcHitType.DEST_NO_TARGET) { 451// v.set(0f, 0f); 452// } 453 454 float rampUp = 0.25f / dur; 455 if (inimical) { 456 rampUp = 0f; 457 //nSize *= 2f; 458 } 459 engine.addNegativeNebulaParticle(pt, v, nSize * 1f, endSizeMult, 460 //engine.addNegativeSwirlyNebulaParticle(pt, v, nSize * 1f, endSizeMult, 461 rampUp, 0f, dur, color); 462 } 463 464 float dur = baseDuration; 465 float rampUp = 0.5f / dur; 466 if (inimical) { 467 //numSwirly = 15; 468 //rampUp = 0.1f; 469 rampUp = 0f; 470 } 471 color = UNDERCOLOR; 472// if (inimical) { 473// color = DwellerShroud.SHROUD_COLOR; 474// } 475 for (int i = 0; i < numSwirly; i++) { 476 Vector2f loc = new Vector2f(point); 477 float scatterMult = 1f; 478 if (inimical) scatterMult = 0.5f; 479 loc = Misc.getPointWithinRadius(loc, size * 1f * scatterMult); 480 float s = size * 4f * (0.5f + (float) Math.random() * 0.5f); 481 if (inimical) { 482 //size *= 2f; 483 //size *= 1.25f; 484 //size *= 1.1f; 485 } 486 engine.addSwirlyNebulaParticle(loc, vel, s, 1.5f, rampUp, 0f, dur, color, false); 487 } 488 } 489 490 491 public static boolean isProjectileExpired(DamagingProjectileAPI proj) { 492 return proj.isExpired() || proj.didDamage() || !Global.getCombatEngine().isEntityInPlay(proj); 493 } 494 495 public static boolean isWeaponCharging(WeaponAPI weapon) { 496 return weapon.getChargeLevel() > 0 && weapon.getCooldownRemaining() <= 0; 497 } 498} 499 500 501 502 503 504