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