001package com.fs.starfarer.api.impl.combat; 002 003import java.awt.Color; 004 005import org.lwjgl.util.vector.Vector2f; 006 007import com.fs.starfarer.api.Global; 008import com.fs.starfarer.api.combat.BeamAPI; 009import com.fs.starfarer.api.combat.BeamEffectPlugin; 010import com.fs.starfarer.api.combat.BoundsAPI; 011import com.fs.starfarer.api.combat.BoundsAPI.SegmentAPI; 012import com.fs.starfarer.api.combat.CombatEngineAPI; 013import com.fs.starfarer.api.combat.CombatEntityAPI; 014import com.fs.starfarer.api.combat.EmpArcEntityAPI; 015import com.fs.starfarer.api.combat.MissileAPI; 016import com.fs.starfarer.api.combat.ShipAPI; 017import com.fs.starfarer.api.combat.WeaponAPI.WeaponType; 018import com.fs.starfarer.api.loading.MissileSpecAPI; 019import com.fs.starfarer.api.util.IntervalUtil; 020import com.fs.starfarer.api.util.Misc; 021 022/** 023 * Colors are: 024 * beam fringe color, for beam fringe and emp arcs 025 * beam glow color (beam weapon glow) 026 * mine glow color (border around core of explosion, also pings?) 027 * mine ping color (should be same as glow color) 028 * explosion undercolor (specified in code only) 029 * color subtracted around source of beam (code only) 030 */ 031public class RiftCascadeEffect implements BeamEffectPlugin { //WithReset { 032 033 public static Color STANDARD_RIFT_COLOR = new Color(100,60,255,255); 034 public static Color EXPLOSION_UNDERCOLOR = new Color(100, 0, 25, 100); 035 public static Color NEGATIVE_SOURCE_COLOR = new Color(200,255,200,25); 036 037 public static String RIFTCASCADE_MINELAYER = "riftcascade_minelayer"; 038 039 public static int MAX_RIFTS = 5; 040 public static float UNUSED_RANGE_PER_SPAWN = 200; 041 public static float SPAWN_SPACING = 175; 042 public static float SPAWN_INTERVAL = 0.1f; 043 044 045 046 protected Vector2f arcFrom = null; 047 protected Vector2f prevMineLoc = null; 048 049 protected boolean doneSpawningMines = false; 050 protected float spawned = 0; 051 protected int numToSpawn = 0; 052 protected float untilNextSpawn = 0; 053 protected float spawnDir = 0; 054 055 protected IntervalUtil tracker = new IntervalUtil(0.1f, 0.2f); 056 057 // re-instantiated when beam fires so this doesn't matter 058// public void reset() { 059// doneSpawningMines = false; 060// spawned = 0; 061// untilNextSpawn = 0; 062// arcFrom = null; 063// prevMineLoc = null; 064// numToSpawn = 0; 065// spawnDir = 0; 066// } 067 068 public RiftCascadeEffect() { 069 //System.out.println("23rwerefewfwe"); 070 } 071 072 073 public void advance(float amount, CombatEngineAPI engine, BeamAPI beam) { 074 tracker.advance(amount); 075 if (tracker.intervalElapsed()) { 076 spawnNegativeParticles(engine, beam); 077 } 078 079 if (beam.getBrightness() < 1f) return; 080 081 082 if (doneSpawningMines) return; 083 084 if (numToSpawn <= 0 && beam.getDamageTarget() != null) { 085 float range = beam.getWeapon().getRange(); 086 float length = beam.getLengthPrevFrame(); 087 //float perSpawn = range / NUM_SPAWNS; 088 numToSpawn = (int) ((range - length) / UNUSED_RANGE_PER_SPAWN) + 1; 089 if (numToSpawn > MAX_RIFTS) { 090 numToSpawn = MAX_RIFTS; 091 } 092 untilNextSpawn = 0f; 093 } 094 //numToSpawn = 5; 095 096// if (beam.getBrightness() >= 1f) { 097// canSpawn = true; 098// } 099 //untilNextSpawn = 0f; 100 101 untilNextSpawn -= amount; 102 if (untilNextSpawn > 0) return; 103// if (!canSpawn || beam.getBrightness() >= 1f) return; 104 105 float perSpawn = SPAWN_SPACING; 106 107 ShipAPI ship = beam.getSource(); 108 109 boolean spawnedMine = false; 110 if (beam.getLength() > beam.getWeapon().getRange() - 10f) { 111 float angle = Misc.getAngleInDegrees(beam.getFrom(), beam.getRayEndPrevFrame()); 112 Vector2f loc = Misc.getUnitVectorAtDegreeAngle(angle); 113 loc.scale(beam.getLength()); 114 Vector2f.add(loc, beam.getFrom(), loc); 115 116 spawnMine(ship, loc); 117 spawnedMine = true; 118 } else if (beam.getDamageTarget() != null) { 119 Vector2f arcTo = getNextArcLoc(engine, beam, perSpawn); 120 float thickness = beam.getWidth(); 121 //thickness = 20; 122 float dist = Misc.getDistance(arcFrom, arcTo); 123 if (dist < SPAWN_SPACING * 2f) { 124 EmpArcEntityAPI arc = engine.spawnEmpArcVisual(arcFrom, null, arcTo, null, thickness, beam.getFringeColor(), Color.white); 125 arc.setCoreWidthOverride(Math.max(20f, thickness * 0.67f)); 126 //Global.getSoundPlayer().playSound("tachyon_lance_emp_impact", 1f, 1f, arc.getLocation(), arc.getVelocity()); 127 } 128 spawnMine(ship, arcTo); 129 spawnedMine = true; 130 arcFrom = arcTo; 131 } 132 133 untilNextSpawn = SPAWN_INTERVAL; 134 if (spawnedMine) { 135 spawned++; 136 if (spawned >= numToSpawn) { 137 doneSpawningMines = true; 138 } 139 } 140 } 141 142 public void spawnNegativeParticles(CombatEngineAPI engine, BeamAPI beam) { 143 float length = beam.getLengthPrevFrame(); 144 if (length <= 10f) return; 145 146 //NEGATIVE_SOURCE_COLOR = new Color(200,255,200,25); 147 148 Vector2f from = beam.getFrom(); 149 Vector2f to = beam.getRayEndPrevFrame(); 150 151 ShipAPI ship = beam.getSource(); 152 153 float angle = Misc.getAngleInDegrees(from, to); 154 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(angle); 155// Vector2f perp1 = new Vector2f(-dir.y, dir.x); 156// Vector2f perp2 = new Vector2f(dir.y, -dir.x); 157 158 //Color color = new Color(150,255,150,25); 159 Color color = NEGATIVE_SOURCE_COLOR; 160 //color = Misc.setAlpha(color, 50); 161 162 float sizeMult = 1f; 163 sizeMult = 0.67f; 164 165 for (int i = 0; i < 3; i++) { 166 float rampUp = 0.25f + 0.25f * (float) Math.random(); 167 float dur = 1f + 1f * (float) Math.random(); 168 //dur *= 2f; 169 float size = 200f + 50f * (float) Math.random(); 170 size *= sizeMult; 171 //size *= 0.5f; 172 //Vector2f loc = Misc.getPointWithinRadius(from, size * 0.5f); 173 //Vector2f loc = Misc.getPointAtRadius(from, size * 0.33f); 174 Vector2f loc = Misc.getPointAtRadius(beam.getWeapon().getLocation(), size * 0.33f); 175 engine.addNegativeParticle(loc, ship.getVelocity(), size, rampUp / dur, dur, color); 176 //engine.addNegativeNebulaParticle(loc, ship.getVelocity(), size, 2f, rampUp, 0f, dur, color); 177 } 178 179 if (true) return; 180 181 // particles along the beam 182 float spawnOtherParticleRange = 100; 183 if (length > spawnOtherParticleRange * 2f && (float) Math.random() < 0.25f) { 184 //color = new Color(150,255,150,255); 185 color = new Color(150,255,150,75); 186 int numToSpawn = (int) ((length - spawnOtherParticleRange) / 200f + 1); 187 numToSpawn = 1; 188 for (int i = 0; i < numToSpawn; i++) { 189 float distAlongBeam = spawnOtherParticleRange + (length - spawnOtherParticleRange * 2f) * (float) Math.random(); 190 float groupSpeed = 100f + (float) Math.random() * 100f; 191 for (int j = 0; j < 7; j++) { 192 float rampUp = 0.25f + 0.25f * (float) Math.random(); 193 float dur = 1f + 1f * (float) Math.random(); 194 float size = 50f + 50f * (float) Math.random(); 195 Vector2f loc = new Vector2f(dir); 196 float sign = Math.signum((float) Math.random() - 0.5f); 197 loc.scale(distAlongBeam + sign * (float) Math.random() * size * 0.5f); 198 Vector2f.add(loc, from, loc); 199 200// Vector2f off = new Vector2f(perp1); 201// if ((float) Math.random() < 0.5f) off = new Vector2f(perp2); 202// 203// off.scale(size * 0.1f); 204 //Vector2f.add(loc, off, loc); 205 206 loc = Misc.getPointWithinRadius(loc, size * 0.25f); 207 208 float dist = Misc.getDistance(loc, to); 209 Vector2f vel = new Vector2f(dir); 210 if ((float) Math.random() < 0.5f) { 211 vel.negate(); 212 dist = Misc.getDistance(loc, from); 213 } 214 215 float speed = groupSpeed; 216 float maxSpeed = dist / dur; 217 if (speed > maxSpeed) speed = maxSpeed; 218 vel.scale(speed); 219 Vector2f.add(vel, ship.getVelocity(), vel); 220 221 engine.addNegativeParticle(loc, vel, size, rampUp, dur, color); 222 } 223 } 224 } 225 } 226 227 228 public Vector2f getNextArcLoc(CombatEngineAPI engine, BeamAPI beam, float perSpawn) { 229 CombatEntityAPI target = beam.getDamageTarget(); 230 float radiusOverride = -1f; 231 if (target instanceof ShipAPI) { 232 ShipAPI ship = (ShipAPI) target; 233 if (ship.getParentStation() != null && ship.getStationSlot() != null) { 234 //radiusOverride = Misc.getDistance(ship.getLocation(), ship.getParentStation().getLocation()); 235 //radiusOverride += ship.getCollisionRadius(); 236 radiusOverride = Misc.getDistance(beam.getRayEndPrevFrame(), ship.getParentStation().getLocation()) + 0f; 237 target = ship.getParentStation(); 238 } 239 } 240 241 if (arcFrom == null) { 242 arcFrom = new Vector2f(beam.getRayEndPrevFrame()); 243// Vector2f loc = Misc.getUnitVectorAtDegreeAngle(beamAngle); 244// loc.scale(beam.getLengthPrevFrame()); 245// //loc.scale(200f); 246// Vector2f.add(loc, beam.getFrom(), loc); 247// arcFrom = loc; 248 249 float beamAngle = Misc.getAngleInDegrees(beam.getFrom(), beam.getRayEndPrevFrame()); 250 float beamSourceToTarget = Misc.getAngleInDegrees(beam.getFrom(), target.getLocation()); 251 252 // this is the direction we'll rotate - from the target's center - so that it's spawning mines around the side 253 // closer to the beam's straight line 254 spawnDir = Misc.getClosestTurnDirection(beamAngle, beamSourceToTarget); 255 if (spawnDir == 0) spawnDir = 1; 256 257 boolean computeNextLoc = false; 258 if (prevMineLoc != null) { 259 float dist = Misc.getDistance(arcFrom, prevMineLoc); 260 if (dist < perSpawn) { 261 perSpawn -= dist; 262 computeNextLoc = true; 263 } 264 } 265 if (!computeNextLoc) { 266 return arcFrom; 267 } 268 } 269 270// target = Global.getCombatEngine().getPlayerShip(); 271// target.getLocation().y += 750f; 272 273 Vector2f targetLoc = target.getLocation(); 274 float targetRadius = target.getCollisionRadius(); 275 if (radiusOverride >= 0) { 276 targetRadius = radiusOverride; 277 } 278 279// if (target instanceof ShipAPI) { 280// ShipAPI ship = (ShipAPI) target; 281// targetLoc = ship.getShieldCenterEvenIfNoShield(); 282// targetRadius = ship.getShieldRadiusEvenIfNoShield(); 283// } 284 285 boolean hitShield = target.getShield() != null && target.getShield().isWithinArc(beam.getRayEndPrevFrame()); 286 if (hitShield) perSpawn *= 0.67f; 287// float beamAngle = Misc.getAngleInDegrees(beam.getFrom(), beam.getRayEndPrevFrame()); 288// float beamSourceToTarget = Misc.getAngleInDegrees(beam.getFrom(), targetLoc); 289// 290// // this is the direction we'll rotate - from the target's center - so that it's spawning mines around the side 291// // closer to the beam's straight line 292// float dir = Misc.getClosestTurnDirection(beamAngle, beamSourceToTarget); 293 294 float prevAngle = Misc.getAngleInDegrees(targetLoc, arcFrom); 295 float anglePerSegment = 360f * perSpawn / (3.14f * 2f * targetRadius); 296 if (anglePerSegment > 90f) anglePerSegment = 90f; 297 float angle = prevAngle + anglePerSegment * spawnDir; 298 299 300 Vector2f arcTo = Misc.getUnitVectorAtDegreeAngle(angle); 301 arcTo.scale(targetRadius); 302 Vector2f.add(targetLoc, arcTo, arcTo); 303 304 float actualRadius = Global.getSettings().getTargetingRadius(arcTo, target, hitShield); 305 if (radiusOverride >= 0) { 306 actualRadius = radiusOverride; 307 } 308 if (!hitShield) { 309 //actualRadius *= 1f + 0.1f * (float) Math.random(); 310 actualRadius += 30f + 50f * (float) Math.random(); 311 } else { 312// actualRadius = target.getShield().getRadius(); 313// actualRadius += 20f + 20f * (float) Math.random(); 314 actualRadius += 30f + 50f * (float) Math.random(); 315 } 316 317// float angleDiff = Misc.getAngleDiff(beamSourceToTarget + 180f, angle); 318// if (angleDiff > 150f) { 319// actualRadius += perSpawn * (180f - angleDiff) / 30f; 320// } 321 322 arcTo = Misc.getUnitVectorAtDegreeAngle(angle); 323 arcTo.scale(actualRadius); 324 Vector2f.add(targetLoc, arcTo, arcTo); 325 326 327 // now we've got an arcTo location somewhere roughly circular; try to cleave more closely to the hull 328 // if the target is a ship 329 if (target instanceof ShipAPI && !hitShield) { 330 ShipAPI ship = (ShipAPI) target; 331 BoundsAPI bounds = ship.getExactBounds(); 332 if (bounds != null) { 333 Vector2f best = null; 334 float bestDist = Float.MAX_VALUE; 335 for (SegmentAPI segment : bounds.getSegments()) { 336 float test = Misc.getDistance(segment.getP1(), arcTo); 337 if (test < bestDist) { 338 bestDist = test; 339 best = segment.getP1(); 340 } 341 } 342 if (best != null) { 343 Object o = Global.getSettings().getWeaponSpec(RIFTCASCADE_MINELAYER).getProjectileSpec(); 344 if (o instanceof MissileSpecAPI) { 345 MissileSpecAPI spec = (MissileSpecAPI) o; 346 float explosionRadius = (float) spec.getBehaviorJSON().optJSONObject("explosionSpec").optDouble("coreRadius", 100f); 347 float sizeMult = getSizeMult(); 348 explosionRadius *= sizeMult; 349 350 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(best, arcTo)); 351 dir.scale(explosionRadius * 0.9f); 352 Vector2f.add(best, dir, dir); 353 arcTo = dir; 354 355// dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(prevMineLoc, arcTo)); 356// dir.scale(perSpawn); 357// Vector2f.add(prevMineLoc, dir, dir); 358 } 359 } 360 } 361 362 } 363 364 return arcTo; 365 } 366 367 public float getSizeMult() { 368 float sizeMult = 1f - spawned / (float) Math.max(1, numToSpawn - 1); 369 sizeMult = 0.75f + (1f - sizeMult) * 0.5f; 370 //sizeMult = 0.5f + 0.5f * sizeMult; 371 return sizeMult; 372 } 373 374 public void spawnMine(ShipAPI source, Vector2f mineLoc) { 375 CombatEngineAPI engine = Global.getCombatEngine(); 376 377 MissileAPI mine = (MissileAPI) engine.spawnProjectile(source, null, 378 RIFTCASCADE_MINELAYER, 379 mineLoc, 380 (float) Math.random() * 360f, null); 381 382 // "spawned" does not include this mine 383 float sizeMult = getSizeMult(); 384 mine.setCustomData(RiftCascadeMineExplosion.SIZE_MULT_KEY, sizeMult); 385 386 if (source != null) { 387 Global.getCombatEngine().applyDamageModifiersToSpawnedProjectileWithNullWeapon( 388 source, WeaponType.ENERGY, false, mine.getDamage()); 389 } 390 391 mine.getDamage().getModifier().modifyMult("mine_sizeMult", sizeMult); 392 393 394 float fadeInTime = 0.05f; 395 mine.getVelocity().scale(0); 396 mine.fadeOutThenIn(fadeInTime); 397 398 //Global.getCombatEngine().addPlugin(createMissileJitterPlugin(mine, fadeInTime)); 399 400 //mine.setFlightTime((float) Math.random()); 401 float liveTime = 0f; 402 //liveTime = 0.01f; 403 mine.setFlightTime(mine.getMaxFlightTime() - liveTime); 404 mine.addDamagedAlready(source); 405 mine.setNoMineFFConcerns(true); 406 407 prevMineLoc = mineLoc; 408 } 409 410} 411 412 413 414 415