001package com.fs.starfarer.api.impl.combat;
002
003import java.util.ArrayList;
004import java.util.EnumSet;
005import java.util.List;
006
007import java.awt.Color;
008
009import org.lwjgl.opengl.GL14;
010import org.lwjgl.util.vector.Vector2f;
011
012import com.fs.starfarer.api.Global;
013import com.fs.starfarer.api.combat.ArmorGridAPI;
014import com.fs.starfarer.api.combat.BaseCombatLayeredRenderingPlugin;
015import com.fs.starfarer.api.combat.CombatEngineAPI;
016import com.fs.starfarer.api.combat.CombatEngineLayers;
017import com.fs.starfarer.api.combat.CombatEntityAPI;
018import com.fs.starfarer.api.combat.DamageType;
019import com.fs.starfarer.api.combat.DamagingProjectileAPI;
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.listeners.ApplyDamageResultAPI;
024import com.fs.starfarer.api.graphics.SpriteAPI;
025import com.fs.starfarer.api.util.FaderUtil;
026import com.fs.starfarer.api.util.IntervalUtil;
027import com.fs.starfarer.api.util.Misc;
028
029public class DisintegratorEffect extends BaseCombatLayeredRenderingPlugin implements OnHitEffectPlugin {
030
031        // each tick is on average .9 seconds
032        // ticks can't be longer than a second or floating damage numbers separate
033        //public static int NUM_TICKS = 22;
034        public static int NUM_TICKS = 11;
035        public static float TOTAL_DAMAGE = 1000;
036        
037        public DisintegratorEffect() {
038        }
039        
040        protected float getTotalDamage() {
041                return TOTAL_DAMAGE;
042        }
043        protected int getNumTicks() {
044                return NUM_TICKS;
045        }
046        protected boolean canDamageHull() {
047                return false;
048        }
049        
050        public void onHit(DamagingProjectileAPI projectile, CombatEntityAPI target, Vector2f point, boolean shieldHit, ApplyDamageResultAPI damageResult, CombatEngineAPI engine) {
051                if (shieldHit) return;
052                if (projectile.isFading()) return;
053                if (!(target instanceof ShipAPI)) return;
054                
055                Vector2f offset = Vector2f.sub(point, target.getLocation(), new Vector2f());
056                offset = Misc.rotateAroundOrigin(offset, -target.getFacing());
057                
058                DisintegratorEffect effect = new DisintegratorEffect(projectile, (ShipAPI) target, offset);
059                CombatEntityAPI e = engine.addLayeredRenderingPlugin(effect);
060                e.getLocation().set(projectile.getLocation());
061        }
062        
063        public static class ParticleData {
064                public SpriteAPI sprite;
065                public Vector2f offset = new Vector2f();
066                public Vector2f vel = new Vector2f();
067                public float scale = 1f;
068                public float scaleIncreaseRate = 1f;
069                public float turnDir = 1f;
070                public float angle = 1f;
071                
072                public float maxDur;
073                public FaderUtil fader;
074                public float elapsed = 0f;
075                public float baseSize;
076                
077                public Color color = new Color(100,150,255,35);
078                
079                public ParticleData(float baseSize, float maxDur, float endSizeMult) {
080                        sprite = Global.getSettings().getSprite("misc", "nebula_particles");
081                        //sprite = Global.getSettings().getSprite("misc", "dust_particles");
082                        float i = Misc.random.nextInt(4);
083                        float j = Misc.random.nextInt(4);
084                        sprite.setTexWidth(0.25f);
085                        sprite.setTexHeight(0.25f);
086                        sprite.setTexX(i * 0.25f);
087                        sprite.setTexY(j * 0.25f);
088                        sprite.setAdditiveBlend();
089                        
090                        angle = (float) Math.random() * 360f;
091                        
092                        this.maxDur = maxDur;
093                        scaleIncreaseRate = endSizeMult / maxDur;
094                        if (endSizeMult < 1f) {
095                                scaleIncreaseRate = -1f * endSizeMult;
096                        }
097                        scale = 1f;
098                        
099                        this.baseSize = baseSize;
100                        turnDir = Math.signum((float) Math.random() - 0.5f) * 20f * (float) Math.random();
101                        //turnDir = 0f;
102                        
103                        float driftDir = (float) Math.random() * 360f;
104                        vel = Misc.getUnitVectorAtDegreeAngle(driftDir);
105                        //vel.scale(proj.getProjectileSpec().getLength() / maxDur * (0f + (float) Math.random() * 3f));
106                        vel.scale(0.25f * baseSize / maxDur * (1f + (float) Math.random() * 1f));
107                        
108                        fader = new FaderUtil(0f, 0.5f, 0.5f);
109                        fader.forceOut();
110                        fader.fadeIn();
111                }
112                
113                public void advance(float amount) {
114                        scale += scaleIncreaseRate * amount;
115                        
116                        offset.x += vel.x * amount;
117                        offset.y += vel.y * amount;
118                                
119                        angle += turnDir * amount;
120                        
121                        elapsed += amount;
122                        if (maxDur - elapsed <= fader.getDurationOut() + 0.1f) {
123                                fader.fadeOut();
124                        }
125                        fader.advance(amount);
126                }
127        }
128        
129        protected List<ParticleData> particles = new ArrayList<ParticleData>();
130        protected DamagingProjectileAPI proj;
131        protected ShipAPI target;
132        protected Vector2f offset;
133        protected int ticks = 0;
134        protected IntervalUtil interval; 
135        protected FaderUtil fader = new FaderUtil(1f, 0.5f, 0.5f); 
136
137        public DisintegratorEffect(DamagingProjectileAPI proj, ShipAPI target, Vector2f offset) {
138                this.proj = proj;
139                this.target = target;
140                this.offset = offset;
141                
142                interval = new IntervalUtil(0.8f, 1f);
143                interval.forceIntervalElapsed();
144        }
145        
146        public float getRenderRadius() {
147                return 500f;
148        }
149        
150        
151        protected EnumSet<CombatEngineLayers> layers = EnumSet.of(CombatEngineLayers.BELOW_INDICATORS_LAYER);
152        @Override
153        public EnumSet<CombatEngineLayers> getActiveLayers() {
154                return layers;
155        }
156
157        public void init(CombatEntityAPI entity) {
158                super.init(entity);
159        }
160        
161        public void advance(float amount) {
162                if (Global.getCombatEngine().isPaused()) return;
163                
164                Vector2f loc = new Vector2f(offset);
165                loc = Misc.rotateAroundOrigin(loc, target.getFacing());
166                Vector2f.add(target.getLocation(), loc, loc);
167                entity.getLocation().set(loc);
168                
169                List<ParticleData> remove = new ArrayList<ParticleData>();
170                for (ParticleData p : particles) {
171                        p.advance(amount);
172                        if (p.elapsed >= p.maxDur) {
173                                remove.add(p);
174                        }
175                }
176                particles.removeAll(remove);
177                
178                float volume = 1f;
179                if (ticks >= getNumTicks() || !target.isAlive() || !Global.getCombatEngine().isEntityInPlay(target)) {
180                        fader.fadeOut();
181                        fader.advance(amount);
182                        volume = fader.getBrightness();
183                }
184                Global.getSoundPlayer().playLoop(getSoundLoopId(), target, 1f, volume, loc, target.getVelocity());
185                
186                
187                interval.advance(amount);
188                if (interval.intervalElapsed() && ticks < getNumTicks()) {
189                        dealDamage();
190                        ticks++;
191                }
192        }
193        
194        protected String getSoundLoopId() {
195                return "disintegrator_loop";
196        }
197
198        protected int getNumParticlesPerTick() {
199                return 3;
200        }
201
202        protected void addParticle() {
203                ParticleData p = new ParticleData(30f, 3f + (float) Math.random() * 2f, 2f);
204                particles.add(p);
205                p.offset = Misc.getPointWithinRadius(p.offset, 20f);
206        }
207        
208        protected void damageDealt(Vector2f loc, float hullDamage, float armorDamage) {
209                
210        }
211        
212        protected void dealDamage() {
213                CombatEngineAPI engine = Global.getCombatEngine();
214                
215                int num = getNumParticlesPerTick();
216                for (int i = 0; i < num; i++) {
217                        addParticle();
218                }
219                
220                
221                Vector2f point = new Vector2f(entity.getLocation());
222                
223                // maximum armor in a cell is 1/15th of the ship's stated armor rating
224
225                ArmorGridAPI grid = target.getArmorGrid();
226                int[] cell = grid.getCellAtLocation(point);
227                if (cell == null) return;
228                
229                int gridWidth = grid.getGrid().length;
230                int gridHeight = grid.getGrid()[0].length;
231                
232                float damageTypeMult = getDamageTypeMult(proj.getSource(), target);
233                
234                float damagePerTick = (float) getTotalDamage() / (float) getNumTicks();
235                float damageDealt = 0f;
236                float hullDamage = 0f;
237                for (int i = -2; i <= 2; i++) {
238                        for (int j = -2; j <= 2; j++) {
239                                if ((i == 2 || i == -2) && (j == 2 || j == -2)) continue; // skip corners
240                                
241                                int cx = cell[0] + i;
242                                int cy = cell[1] + j;
243                                
244                                if (cx < 0 || cx >= gridWidth || cy < 0 || cy >= gridHeight) continue;
245                                
246                                float damMult = 1/30f;
247                                if (i == 0 && j == 0) {
248                                        damMult = 1/15f;
249                                } else if (i <= 1 && i >= -1 && j <= 1 && j >= -1) { // S hits
250                                        damMult = 1/15f;
251                                } else { // T hits
252                                        damMult = 1/30f;
253                                }
254                                
255                                float armorInCell = grid.getArmorValue(cx, cy);
256                                float damage = damagePerTick * damMult * damageTypeMult;
257                                if (damage > armorInCell && canDamageHull()) {
258                                        hullDamage += damage - armorInCell;
259                                }
260                                damage = Math.min(damage, armorInCell);
261                                if (damage <= 0) continue;
262                                
263                                target.getArmorGrid().setArmorValue(cx, cy, Math.max(0, armorInCell - damage));
264                                damageDealt += damage;
265                        }
266                }
267                
268                if (damageDealt > 0) {
269                        if (Misc.shouldShowDamageFloaty(proj.getSource(), target)) {
270                                engine.addFloatingDamageText(point, damageDealt, 0f, Misc.FLOATY_ARMOR_DAMAGE_COLOR, target, proj.getSource());
271                        }
272                        target.syncWithArmorGridState();
273                }
274                
275                if (hullDamage > 1f) {
276                        float showHullDamage = Math.min(hullDamage, target.getHitpoints());
277                        if (showHullDamage >= 0) {
278                                target.setHitpoints(target.getHitpoints() - hullDamage);
279                                if (target.getHitpoints() <= 0f && !target.isHulk()) {
280                                        target.setSpawnDebris(false);
281                                        engine.applyDamage(target, point, 100f, DamageType.ENERGY, 0f, true, false, proj.getSource(), false);
282                                }
283                                if (Misc.shouldShowDamageFloaty(proj.getSource(), target)) {
284                                        Vector2f p2 = new Vector2f(point);
285                                        p2.y += 20f;
286                                        engine.addFloatingDamageText(p2, hullDamage, 0f, Misc.FLOATY_HULL_DAMAGE_COLOR, target, proj.getSource());
287                                }
288//                              String key = "wfewfewf";
289//                              Float total = (Float) engine.getCustomData().get(key);
290//                              if (total == null) total = 0f;
291//                              total += hullDamage;
292//                              engine.getCustomData().put(key, total);
293//                              System.out.println("Total hull damage dealt: " + total);
294                        }
295                }
296                
297                damageDealt(point, hullDamage, damageDealt);
298                
299        }
300
301        public boolean isExpired() {
302                return particles.isEmpty() && 
303                                        (ticks >= getNumTicks() || !target.isAlive() || !Global.getCombatEngine().isEntityInPlay(target));
304        }
305
306        public void render(CombatEngineLayers layer, ViewportAPI viewport) {
307                float x = entity.getLocation().x;
308                float y = entity.getLocation().y;
309                
310                //Color color = new Color(100,150,255,35);
311                float b = viewport.getAlphaMult();
312
313                GL14.glBlendEquation(GL14.GL_FUNC_REVERSE_SUBTRACT);
314                
315                for (ParticleData p : particles) {
316                        //float size = proj.getProjectileSpec().getWidth() * 0.6f;
317                        float size = p.baseSize * p.scale;
318                        
319                        Vector2f loc = new Vector2f(x + p.offset.x, y + p.offset.y);
320                        
321                        float alphaMult = 1f;
322                        
323                        p.sprite.setAngle(p.angle);
324                        p.sprite.setSize(size, size);
325                        p.sprite.setAlphaMult(b * alphaMult * p.fader.getBrightness());
326                        p.sprite.setColor(p.color);
327                        p.sprite.renderAtCenter(loc.x, loc.y);
328                }
329                
330                GL14.glBlendEquation(GL14.GL_FUNC_ADD);
331        }
332        
333        
334        public static float getDamageTypeMult(ShipAPI source, ShipAPI target) {
335                if (source == null || target == null) return 1f;
336                
337                float damageTypeMult = target.getMutableStats().getArmorDamageTakenMult().getModifiedValue();
338                switch (target.getHullSize()) {
339                case CAPITAL_SHIP:
340                        damageTypeMult *= source.getMutableStats().getDamageToCapital().getModifiedValue();
341                        break;
342                case CRUISER:
343                        damageTypeMult *= source.getMutableStats().getDamageToCruisers().getModifiedValue();
344                        break;
345                case DESTROYER:
346                        damageTypeMult *= source.getMutableStats().getDamageToDestroyers().getModifiedValue();
347                        break;
348                case FRIGATE:
349                        damageTypeMult *= source.getMutableStats().getDamageToFrigates().getModifiedValue();
350                        break;
351                case FIGHTER:
352                        damageTypeMult *= source.getMutableStats().getDamageToFighters().getModifiedValue();
353                        break;
354                }
355                return damageTypeMult;
356        }
357
358        public Vector2f getOffset() {
359                return offset;
360        }
361
362        public void setOffset(Vector2f offset) {
363                this.offset = offset;
364        }
365
366}
367
368
369
370