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