001package com.fs.starfarer.api.impl.combat;
002
003import java.util.ArrayList;
004import java.util.EnumSet;
005import java.util.Iterator;
006import java.util.List;
007
008import java.awt.Color;
009
010import org.lwjgl.util.vector.Vector2f;
011
012import com.fs.starfarer.api.Global;
013import com.fs.starfarer.api.combat.BaseCombatLayeredRenderingPlugin;
014import com.fs.starfarer.api.combat.CombatEngineAPI;
015import com.fs.starfarer.api.combat.CombatEngineLayers;
016import com.fs.starfarer.api.combat.CombatEntityAPI;
017import com.fs.starfarer.api.combat.DamagingProjectileAPI;
018import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin;
019import com.fs.starfarer.api.combat.OnFireEffectPlugin;
020import com.fs.starfarer.api.combat.OnHitEffectPlugin;
021import com.fs.starfarer.api.combat.ShipAPI;
022import com.fs.starfarer.api.combat.ViewportAPI;
023import com.fs.starfarer.api.combat.WeaponAPI;
024import com.fs.starfarer.api.combat.listeners.ApplyDamageResultAPI;
025import com.fs.starfarer.api.graphics.SpriteAPI;
026import com.fs.starfarer.api.util.FaderUtil;
027import com.fs.starfarer.api.util.Misc;
028
029/**
030 * The effect for the Cryoflamer, which was at one point called "Cryoflux...".
031 * Not related to the Cryoflux Transducer ship system.
032 * 
033 * IMPORTANT: will be multiple instances of this, as this doubles as the every frame effect and the on fire effect (same instance)
034 * But also as the visual for each individual shot (created via onFire, using the non-default constructor)
035 */
036public class CryofluxTransducerEffect extends BaseCombatLayeredRenderingPlugin implements OnFireEffectPlugin,
037                                                                                                                                                                        OnHitEffectPlugin,
038                                                                                                                                                                        EveryFrameWeaponEffectPlugin {
039
040        public CryofluxTransducerEffect() {
041        }
042        
043        protected String getLoopId() {
044                return "cryoflamer_loop";
045        }
046        
047        protected void playImpactSound(ApplyDamageResultAPI damageResult, Vector2f point, Vector2f vel) {
048                Misc.playSound(damageResult, point, vel,
049                                "cryoflamer_hit_shield_light",
050                                "cryoflamer_hit_shield_solid",
051                                "cryoflamer_hit_shield_heavy",
052                                "cryoflamer_hit_light",
053                                "cryoflamer_hit_solid",
054                                "cryoflamer_hit_heavy");
055        }
056        
057        protected String getParticleSpriteCat() {
058                return "misc";
059        }
060        protected String getParticleSpriteKey() {
061                return "nebula_particles";
062        }
063        protected float getParticleScale() {
064                return 1f;
065        }
066        protected float getParticleScaleIncreaseRateMult() {
067                return 1f;
068        }
069        protected int getNumParticles() {
070                return 7;
071        }
072        protected float getThresholdDist() {
073                return 20f;
074        }
075        
076        protected List<CryofluxTransducerEffect> trails;
077        public void advance(float amount, CombatEngineAPI engine, WeaponAPI weapon) {
078                if (trails == null) return;
079                
080                Iterator<CryofluxTransducerEffect> iter = trails.iterator();
081                while (iter.hasNext()) {
082                        if (iter.next().isExpired()) iter.remove();
083                }
084                
085                // sound loop playback
086                if (weapon.getShip() != null) {
087                        float maxRange = weapon.getRange();
088                        ShipAPI ship = weapon.getShip();
089                        Vector2f com = new Vector2f();
090                        float weight = 0f;
091                        float totalDist = 0f;
092                        Vector2f source = weapon.getLocation();
093                        for (CryofluxTransducerEffect curr : trails) {
094                                if (curr.proj != null) {
095                                        Vector2f.add(com, curr.proj.getLocation(), com);
096                                        weight += curr.proj.getBrightness();
097                                        totalDist += Misc.getDistance(source, curr.proj.getLocation());
098                                }
099                        }
100                        if (weight > 0.1f) {
101                                com.scale(1f / weight);
102                                float volume = Math.min(weight, 1f);
103                                if (trails.size() > 0) {
104                                        totalDist /= (float) trails.size();
105                                        float mult = totalDist / Math.max(maxRange, 1f);
106                                        mult = 1f - mult;
107                                        if (mult > 1f) mult = 1f;
108                                        if (mult < 0f) mult = 0f;
109                                        mult = (float) Math.sqrt(mult);
110                                        volume *= mult;
111                                }
112                                Global.getSoundPlayer().playLoop(getLoopId(), ship, 1f, volume, com, ship.getVelocity());
113                        }
114                }
115                
116                
117                //System.out.println("Trails: " + trails.size());
118                float numIter = 1f; // more doesn't actually change anything
119                amount /= numIter;
120                float thresholdDist = getThresholdDist();
121                //thresholdDist = 50f;
122                // drag along the previous projectile, starting with the most recently launched; new ones are added at the start
123                // note: prev is fired before and so is in front of proj
124                for (int i = 0; i < numIter; i++) { 
125                        for (CryofluxTransducerEffect trail : trails) {
126                                //trail.proj.setFacing(trail.proj.getFacing() + 180f * amount);
127                                if (trail.prev != null && !trail.prev.isExpired() && Global.getCombatEngine().isEntityInPlay(trail.prev)) {
128                                        float dist1 = Misc.getDistance(trail.prev.getLocation(), trail.proj.getLocation());
129                                        if (dist1 < trail.proj.getProjectileSpec().getLength() * 1f) {
130                                                float maxSpeed = trail.prev.getMoveSpeed() * 0.5f;// * Math.max(0.5f, 1f - trail.prev.getElapsed() * 0.5f);
131                                                // goal here is to prevent longer shot series (e.g. from Paragon) from moving too unnaturally
132                                                float e = trail.prev.getElapsed();
133                                                float t = 0.5f;
134                                                if (e > t) {
135                                                        maxSpeed *= Math.max(0.25f, 1f - (e - t) * 0.5f);
136                                                }
137                                                if (dist1 < thresholdDist && e > t) {
138                                                        maxSpeed *= dist1 / thresholdDist;
139                                                }
140                                                
141                                                Vector2f driftTo = Misc.closestPointOnLineToPoint(trail.proj.getLocation(), trail.proj.getTailEnd(), trail.prev.getLocation());
142                                                float dist = Misc.getDistance(driftTo, trail.prev.getLocation());
143                                                Vector2f diff = Vector2f.sub(driftTo, trail.prev.getLocation(), new Vector2f());
144                                                diff = Misc.normalise(diff);
145                                                diff.scale(Math.min(dist, maxSpeed * amount));
146                                                Vector2f.add(trail.prev.getLocation(), diff, trail.prev.getLocation());
147                                                Vector2f.add(trail.prev.getTailEnd(), diff, trail.prev.getTailEnd());
148                                        }
149                                }
150                        }
151                }
152        }
153        
154        
155        public void onHit(DamagingProjectileAPI projectile, CombatEntityAPI target, Vector2f point, boolean shieldHit, ApplyDamageResultAPI damageResult, CombatEngineAPI engine) {
156                Color color = projectile.getProjectileSpec().getFringeColor();
157//              Color inverted = NSLanceEffect.getColorForDarkening(color);
158//              inverted = Misc.setAlpha(inverted, 50);
159//              Color inverted = new Color(255, 255, 100, 50);
160                
161                Vector2f vel = new Vector2f();
162                if (target instanceof ShipAPI) {
163                        vel.set(target.getVelocity());
164                }
165                
166                float size = projectile.getProjectileSpec().getWidth() * 1f;
167                //size = Misc.getHitGlowSize(size, projectile.getDamage().getBaseDamage(), damageResult);
168                float sizeMult = Misc.getHitGlowSize(100f, projectile.getDamage().getBaseDamage(), damageResult) / 100f;
169//              sizeMult = 1.5f;
170//              System.out.println(sizeMult);
171                float dur = 1f;
172                float rampUp = 0f;
173                Color c = Misc.scaleAlpha(color, projectile.getBrightness());
174                engine.addNebulaParticle(point, vel, size, 5f + 3f * sizeMult,
175                                                                                rampUp, 0f, dur, c);
176//              engine.addNegativeNebulaParticle(point, vel, size, 2f,
177//                                                                              rampUp, 0f, dur, inverted);
178//              engine.addNegativeParticle(point, vel, size,
179//                                                                 rampUp, dur, inverted);
180                
181                playImpactSound(damageResult, point, vel);
182        }
183        
184        public void onFire(DamagingProjectileAPI projectile, WeaponAPI weapon, CombatEngineAPI engine) {
185                String prevKey = "cryo_prev_" + weapon.getShip().getId() + "_" + weapon.getSlot().getId();
186                DamagingProjectileAPI prev = (DamagingProjectileAPI) engine.getCustomData().get(prevKey);
187                
188                CryofluxTransducerEffect trail = createTrail(projectile, prev);
189                CombatEntityAPI e = engine.addLayeredRenderingPlugin(trail);
190                e.getLocation().set(projectile.getLocation());
191                
192                engine.getCustomData().put(prevKey, projectile);
193                
194                if (trails == null) {
195                        trails = new ArrayList<CryofluxTransducerEffect>();
196                }
197                trails.add(0, trail);
198        }
199        
200        protected CryofluxTransducerEffect createTrail(DamagingProjectileAPI projectile, DamagingProjectileAPI prev) {
201                return new CryofluxTransducerEffect(projectile, prev);
202        }
203        
204        
205        public static class ParticleData {
206                public SpriteAPI sprite;
207                public Vector2f offset = new Vector2f();
208                public Vector2f vel = new Vector2f();
209                public float scale = 1f;
210                public DamagingProjectileAPI proj;
211                public float scaleIncreaseRate = 1f;
212                public float turnDir = 1f;
213                public float angle = 1f;
214                public FaderUtil fader;
215                
216                public ParticleData(DamagingProjectileAPI proj, CryofluxTransducerEffect effect) {
217                        this.proj = proj;
218                        sprite = Global.getSettings().getSprite(effect.getParticleSpriteCat(), effect.getParticleSpriteKey());
219                        //sprite = Global.getSettings().getSprite("misc", "dust_particles");
220                        float i = Misc.random.nextInt(4);
221                        float j = Misc.random.nextInt(4);
222                        sprite.setTexWidth(0.25f);
223                        sprite.setTexHeight(0.25f);
224                        sprite.setTexX(i * 0.25f);
225                        sprite.setTexY(j * 0.25f);
226                        sprite.setAdditiveBlend();
227                        
228                        angle = (float) Math.random() * 360f;
229                        
230                        float maxDur = proj.getWeapon().getRange() / proj.getWeapon().getProjectileSpeed();
231                        scaleIncreaseRate = 2f / maxDur;
232                        scaleIncreaseRate *= effect.getParticleScaleIncreaseRateMult();
233                        scale = effect.getParticleScale();
234                        //scale = 2f;
235//                      scale = 0.1f;
236//                      scaleIncreaseRate = 2.9f / maxDur;
237//                      scale = 0.1f;
238//                      scaleIncreaseRate = 2.5f / maxDur;
239//                      scale = 0.5f;
240                        
241                        turnDir = Math.signum((float) Math.random() - 0.5f) * 60f * (float) Math.random();
242                        //turnDir = 0f;
243                        
244                        float driftDir = (float) Math.random() * 360f;
245                        vel = Misc.getUnitVectorAtDegreeAngle(driftDir);
246                        vel.scale(proj.getProjectileSpec().getWidth() / maxDur * 0.33f);
247                        
248//                      offset.x += vel.x * 1f;
249//                      offset.y += vel.y * 1f;
250                        fader = new FaderUtil(0f, 0.25f, 0.5f);
251                        fader.fadeIn();
252                }
253                
254                public void advance(float amount) {
255                        scale += scaleIncreaseRate * amount;
256                        if (scale < 1f) {
257                                scale += scaleIncreaseRate * amount * 1f;
258                        }
259                        
260                        offset.x += vel.x * amount;
261                        offset.y += vel.y * amount;
262                        
263                        angle += turnDir * amount;
264                        
265                        fader.advance(amount);
266                }
267        }
268        
269        protected List<ParticleData> particles = new ArrayList<ParticleData>();
270        
271        protected DamagingProjectileAPI proj;
272        protected DamagingProjectileAPI prev;
273        protected float baseFacing = 0f;
274        public CryofluxTransducerEffect(DamagingProjectileAPI proj, DamagingProjectileAPI prev) {
275                this.proj = proj;
276                this.prev = prev;
277                
278                baseFacing = proj.getFacing();
279                
280                int num = getNumParticles();
281                for (int i = 0; i < num; i++) {
282                        particles.add(new ParticleData(proj, this));
283                }
284                
285                float length = proj.getProjectileSpec().getLength();
286                float width = proj.getProjectileSpec().getWidth();
287                
288                float index = 0;
289                for (ParticleData p : particles) {
290                        float f = index / (particles.size() - 1);
291                        Vector2f dir = Misc.getUnitVectorAtDegreeAngle(proj.getFacing() + 180f);
292                        dir.scale(length * f);
293                        Vector2f.add(p.offset, dir, p.offset);
294                        
295                        p.offset = Misc.getPointWithinRadius(p.offset, width * 0.5f);
296                        //p.scale = 0.25f + 0.75f * (1 - f);
297                        
298                        index++;
299                }
300        }
301        
302        public float getRenderRadius() {
303                return 300f;
304        }
305        
306        
307        protected EnumSet<CombatEngineLayers> layers = EnumSet.of(CombatEngineLayers.ABOVE_SHIPS_AND_MISSILES_LAYER);
308        @Override
309        public EnumSet<CombatEngineLayers> getActiveLayers() {
310                return layers;
311        }
312
313
314        public void init(CombatEntityAPI entity) {
315                super.init(entity);
316        }
317        
318        public void advance(float amount) {
319                if (Global.getCombatEngine().isPaused()) return;
320                
321                entity.getLocation().set(proj.getLocation());
322                
323                for (ParticleData p : particles) {
324                        p.advance(amount);
325                }
326        }
327
328
329        public boolean isExpired() {
330                return proj.isExpired() || !Global.getCombatEngine().isEntityInPlay(proj);
331        }
332        
333        public Color getParticleColor() {
334                Color color = proj.getProjectileSpec().getFringeColor();
335                color = Misc.setAlpha(color, 50);
336                return color;
337        }
338
339        public void render(CombatEngineLayers layer, ViewportAPI viewport) {
340                float x = entity.getLocation().x;
341                float y = entity.getLocation().y;
342        
343                //Color color = new Color(100,150,255,50);
344                Color color = getParticleColor();
345                float b = proj.getBrightness();
346                b *= viewport.getAlphaMult();
347                
348                for (ParticleData p : particles) {
349                        float size = proj.getProjectileSpec().getWidth() * 0.6f;
350                        size *= p.scale;
351                        
352                        float alphaMult = 1f;
353                        Vector2f offset = p.offset;
354                        float diff = Misc.getAngleDiff(baseFacing, proj.getFacing());
355                        if (Math.abs(diff) > 0.1f) {
356                                offset = Misc.rotateAroundOrigin(offset, diff);
357                        }
358                        Vector2f loc = new Vector2f(x + offset.x, y + offset.y);
359                        
360                        p.sprite.setAngle(p.angle);
361                        p.sprite.setSize(size, size);
362                        p.sprite.setAlphaMult(b * alphaMult * p.fader.getBrightness());
363                        p.sprite.setColor(color);
364                        p.sprite.renderAtCenter(loc.x, loc.y);
365                }
366        }
367
368}
369
370
371
372