001package com.fs.starfarer.api.impl.combat.dweller;
002
003import java.util.ArrayList;
004import java.util.EnumSet;
005import java.util.LinkedHashMap;
006import java.util.List;
007
008import java.awt.Color;
009
010import org.lwjgl.opengl.GL11;
011import org.lwjgl.opengl.GL14;
012import org.lwjgl.util.vector.Vector2f;
013
014import com.fs.starfarer.api.Global;
015import com.fs.starfarer.api.combat.BaseCombatLayeredRenderingPlugin;
016import com.fs.starfarer.api.combat.CombatEngineAPI;
017import com.fs.starfarer.api.combat.CombatEngineLayers;
018import com.fs.starfarer.api.combat.CombatEntityAPI;
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.HullDamageAboutToBeTakenListener;
024import com.fs.starfarer.api.graphics.SpriteAPI;
025import com.fs.starfarer.api.impl.combat.ShipExplosionFlareVisual;
026import com.fs.starfarer.api.impl.combat.ShipExplosionFlareVisual.ShipExplosionFlareParams;
027import com.fs.starfarer.api.util.Misc;
028import com.fs.starfarer.api.util.MutatingValueUtil;
029
030public class DwellerCombatPlugin extends BaseCombatLayeredRenderingPlugin implements HullDamageAboutToBeTakenListener {
031        
032        public static Color STANDARD_PART_GLOW_COLOR = new Color(255, 0, 50, 255);
033        
034        public static String WEAPON_ACTIVATED = "weapon_activated";
035        public static String SHIELD_ACTIVATED = "shield_activated";
036        public static String SYSTEM_ACTIVATED = "system_activated";
037        public static String FLUX_ACTIVATED = "flux_activated";
038        
039        public static interface DCPPlugin {
040                public void advance(DwellerCombatPlugin plugin, float amount);
041        }
042        
043        
044        public static class WobblyPart extends BaseDwellerShipPart {
045                public WarpingSpriteRendererUtilV2 renderer;
046                public boolean negativeBlend = false;
047                public boolean additiveBlend = false;
048                public MutatingValueUtil spin;
049                public float angle = 0f;
050                
051                public WobblyPart(String spriteKey, float scale, float warpMult, Vector2f offset, float facingOffset) {
052                        this(spriteKey, scale, 5, 5, warpMult, offset, facingOffset);
053                }
054                public WobblyPart(String spriteKey, float scale, int verticesWide, int verticesTall, 
055                                                float warpMult, Vector2f offset, float facingOffset) {
056                        super(offset, facingOffset);
057                        
058                        SpriteAPI sprite = Global.getSettings().getSprite("dweller", spriteKey);
059                        
060                        float width = sprite.getWidth() * scale;
061                        float height = sprite.getHeight() * scale;
062                        float warpAmt = width * 0.04f * warpMult;
063                        
064                        sprite.setSize(width, height);
065                        sprite.setCenter(width/2f, height/2f);
066                        renderer = new WarpingSpriteRendererUtilV2(sprite, verticesWide, verticesTall, warpAmt, warpAmt * 1.4f, 1f);
067                        
068                        spin = new MutatingValueUtil(0, 0, 0);
069                }
070                
071                public float getAngle() {
072                        return angle;
073                }
074                public void setAngle(float angle) {
075                        this.angle = angle;
076                }
077                public void setSpin(float min, float max, float rate) {
078                        spin = new MutatingValueUtil(min, max, rate);
079                }
080                
081                public MutatingValueUtil getSpin() {
082                        return spin;
083                }
084                
085                public void advance(float amount) {
086                        super.advance(amount);
087                        spin.advance(amount);
088                        this.angle += spin.getValue() * amount;
089                        renderer.advance(amount);
090                }
091                
092                public void renderImpl(float x, float y, float alphaMult, float angle, CombatEngineLayers layer) {
093                        if (layer == CombatEngineLayers.BELOW_INDICATORS_LAYER) {
094                                if (negativeBlend) {
095                                        GL14.glBlendEquation(GL14.GL_FUNC_REVERSE_SUBTRACT);
096                                        renderer.getSprite().setBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
097                                } else {
098                                        if (additiveBlend) {
099                                                renderer.getSprite().setAdditiveBlend();
100                                        } else {
101                                                renderer.getSprite().setNormalBlend();
102                                        }
103                                }
104                                
105                                renderer.getSprite().setAlphaMult(alphaMult);
106                                renderer.getSprite().setColor(color);
107                                renderer.getSprite().setAngle(angle + facingOffset + this.angle);
108                                renderer.renderAtCenter(x, y);
109                                
110                                if (negativeBlend) {
111                                        GL14.glBlendEquation(GL14.GL_FUNC_ADD);
112                                }
113                        }
114                }
115        }
116        
117        
118        public static DwellerCombatPlugin getDwellerPluginFor(CombatEntityAPI entity) {
119                if (entity == null) return null;
120                return getShipMap().get(entity);
121        }
122        
123        public static String KEY_SHIP_MAP = "DwellerCombatPlugin_shipMap_key";
124        
125        @SuppressWarnings("unchecked")
126        public static LinkedHashMap<CombatEntityAPI, DwellerCombatPlugin> getShipMap() {
127                LinkedHashMap<CombatEntityAPI, DwellerCombatPlugin> map = 
128                                (LinkedHashMap<CombatEntityAPI, DwellerCombatPlugin>) Global.getCombatEngine().getCustomData().get(KEY_SHIP_MAP);
129                if (map == null) {
130                        map = new LinkedHashMap<>();
131                        Global.getCombatEngine().getCustomData().put(KEY_SHIP_MAP, map);
132                }
133                return map;
134        }
135
136//      @SuppressWarnings("unchecked")
137//      public static ListMap<DwellerCombatPlugin> getStringToDwellerPluginMap(String key) {
138//              ListMap<DwellerCombatPlugin> map = 
139//                              (ListMap<DwellerCombatPlugin>) Global.getCombatEngine().getCustomData().get(key);
140//              if (map == null) {
141//                      map = new ListMap<>();
142//                      Global.getCombatEngine().getCustomData().put(key, map);
143//              }
144//              return map;
145//      }
146        
147        protected CombatEntityAPI attachedTo;
148        protected float elapsed = 0f;
149        
150        protected List<DwellerShipPart> parts = new ArrayList<>();
151        
152        protected boolean spawnedShipExplosionParticles = false;
153        protected DCPPlugin plugin = null;
154        
155        public Object custom1;
156        public Object custom2;
157        public Object custom3;
158        
159        
160        public DwellerCombatPlugin(CombatEntityAPI attachedTo) {
161                CombatEntityAPI e = Global.getCombatEngine().addLayeredRenderingPlugin(this);
162                e.getLocation().set(attachedTo.getLocation());
163                
164                this.attachedTo = attachedTo;
165                
166                if (attachedTo instanceof ShipAPI) {
167                        ShipAPI ship = (ShipAPI) attachedTo;
168                        ship.addListener(this);
169                }
170                
171                getShipMap().put(attachedTo, this);
172        }
173        
174        public void init(CombatEntityAPI entity) {
175                super.init(entity);
176        }
177        
178        public float getRenderRadius() {
179                float extra = 300f;
180                return attachedTo.getCollisionRadius() + extra;
181        }
182        
183        //protected EnumSet<CombatEngineLayers> layers = EnumSet.of(CombatEngineLayers.BELOW_PHASED_SHIPS_LAYER);
184        //protected EnumSet<CombatEngineLayers> layers = EnumSet.of(CombatEngineLayers.ABOVE_PARTICLES);
185        protected EnumSet<CombatEngineLayers> layers = EnumSet.of(CombatEngineLayers.BELOW_INDICATORS_LAYER);
186        @Override
187        public EnumSet<CombatEngineLayers> getActiveLayers() {
188                return layers;
189        }
190        
191
192        protected float sinceTest = 10f;
193        public void advance(float amount) {
194                //if (true) return;
195                
196                
197                if (Global.getCombatEngine().isPaused() || entity == null || isExpired()) return;
198                
199                
200                entity.getLocation().set(attachedTo.getLocation());
201
202                elapsed += amount;
203                
204//              sinceTest += amount;
205//              if (Keyboard.isKeyDown(Keyboard.KEY_K) && sinceTest > 1f) {
206//                      spawnedShipExplosionParticles = false;
207//                      notifyAboutToTakeHullDamage(null, (ShipAPI) attachedTo, new Vector2f(), 1000000f);
208//                      sinceTest = 0f;
209//              }
210                
211//              Vector2f aVel = attachedTo.getVelocity();
212//              float aSpeed = aVel.length();
213//              Vector2f facingDir = Misc.getUnitVectorAtDegreeAngle(attachedTo.getFacing());
214//              Vector2f aLoc = new Vector2f(attachedTo.getLocation());
215                
216                if (isExpired()) {
217                }
218                
219                if (attachedTo instanceof ShipAPI) {
220                        ShipAPI ship = (ShipAPI) attachedTo;
221                        if (ship.getShield() != null) {
222                                ship.setJitterShields(true);
223                                ship.setCircularJitter(true);
224                                Color color = new Color(255,0,50,255);
225                                ship.setJitter(this, color, 1f, 3, 0f);
226                                //ship.getShield().applyShieldEffects(null, null, 0f, 2f, 1f);
227                        }
228                }
229                
230                if (shouldDespawn()) {
231                        for (DwellerShipPart part : parts) {
232                                part.fadeOut();
233                        }
234                } else {
235                        if (attachedTo instanceof ShipAPI) {
236                                ShipAPI ship = (ShipAPI) attachedTo;
237                                
238                                fadeOut(WEAPON_ACTIVATED, SHIELD_ACTIVATED, FLUX_ACTIVATED, SYSTEM_ACTIVATED);
239                                
240                                boolean activeWeapons = false;
241                                for (WeaponAPI w : ship.getAllWeapons()) {
242                                        if (w.isDecorative()) continue;
243                                        if (w.isFiring()) {
244                                                activeWeapons = true;
245                                                break;
246                                        }
247                                }
248                        
249                                if (activeWeapons) {
250                                        fadeIn(WEAPON_ACTIVATED);
251                                }
252                                
253                                if (ship.getShield() != null && ship.getShield().isOn()) {
254                                        fadeIn(SHIELD_ACTIVATED);
255                                }
256                                
257                                float systemLevel = 0f;
258                                if (ship.getSystem() != null) systemLevel = ship.getSystem().getEffectLevel();
259                                if (systemLevel > 0) {
260                                        fadeIn(SYSTEM_ACTIVATED);
261                                }
262                                setBrightness(systemLevel, SYSTEM_ACTIVATED);
263                                
264                                float fluxLevel = ship.getFluxLevel();
265                                if (fluxLevel > 0f) {
266                                        fadeIn(FLUX_ACTIVATED);
267                                }
268                                setBrightness(fluxLevel, FLUX_ACTIVATED);
269                        }
270                }
271                
272//              float mult = 1f;
273//              mult += ((ShipAPI) attachedTo).getSystem().getEffectLevel() * 20f;
274                //if (((ShipAPI) attachedTo).getSystem().isActive()) mult = 10f;
275                for (DwellerShipPart part : parts) {
276                        part.advance(amount);
277                }
278                
279                if (plugin != null) {
280                        plugin.advance(this, amount);
281                }
282        }
283        
284
285        public boolean shouldDespawn() {
286                //if (true) return false;
287                if (attachedTo instanceof ShipAPI) {
288                        ShipAPI ship = (ShipAPI) attachedTo;
289                        return !Global.getCombatEngine().isShipAlive(ship);
290                }
291                if (attachedTo instanceof MissileAPI) {
292                        MissileAPI missile = (MissileAPI) attachedTo;
293                        return !Global.getCombatEngine().isMissileAlive(missile);
294                }
295                return attachedTo.isExpired() || !Global.getCombatEngine().isEntityInPlay(attachedTo);
296        }
297
298        public boolean isExpired() {
299                boolean shouldDespawn = shouldDespawn();
300                if (shouldDespawn) {
301                        boolean allFaded = true;
302                        for (DwellerShipPart part : parts) {
303                                if (!part.getFader().isFadedOut() && part.getAlphaMult() > 0) {
304                                        allFaded = false;
305                                        break;
306                                }
307                        }
308                        if (allFaded) {
309                                getShipMap().remove(attachedTo);
310                                return true;
311                        }
312                }
313                return false;
314        }
315        
316        public void render(CombatEngineLayers layer, ViewportAPI viewport) {
317                //if (true) return;
318                
319                //Color color = Color.white;
320                float alphaMult = viewport.getAlphaMult();
321                if (alphaMult <= 0f) return;
322                
323                Vector2f aLoc = new Vector2f(attachedTo.getLocation());
324                
325                for (DwellerShipPart part : parts) {
326                        part.render(aLoc.x, aLoc.y, alphaMult, attachedTo.getFacing() - 90f, layer);
327                }
328        }
329
330
331        public CombatEntityAPI getAttachedTo() {
332                return attachedTo;
333        }
334
335        public List<DwellerShipPart> getParts() {
336                return parts;
337        }
338
339        public DwellerShipPart getPart(String id) {
340                for (DwellerShipPart curr : parts) {
341                        if (id.equals(curr.getId())) return curr;
342                }
343                return null;
344        }
345        
346        public void fadeIn(String ... tags) {
347                for (DwellerShipPart part : getParts(tags)) {
348                        part.fadeIn();
349                }
350        }
351        public void fadeOut(String ... tags) {
352                for (DwellerShipPart part : getParts(tags)) {
353                        part.fadeOut();
354                }
355        }
356        public void setAlphaMult(float alphaMult, String ... tags) {
357                for (DwellerShipPart part : getParts(tags)) {
358                        part.setAlphaMult(alphaMult);
359                }
360        }
361        public void setBrightness(float b, String ... tags) {
362                String key = "";
363                for (String tag : tags) key += tag + "_";
364                if (tags.length == 1) key = tags[0];
365                
366                for (DwellerShipPart part : getParts(tags)) {
367                        part.getBrightness().shift(key, b, 0.5f, 0.5f, 1f);
368                }
369        }
370        
371        public List<DwellerShipPart> getParts(String ... tags) {
372                List<DwellerShipPart> result = new ArrayList<>();
373                
374                OUTER: for (DwellerShipPart curr : parts) {
375                        for (String tag : tags) {
376                                if (curr.hasTag(tag)) {
377                                        result.add(curr);
378                                        continue OUTER;
379                                }
380                        }
381                }
382                
383                return result;
384        }
385
386
387        @Override
388        public boolean notifyAboutToTakeHullDamage(Object param, ShipAPI ship, Vector2f point, float damageAmount) {
389                float hull = ship.getHitpoints();
390                if (damageAmount >= hull && !spawnedShipExplosionParticles && ship.getExplosionScale() > 0f) {
391                        int numSwirly = 11;
392                        int numDark = 11;
393                        //numDark = 0;
394                        float size = ship.getCollisionRadius() * 0.25f;
395                        if (size < 15) size = 15;
396                        if (size > 50) size = 50;
397                        //if (DwellerCombatStrategyAI.isMaw(ship)) {
398                        float durMult = 1f;
399                        float flashMult = 1f;
400                        if (ship.isCapital()) {
401                                size = 100;
402                                durMult = 2f;
403                                flashMult = 2f;
404                        }
405//                      size = 50;
406//                      size = 15;
407                        
408                        float baseSize = size;
409                        
410                        CombatEngineAPI engine = Global.getCombatEngine();
411                        
412                        float rampUp = 0f;
413                        
414                        Color color = DwellerShroud.SHROUD_COLOR;
415                        
416                        for (int i = 0; i < numSwirly; i++) {
417                                Vector2f loc = new Vector2f(ship.getLocation());
418                                //loc.x += 750f;
419                                float scatterMult = 0.5f;
420                                //loc = Misc.getPointWithinRadius(loc, baseSize * 1f * scatterMult);
421                                loc = Misc.getPointWithinRadius(loc, size * 1f * scatterMult);
422                                float s = size * 4f * (0.5f + (float) Math.random() * 0.5f);
423                                
424                                float dur = 0.5f + (float) Math.random() * 0.5f;
425                                dur *= durMult;
426                                
427                                size *= 1.25f;
428
429                                engine.addSwirlyNebulaParticle(loc, ship.getVelocity(), s, 3f, rampUp, 0f, dur, color, false);
430                        }
431                        
432                        size = baseSize;
433                        for (int i = 0; i < numDark; i++) {
434                                Vector2f loc = new Vector2f(ship.getLocation());
435                                //loc.x += 750f;
436                                float scatterMult = 0.5f;
437                                //loc = Misc.getPointWithinRadius(loc, baseSize * 1f * scatterMult);
438                                loc = Misc.getPointWithinRadius(loc, size * 1f * scatterMult);
439                                float s = size * 4f * (0.5f + (float) Math.random() * 0.5f);
440                                
441                                float dur = 0.5f + (float) Math.random() * 0.5f;
442                                dur *= durMult;
443                                
444                                //size *= 1.1f;
445                                size *= 1.25f;
446                                //s *= 0.5f;
447                                
448                                engine.addNegativeSwirlyNebulaParticle(loc, ship.getVelocity(), s, 3f, rampUp, 0f, dur, color);
449                        }
450                        
451                        
452                        Vector2f expVel = ship.getVelocity();
453                        Vector2f expLoc = ship.getShieldCenterEvenIfNoShield();
454                        expLoc = ship.getLocation();
455                        float explosionScale = 1f;
456                        if (ship.isCapital()) {
457                                explosionScale *= 1.7f;
458                        } else if (ship.isCruiser()) {
459                                explosionScale *= 1.5f;
460                        } else if (ship.isDestroyer()) {
461                                explosionScale *= 1.5f;
462                        }
463
464                        Color flashColor = new Color(255,50,100,255);
465                        float b = 1f;
466
467                        float glowSize = (float) Math.sqrt(ship.getCollisionRadius()) * 15f * 4f;
468                        if (ship.isFighter()) glowSize *= 0.5f;
469                        glowSize *= explosionScale;
470                        glowSize *= flashMult;
471
472                        ShipExplosionFlareParams sp = new ShipExplosionFlareParams();
473                        //sp.attachedTo = this;
474                        float er = (float) Math.sqrt(ship.getCollisionRadius()) * 15f * 4.0f;
475                        float mult = 475f/994f;
476                        er *= mult;
477                        sp.flareWidth = er * 4f * explosionScale;
478                        sp.flareHeight = er * 1.6f * explosionScale;
479                        sp.color = flashColor;
480                        sp.fadeIn = 0.1f;
481                        sp.fadeOut = 2f;
482                        
483                        CombatEntityAPI e = engine.addLayeredRenderingPlugin(new ShipExplosionFlareVisual(sp));
484                        e.getLocation().set(expLoc);
485                        e.getVelocity().set(expVel);
486
487                        engine.addHitParticle(expLoc, expVel, glowSize * 1f, b, 1.5f * durMult, flashColor);
488                        engine.addHitParticle(expLoc, expVel, glowSize * 0.5f, b, 1.5f * durMult, flashColor);
489//                      if (ship.isFrigate()) { // needs just a little extra oomph 
490//                              engine.addHitParticle(expLoc, expVel, glowSize * 0.5f, b, 1.5f, Color.white);
491//                      }                       
492                        
493                        spawnedShipExplosionParticles = true;
494                }
495                return false;
496        }
497
498        public DCPPlugin getPlugin() {
499                return plugin;
500        }
501
502        public void setPlugin(DCPPlugin plugin) {
503                this.plugin = plugin;
504        }
505        
506}
507
508
509
510
511
512
513