001package com.fs.starfarer.api.impl.campaign;
002
003import java.awt.Color;
004import java.util.ArrayList;
005import java.util.LinkedHashSet;
006import java.util.List;
007
008import org.lwjgl.util.vector.Vector2f;
009
010import com.fs.starfarer.api.Global;
011import com.fs.starfarer.api.campaign.CampaignEngineLayers;
012import com.fs.starfarer.api.campaign.CampaignFleetAPI;
013import com.fs.starfarer.api.campaign.LocationAPI;
014import com.fs.starfarer.api.campaign.SectorEntityToken;
015import com.fs.starfarer.api.combat.ViewportAPI;
016import com.fs.starfarer.api.fleet.FleetMemberAPI;
017import com.fs.starfarer.api.graphics.SpriteAPI;
018import com.fs.starfarer.api.impl.campaign.ids.Stats;
019import com.fs.starfarer.api.impl.campaign.terrain.HyperspaceTerrainPlugin;
020import com.fs.starfarer.api.impl.campaign.terrain.ShoveFleetScript;
021import com.fs.starfarer.api.util.Misc;
022import com.fs.starfarer.api.util.WeightedRandomPicker;
023
024public class ExplosionEntityPlugin extends BaseCustomEntityPlugin {
025
026        public static enum ExplosionFleetDamage {
027                NONE,
028                LOW,
029                MEDIUM,
030                HIGH,
031                EXTREME,
032        }
033        public static class ExplosionParams {
034                public Color color;
035                public ExplosionFleetDamage damage = ExplosionFleetDamage.NONE;
036                public float radius;
037                public float durationMult = 1f;
038                public Vector2f loc;
039                public LocationAPI where;
040                public ExplosionParams(Color color, LocationAPI where, Vector2f loc, float radius, float durationMult) {
041                        this.color = color;
042                        this.where = where;
043                        this.loc = loc;
044                        this.radius = radius;
045                        this.durationMult = durationMult;
046                }
047        }
048
049        
050        public static class ParticleData {
051                public Vector2f offset = new Vector2f();
052                public Vector2f vel = new Vector2f();
053                public float scale = 1f;
054                public float scaleDelta = 1f;
055                public float turnDir = 1f;
056                public float angle = 1f;
057                public float size;
058                
059                public float maxDur;
060                public float elapsed;
061                public float swImpact = 1f;
062                
063                public int i;
064                public int j;
065                
066                public Color color;
067                
068                public ParticleData(Color color, float size, float maxDur, float endScale) {
069                        i = Misc.random.nextInt(4);
070                        j = Misc.random.nextInt(4);
071                        
072                        this.color = color;
073                        this.size = size;
074                        angle = (float) Math.random() * 360f;
075                        
076                        this.maxDur = maxDur;
077                        scaleDelta = (endScale - 1f) / maxDur;
078                        scale = 1f;
079                        
080                        turnDir = Math.signum((float) Math.random() - 0.5f) * 10f * (float) Math.random();
081                        
082                        //turnDir = 0f;
083                }
084                
085                public void setVelocity(float direction, float minSpeed, float maxSpeed) {
086                        vel = Misc.getUnitVectorAtDegreeAngle(direction);
087                        vel.scale(minSpeed + (maxSpeed - minSpeed) * (float) Math.random());
088                }
089                
090                public void setOffset(float direction, float minDist, float maxDist) {
091                        offset = Misc.getUnitVectorAtDegreeAngle(direction);
092                        offset.scale(minDist + (maxDist - minDist) * (float) Math.random());
093                }
094                
095                public void advance(float amount) {
096                        scale += scaleDelta * amount;
097                        if (scale < 0) scale = 0f;
098                        
099                        offset.x += vel.x * amount;
100                        offset.y += vel.y * amount;
101                        
102                        angle += turnDir * amount;
103                        
104                        elapsed += amount;
105                }
106                
107                public float getBrightness() {
108                        float b = 1f - (elapsed / maxDur);
109                        if (b < 0) b = 0;
110                        if (b > 1) b = 1;
111                        return b;
112                }
113        }       
114        
115        
116        protected ExplosionParams params;
117        protected List<ParticleData> particles = new ArrayList<ParticleData>();
118        
119        transient protected SpriteAPI sprite;
120        
121        protected float shockwaveRadius;
122        protected float shockwaveWidth;
123        protected float shockwaveSpeed;
124        protected float shockwaveDuration;
125        protected float shockwaveAccel;
126        
127        protected float maxParticleSize;
128        
129        
130        public void init(SectorEntityToken entity, Object pluginParams) {
131                super.init(entity, pluginParams);
132                readResolve();
133                
134                params = (ExplosionParams) pluginParams;
135                
136                
137                if (params.where.isCurrentLocation()) {
138                        Global.getSoundPlayer().playSound("gate_explosion", 1f, 1f, params.loc, Misc.ZERO);
139                }
140                
141                float baseSize = params.radius * 0.08f;
142                maxParticleSize = baseSize * 2f;
143                
144                float fullArea = (float) (Math.PI * params.radius * params.radius);
145                float particleArea = (float) (Math.PI * baseSize * baseSize);
146                
147                int count = (int) Math.round(fullArea / particleArea * 1f);
148                
149                float durMult = 2f;
150                durMult = params.durationMult;
151
152                //baseSize *= 0.5f;
153                for (int i = 0; i < count; i++) {
154                        float size = baseSize * (1f + (float) Math.random());
155                        
156                        Color randomColor = new Color(Misc.random.nextInt(256), 
157                                                Misc.random.nextInt(256), Misc.random.nextInt(256), params.color.getAlpha());                   
158                        Color adjustedColor = Misc.interpolateColor(params.color, randomColor, 0.2f);
159                        adjustedColor = params.color;
160                        ParticleData data = new ParticleData(adjustedColor, size, 
161                                                (0.25f + (float) Math.random()) * 2f * durMult, 3f);
162                        
163                        float r = (float) Math.random();
164                        float dist = params.radius * 0.2f * (0.1f + r * 0.9f);
165                        float dir = (float) Math.random() * 360f;
166                        data.setOffset(dir, dist, dist);
167                        
168                        dir = Misc.getAngleInDegrees(data.offset);
169//                      data.setVelocity(dir, baseSize * 0.25f, baseSize * 0.5f);
170//                      data.vel.scale(1f / durMult);
171                        
172                        data.swImpact = (float) Math.random();
173                        if (i > count / 2) data.swImpact = 1;
174                        
175                        particles.add(data);
176                }
177                
178                Vector2f loc = new Vector2f(params.loc);
179                loc.x -= params.radius * 0.01f;
180                loc.y += params.radius * 0.01f;
181                
182                float b = 1f;
183                params.where.addHitParticle(loc, new Vector2f(), params.radius * 1f, b, 1f * durMult, params.color);
184                loc = new Vector2f(params.loc);
185                params.where.addHitParticle(loc, new Vector2f(), params.radius * 0.4f, 0.5f, 1f * durMult, Color.white);
186                
187                shockwaveAccel = baseSize * 70f / durMult;
188                //shockwaveRadius = -1500f;
189                shockwaveRadius = 0f;
190                shockwaveRadius = -params.radius * 0.5f;
191                shockwaveSpeed = params.radius * 2f / durMult;
192                shockwaveDuration = params.radius * 2f / shockwaveSpeed;
193                shockwaveWidth = params.radius * 0.5f;
194                
195//              shockwaveAccel = baseSize * 1500f / durMult;
196//              //shockwaveRadius = -1500f;
197//              shockwaveRadius = 0f;
198//              shockwaveSpeed = params.radius * 4f / durMult;
199//              shockwaveDuration = params.radius * 2f / shockwaveSpeed;
200//              shockwaveWidth = params.radius * 0.5f;
201                
202//              shockwaveAccel = baseSize * 10f / durMult;
203//              //shockwaveRadius = -1500f;
204//              shockwaveRadius = 0f;
205//              shockwaveSpeed = params.radius * 0.2f / durMult;
206//              shockwaveDuration = params.radius * 2f / shockwaveSpeed;
207//              shockwaveWidth = params.radius * 0.4f;
208        }
209        
210        Object readResolve() {
211                sprite = Global.getSettings().getSprite("misc", "nebula_particles");
212                return this;
213        }
214        
215
216        public void advance(float amount) {
217                for (ParticleData p : new ArrayList<ParticleData>(particles)) {
218                        p.advance(amount);
219                        if (p.elapsed >= p.maxDur) {
220                                particles.remove(p);
221                        }
222                }
223                if (particles.isEmpty()) {
224                        entity.setExpired(true);
225                }
226                
227                applyDamageToFleets();
228
229                if (shockwaveDuration > 0) {
230                        shockwaveRadius += shockwaveSpeed * amount;
231                        //shockwaveSpeed -= amount * shockwaveSpeed * 5f;
232                        if (shockwaveSpeed < 0) shockwaveSpeed = 0;
233                        shockwaveDuration -= amount;
234                        for (ParticleData p : particles) {
235                                float dist = p.offset.length();
236                                
237                                float impact = 0f;
238                                if (dist < shockwaveRadius && dist > shockwaveRadius - shockwaveWidth) {
239                                        impact = 1f - (shockwaveRadius - dist) / shockwaveWidth;
240                                        impact = -impact;
241                                } else if (dist > shockwaveRadius && dist < shockwaveRadius + shockwaveWidth) {
242                                        impact = 1f - (dist - shockwaveRadius) / shockwaveWidth;
243                                }
244                                
245                                float speed = p.vel.length();
246                                float dot = Vector2f.dot(p.offset, p.vel);
247                                float threshold = shockwaveSpeed * 0.5f;
248                                if (speed > threshold) {// && dot > 0) {
249                                        impact *= threshold / speed;
250                                }
251                                if (dot < 0) {
252                                        impact *= 0.2f;
253                                }
254                                //impact *= 0.1f + 0.9f * (1f - p.size / maxParticleSize);
255                                impact *= p.swImpact;
256                                
257                                Vector2f accel = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(p.offset));
258                                accel.scale(impact * shockwaveAccel);
259                                p.vel.x += accel.x * amount;
260                                p.vel.y += accel.y * amount;
261                        }
262                }
263                
264        }
265        
266
267        public float getRenderRange() {
268                float extra = 2000f;
269                if (params != null) extra = params.radius * 3;
270                return entity.getRadius() + extra;
271        }
272
273        public void render(CampaignEngineLayers layer, ViewportAPI viewport) {
274                float alphaMult = viewport.getAlphaMult();
275                alphaMult *= entity.getSensorFaderBrightness();
276                alphaMult *= entity.getSensorContactFaderBrightness();
277                if (alphaMult <= 0) return;
278                
279                float x = entity.getLocation().x;
280                float y = entity.getLocation().y;
281                
282                //Color color = params.color;
283                //color = Misc.setAlpha(color, 30);
284                float b = alphaMult;
285                
286                sprite.setTexWidth(0.25f);
287                sprite.setTexHeight(0.25f);
288                sprite.setAdditiveBlend();
289                
290                for (ParticleData p : particles) {
291                        float size = p.size;
292                        size *= p.scale;
293                        
294                        Vector2f loc = new Vector2f(x + p.offset.x, y + p.offset.y);
295                        
296                        float a = 1f;
297                        a = 0.33f;
298                        
299                        sprite.setTexX(p.i * 0.25f);
300                        sprite.setTexY(p.j * 0.25f);
301                        
302                        sprite.setAngle(p.angle);
303                        sprite.setSize(size, size);
304                        sprite.setAlphaMult(b * a * p.getBrightness());
305                        sprite.setColor(p.color);
306                        sprite.renderAtCenter(loc.x, loc.y);
307                }
308        }
309        
310        protected LinkedHashSet<String> damagedAlready = new LinkedHashSet<String>();
311        public void applyDamageToFleets() {
312                if (params.damage == null || params.damage == ExplosionFleetDamage.NONE) {
313                        return;
314                }
315                
316                float shockwaveDist = 0f;
317                for (ParticleData p : particles) {
318                        shockwaveDist = Math.max(shockwaveDist, p.offset.length());
319                }
320                
321                for (CampaignFleetAPI fleet : entity.getContainingLocation().getFleets()) {
322                        String id = fleet.getId();
323                        if (damagedAlready.contains(id)) continue;
324                        float dist = Misc.getDistance(fleet, entity);
325                        if (dist < shockwaveDist) {
326                                float damageMult = 1f - (dist / params.radius);
327                                if (damageMult > 1f) damageMult = 1f;
328                                if (damageMult < 0.1f) damageMult = 0.1f;
329                                if (dist < entity.getRadius() + params.radius * 0.1f) damageMult = 1f;
330                                
331                                damagedAlready.add(id);
332                                applyDamageToFleet(fleet, damageMult);
333                        }
334                }
335                
336        }
337        
338        public void applyDamageToFleet(CampaignFleetAPI fleet, float damageMult) {
339                
340                List<FleetMemberAPI> members = fleet.getFleetData().getMembersListCopy();
341                if (members.isEmpty()) return;
342                
343                float totalValue = 0;
344                for (FleetMemberAPI member : members) {
345                        totalValue += member.getStats().getSuppliesToRecover().getModifiedValue();
346                }
347                if (totalValue <= 0) return;
348                
349                
350                float damageFraction = 0f;
351                switch (params.damage) {
352                case NONE:
353                        return;
354                case LOW:
355                        damageFraction = 0.1f;
356                        break;
357                case MEDIUM:
358                        damageFraction = 0.3f;
359                        break;
360                case HIGH:
361                        damageFraction = 0.6f;
362                        break;
363                case EXTREME:
364                        damageFraction = 0.9f;
365                        break;
366                }
367                
368                damageFraction *= damageMult;
369                
370                float shoveDir = Misc.getAngleInDegrees(entity.getLocation(), fleet.getLocation());
371                fleet.addScript(new ShoveFleetScript(fleet, shoveDir, damageFraction));
372                
373                if (fleet.isInCurrentLocation()) {
374                        float dist = Misc.getDistance(fleet, Global.getSector().getPlayerFleet());
375                        if (dist < HyperspaceTerrainPlugin.STORM_STRIKE_SOUND_RANGE) {
376                                float volumeMult = 0.5f + 0.5f * damageFraction;
377                                Global.getSoundPlayer().playSound("gate_explosion_fleet_impact", 1f, volumeMult, fleet.getLocation(), Misc.ZERO);
378                        }
379                }
380                
381                //float strikeValue = totalValue * damageFraction * (0.5f + (float) Math.random() * 0.5f);
382                
383                WeightedRandomPicker<FleetMemberAPI> picker = new WeightedRandomPicker<FleetMemberAPI>();
384                for (FleetMemberAPI member : members) {
385                        float w = 1f;
386                        if (member.isFrigate()) w *= 0.1f;
387                        if (member.isDestroyer()) w *= 0.2f;
388                        if (member.isCruiser()) w *= 0.5f;
389                        picker.add(member, w);
390                }
391                
392                int numStrikes = picker.getItems().size();
393                
394                for (int i = 0; i < numStrikes; i++) {
395                        FleetMemberAPI member = picker.pick();
396                        if (member == null) return;
397                        
398                        float crPerDep = member.getDeployCost();
399                        //if (crPerDep <= 0) continue;                  
400                        float suppliesPerDep = member.getStats().getSuppliesToRecover().getModifiedValue();
401                        if (suppliesPerDep <= 0 || crPerDep <= 0) return;
402                        float suppliesPer100CR = suppliesPerDep * 1f / Math.max(0.01f, crPerDep);
403
404                        // half flat damage, half scaled based on ship supply cost cost
405                        float strikeSupplies = (250f + suppliesPer100CR) * 0.5f * damageFraction;  
406                        //strikeSupplies = suppliesPerDep * 0.5f * damageFraction;  
407                        
408                        float strikeDamage = strikeSupplies / suppliesPer100CR * (0.75f + (float) Math.random() * 0.5f);
409                        
410                        //float strikeDamage = damageFraction * (0.75f + (float) Math.random() * 0.5f);
411                        
412                        float resistance = member.getStats().getDynamic().getValue(Stats.CORONA_EFFECT_MULT);
413                        strikeDamage *= resistance;
414                        
415                        if (strikeDamage > HyperspaceTerrainPlugin.STORM_MAX_STRIKE_DAMAGE) {
416                                strikeDamage = HyperspaceTerrainPlugin.STORM_MAX_STRIKE_DAMAGE;
417                        }
418                        
419                        if (strikeDamage > 0) {
420                                float currCR = member.getRepairTracker().getBaseCR();
421                                float crDamage = Math.min(currCR, strikeDamage);
422                                
423                                if (crDamage > 0) {
424                                        member.getRepairTracker().applyCREvent(-crDamage, "explosion_" + entity.getId(),
425                                                                                                                "Damaged by explosion");
426                                }
427                                
428                                float hitStrength = member.getStats().getArmorBonus().computeEffective(member.getHullSpec().getArmorRating());
429                                //hitStrength *= strikeDamage / crPerDep;
430                                int numHits = (int) (strikeDamage / 0.1f);
431                                if (numHits < 1) numHits = 1;
432                                for (int j = 0; j < numHits; j++) {
433                                        member.getStatus().applyDamage(hitStrength);
434                                }
435                                //member.getStatus().applyHullFractionDamage(1f);
436                                if (member.getStatus().getHullFraction() < 0.01f) {
437                                        member.getStatus().setHullFraction(0.01f);
438                                        picker.remove(member);
439                                } else {
440                                        float w = picker.getWeight(member);
441                                        picker.setWeight(picker.getItems().indexOf(member), w * 0.5f);
442                                }
443                        }
444                        //picker.remove(member);
445                }
446                
447                if (fleet.isPlayerFleet()) {
448                        Global.getSector().getCampaignUI().addMessage(
449                                        "Your fleet suffers damage from being caught in an explosion", Misc.getNegativeHighlightColor());
450                }
451        }
452
453}
454
455
456
457
458
459
460
461
462