001package com.fs.starfarer.api.impl.combat.dweller;
002
003import java.awt.Color;
004
005import org.lwjgl.util.vector.Vector2f;
006
007import com.fs.starfarer.api.Global;
008import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType;
009import com.fs.starfarer.api.campaign.CargoStackAPI;
010import com.fs.starfarer.api.campaign.SpecialItemData;
011import com.fs.starfarer.api.combat.CollisionClass;
012import com.fs.starfarer.api.combat.CombatEngineAPI;
013import com.fs.starfarer.api.combat.CombatEntityAPI;
014import com.fs.starfarer.api.combat.DamageAPI;
015import com.fs.starfarer.api.combat.DamageType;
016import com.fs.starfarer.api.combat.DamagingProjectileAPI;
017import com.fs.starfarer.api.combat.EmpArcEntityAPI;
018import com.fs.starfarer.api.combat.EmpArcEntityAPI.EmpArcParams;
019import com.fs.starfarer.api.combat.ShipAPI;
020import com.fs.starfarer.api.combat.ShipAPI.HullSize;
021import com.fs.starfarer.api.combat.listeners.DamageDealtModifier;
022import com.fs.starfarer.api.impl.campaign.ids.Items;
023import com.fs.starfarer.api.impl.combat.NegativeExplosionVisual.NEParams;
024import com.fs.starfarer.api.impl.combat.RiftCascadeMineExplosion;
025import com.fs.starfarer.api.impl.combat.dweller.DwellerShroud.DwellerShroudParams;
026import com.fs.starfarer.api.impl.combat.threat.EnergyLashSystemScript.DelayedCombatActionPlugin;
027import com.fs.starfarer.api.loading.DamagingExplosionSpec;
028import com.fs.starfarer.api.ui.Alignment;
029import com.fs.starfarer.api.ui.TooltipMakerAPI;
030import com.fs.starfarer.api.util.Misc;
031import com.fs.starfarer.api.util.TimeoutTracker;
032import com.fs.starfarer.api.util.WeightedRandomPicker;
033
034public class ShroudedThunderheadHullmod extends HumanShipShroudedHullmod {
035        
036        public static float MAX_RANGE = 3000f;
037        
038        public static float RECENT_HIT_DUR = 5f;
039        public static float MAX_TIME_SINCE_RECENT_HIT = 0.1f;
040        public static float WEIGHT_PER_RECENT_HIT = 1f;
041        public static float MISFIRE_WEIGHT = 10f;
042        
043        public static float MIN_REFIRE_DELAY = 0.22f;
044        public static float MAX_REFIRE_DELAY = 0.44f;
045        public static float REFIRE_RATE_MULT = 1f;
046
047        public static float FLUX_PER_DAMAGE = 1f;
048        public static float MIN_DAMAGE = 200f;
049        public static float MAX_DAMAGE = 500f;
050        public static float EMP_MULT = 2f;
051        
052        public static class ShroudedThunderheadDamageDealtMod implements DamageDealtModifier {
053                public ShipAPI ship;
054                public ShroudedThunderheadDamageDealtMod(ShipAPI ship) {
055                        this.ship = ship;
056                }
057                public String modifyDamageDealt(Object param,
058                                                                                CombatEntityAPI target, DamageAPI damage,
059                                                                                Vector2f point, boolean shieldHit) {
060                        if (param instanceof DamagingProjectileAPI) {
061                                DamagingProjectileAPI proj = (DamagingProjectileAPI) param;
062                                DamagingExplosionSpec spec = proj.getExplosionSpecIfExplosion();
063                                if (spec != null && spec.getCollisionClassIfByFighter() == CollisionClass.GAS_CLOUD) {
064                                        return null;
065                                }
066                        } else if ((damage.isDps() && !damage.isForceHardFlux()) || damage.getDamage() <= 0f) {
067                                return null;
068                        }
069                        
070                        if (target != null) {
071                                ShroudedThunderheadHullmodData data = getData(ship);
072                                RecentHitData hit = new RecentHitData();
073                                hit.param = param;
074                                hit.target = target;
075                                hit.point = new Vector2f(point);
076                                hit.damage = damage;
077                                hit.shieldHit = shieldHit;
078                                //data.recentHits.add(hit, 1.5f);
079                                data.recentHits.add(hit, RECENT_HIT_DUR);
080                        }
081                        return null;
082                }
083        }
084        
085        @Override
086        public void applyEffectsAfterShipCreation(ShipAPI ship, String id) {
087                super.applyEffectsAfterShipCreation(ship, id);
088                ship.addListener(new ShroudedThunderheadDamageDealtMod(ship));
089        }
090        
091        public String getDescriptionParam(int index, HullSize hullSize) {
092                return null;
093        }
094        
095        @Override
096        public CargoStackAPI getRequiredItem() {
097                return Global.getSettings().createCargoStack(CargoItemType.SPECIAL, 
098                                                                new SpecialItemData(Items.SHROUDED_THUNDERHEAD, null), null);
099        }
100        
101        public static String DATA_KEY = "core_ShroudedThunderheadHullmod_data_key";
102        public static class RecentHitData {
103                Object param;
104                CombatEntityAPI target;
105                Vector2f point;
106                DamageAPI damage;
107                boolean shieldHit;
108        }
109        public static class ShroudedThunderheadHullmodData {
110                float untilArc = 0f;
111                TimeoutTracker<RecentHitData> recentHits = new TimeoutTracker<RecentHitData>();
112
113                boolean hasRecentEnoughHits() {
114                        for (RecentHitData curr : recentHits.getItems()) {
115                                float remaining = recentHits.getRemaining(curr);
116                                if (remaining >= RECENT_HIT_DUR - MAX_TIME_SINCE_RECENT_HIT) return true;
117                        }
118                        return false;
119                }
120                
121                float getHitProbability() {
122                        float recent = recentHits.getItems().size() * WEIGHT_PER_RECENT_HIT;
123                        return recent / (recent + MISFIRE_WEIGHT);
124                }
125                
126                RecentHitData pickRecentHit() {
127                        if (!hasRecentEnoughHits()) return null;
128                        WeightedRandomPicker<RecentHitData> picker = new WeightedRandomPicker<>();
129                        for (RecentHitData curr : recentHits.getItems()) {
130                                float remaining = recentHits.getRemaining(curr);
131                                if (remaining < RECENT_HIT_DUR - MAX_TIME_SINCE_RECENT_HIT * 2f) continue;
132                                picker.add(curr, WEIGHT_PER_RECENT_HIT);
133                        }
134                        RecentHitData misfire = new RecentHitData();
135                        picker.add(misfire, MISFIRE_WEIGHT);
136                        return picker.pick();
137                }
138        }
139        
140        public static ShroudedThunderheadHullmodData getData(ShipAPI ship) {
141                CombatEngineAPI engine = Global.getCombatEngine();
142                String key = DATA_KEY + "_" + ship.getId();
143                ShroudedThunderheadHullmodData data = (ShroudedThunderheadHullmodData) engine.getCustomData().get(key);
144                if (data == null) {
145                        data = new ShroudedThunderheadHullmodData();
146                        engine.getCustomData().put(key, data);
147                }
148                return data;
149        }
150        
151        @Override
152        public void advanceInCombat(ShipAPI ship, float amount) {
153                super.advanceInCombat(ship, amount);
154                
155                if (!ship.isAlive()) return;
156                if (amount <= 0f) return;
157                
158                ShroudedThunderheadHullmodData data = getData(ship);
159                
160                
161                float prob = data.getHitProbability();
162                DwellerShroud shroud = DwellerShroud.getShroudFor(ship);
163                shroud.getParams().flashProbability = Math.min(0.1f + prob * 1.4f, 1f);
164                //shroud.getParams().alphaMult = 0.5f; 
165                
166                data.untilArc -= amount * REFIRE_RATE_MULT;
167                if (data.untilArc <= 0f) {
168                        boolean hasRecentHits = data.hasRecentEnoughHits();
169                        if (hasRecentHits) {
170                                RecentHitData hit = data.pickRecentHit();
171                                if (hit != null && hit.target != null) {
172                                        data.recentHits.remove(hit);
173                                        spawnLightning(ship, hit);
174                                }
175                                data.untilArc = MIN_REFIRE_DELAY + (float) Math.random() * (MAX_REFIRE_DELAY - MIN_REFIRE_DELAY);
176                        }
177                }
178                
179                data.recentHits.advance(amount);
180        }
181        
182        public static float getPowerMult(HullSize size) {
183                switch (size) {
184                case CAPITAL_SHIP: return 1f;
185                case CRUISER: return 0.6666666667f;
186                case DESTROYER: return 0.3333333333f;
187                case FIGHTER:
188                case FRIGATE:
189                        return 0f;
190                }
191                return 1f;
192        }
193        
194        public static float getDamage(HullSize size) {
195                float mult = getPowerMult(size);
196                return MIN_DAMAGE + (MAX_DAMAGE - MIN_DAMAGE) * mult;   
197        }
198        public static float getEMPDamage(HullSize size) {
199                return getDamage(size) * EMP_MULT;      
200        }
201        public static float getFluxCost(HullSize size) {
202                return getDamage(size) * FLUX_PER_DAMAGE;       
203        }
204        
205        public void spawnLightning(ShipAPI ship, RecentHitData hit) {
206                CombatEngineAPI engine = Global.getCombatEngine();
207                
208                Vector2f from = ship.getLocation();
209                Vector2f point = hit.point;
210
211                float dist = Misc.getDistance(from, point);
212
213                if (dist > MAX_RANGE) return;
214                
215                float mult = getPowerMult(ship.getHullSize());
216                float damage = getDamage(ship.getHullSize());
217                float emp = getEMPDamage(ship.getHullSize());
218                
219                if (FLUX_PER_DAMAGE > 0f) {
220                        float fluxCost = getFluxCost(ship.getHullSize());
221                        if (!deductFlux(ship, fluxCost)) {
222                                return;
223                        }
224                }
225                
226                DwellerShroud shroud = DwellerShroud.getShroudFor(ship);
227                if (shroud != null) {
228                        float angle = Misc.getAngleInDegrees(ship.getLocation(), point);
229                        from = Misc.getUnitVectorAtDegreeAngle(angle + 90f - 180f * (float) Math.random());
230                        from.scale((0.5f + (float) Math.random() * 0.25f) * shroud.getShroudParams().maxOffset * shroud.getShroudParams().overloadArcOffsetMult);
231                        Vector2f.add(ship.getLocation(), from, from);
232                }
233                
234                
235                float arcSpeed = RiftLightningEffect.RIFT_LIGHTNING_SPEED;
236                
237                EmpArcParams params = new EmpArcParams();
238                params.segmentLengthMult = 8f;
239                params.zigZagReductionFactor = 0.15f;
240                //params.fadeOutDist = ship.getCollisionRadius() * 0.5f;
241                params.fadeOutDist = 50f;
242                params.minFadeOutMult = 10f;
243//              params.flickerRateMult = 0.7f;
244                params.flickerRateMult = 0.3f;
245//              params.flickerRateMult = 0.05f;
246//              params.glowSizeMult = 3f;
247//              params.brightSpotFullFraction = 0.5f;
248                
249                params.movementDurOverride = Math.max(0.05f, dist / arcSpeed);
250                
251                float arcWidth = 40f + mult * 40f;
252                float explosionRadius = 40f + mult * 40f;
253                
254                //Color color = weapon.getSpec().getGlowColor();
255                Color color = RiftLightningEffect.RIFT_LIGHTNING_COLOR;
256                EmpArcEntityAPI arc = (EmpArcEntityAPI)engine.spawnEmpArcVisual(from, ship, point, null,
257                                arcWidth, // thickness
258                                color,
259                                new Color(255,255,255,255),
260                                params
261                                );
262                arc.setCoreWidthOverride(arcWidth / 2f);
263                
264                arc.setRenderGlowAtStart(false);
265                arc.setFadedOutAtStart(true);
266                arc.setSingleFlickerMode(true);
267
268                float volume = 0.75f + 0.25f * mult;
269                float pitch = 1f + 0.25f * (1f - mult);
270                Global.getSoundPlayer().playSound("rift_lightning_fire", pitch, volume, from, ship.getVelocity());
271                
272                if (shroud != null) {
273                        DwellerShroudParams shroudParams = shroud.getShroudParams();
274                        params = new EmpArcParams();
275                        params.segmentLengthMult = 4f;
276                        params.glowSizeMult = 4f;
277                        params.flickerRateMult = 0.5f + (float) Math.random() * 0.5f;
278                        params.flickerRateMult *= 1.5f;
279                        
280                        //Color fringe = shroudParams.overloadArcFringeColor;
281                        Color fringe = color;
282                        Color core = Color.white;
283
284                        float thickness = shroudParams.overloadArcThickness;
285                        
286                        //Vector2f to = Misc.getPointAtRadius(from, 1f);
287                        
288                        float angle = Misc.getAngleInDegrees(from, ship.getLocation());
289                        angle = angle + 90f * ((float) Math.random() - 0.5f);
290                        Vector2f dir = Misc.getUnitVectorAtDegreeAngle(angle);
291                        dist = shroudParams.maxOffset * shroud.getShroudParams().overloadArcOffsetMult;
292                        dist = dist * 0.5f + dist * 0.5f * (float) Math.random();
293                        //dist *= 1.5f;
294                        dist *= 0.5f;
295                        dir.scale(dist);
296                        Vector2f to = Vector2f.add(from, dir, new Vector2f());
297                        
298                        arc = (EmpArcEntityAPI)engine.spawnEmpArcVisual(
299                                        from, ship, to, ship, thickness, fringe, core, params);
300                        
301                        arc.setCoreWidthOverride(shroudParams.overloadArcCoreThickness);
302                        arc.setSingleFlickerMode(false);
303                        //arc.setRenderGlowAtStart(false);
304                }
305                
306                
307                
308                float explosionDelay = params.movementDurOverride * 0.8f;
309                Global.getCombatEngine().addPlugin(new DelayedCombatActionPlugin(explosionDelay, new Runnable() {
310                        @Override
311                        public void run() {
312                                DamagingExplosionSpec spec = new DamagingExplosionSpec(
313                                                0.1f, // duration
314                                                explosionRadius, // radius
315                                                explosionRadius * 0.5f, // coreRadius
316                                                damage, // maxDamage
317                                                damage / 2f, // minDamage
318                                                CollisionClass.PROJECTILE_NO_FF, // collisionClass
319                                                CollisionClass.GAS_CLOUD, // collisionClassByFighter - using to flag it as from this effect
320                                                3f, // particleSizeMin
321                                                3f, // particleSizeRange
322                                                0.5f, // particleDuration
323                                                0, // particleCount
324                                                new Color(255,255,255,0), // particleColor
325                                                new Color(255,100,100,0)  // explosionColor
326                                                );
327                                spec.setMinEMPDamage(emp * 0.5f);
328                                spec.setMaxEMPDamage(emp);
329
330                                spec.setDamageType(DamageType.ENERGY);
331                                spec.setUseDetailedExplosion(false);
332                                spec.setSoundSetId("rift_lightning_explosion");
333                                spec.setSoundVolume(0.5f + 0.5f * mult);
334
335                                DamagingProjectileAPI explosion = engine.spawnDamagingExplosion(spec, ship, point);
336                                
337                                //explosion.addDamagedAlready(target);
338                                //color = new Color(255,75,75,255);
339
340                                //              float baseSize = 10f;
341                                //
342                                //              NEParams p = RiftCascadeMineExplosion.createStandardRiftParams(
343                                //                              color, baseSize);
344                                //              //p.hitGlowSizeMult = 0.5f;
345                                //              p.noiseMult = 6f;
346                                //              p.thickness = 25f;
347                                //              p.fadeOut = 0.5f;
348                                //              p.spawnHitGlowAt = 1f;
349                                //              p.additiveBlend = true;
350                                //              p.blackColor = Color.white;
351                                //              p.underglow = null;
352                                //              p.withNegativeParticles = false;
353                                //              p.withHitGlow = false;
354                                //              p.fadeIn = 0f;
355                                //              //p.numRiftsToSpawn = 1;
356                                //
357                                //              RiftCascadeMineExplosion.spawnStandardRift(explosion, p);
358
359                                Color color = RiftLightningEffect.RIFT_LIGHTNING_COLOR;
360                                color = new Color(255,75,75,255);
361                                NEParams p = RiftCascadeMineExplosion.createStandardRiftParams(
362                                                color, 14f + 6f * mult);
363                                p.fadeOut = 0.5f + 0.5f * mult;
364                                p.hitGlowSizeMult = 0.6f;
365                                p.thickness = 50f;
366                                //p.thickness = 25f;
367
368
369                                //              p.hitGlowSizeMult = 0.5f;
370                                //              p.thickness = 25f;
371                                //              p.fadeOut = 0.25f;
372
373                                RiftCascadeMineExplosion.spawnStandardRift(explosion, p);
374                        }
375                }));
376                
377        }
378        
379        @Override
380        public boolean shouldAddDescriptionToTooltip(HullSize hullSize, ShipAPI ship, boolean isForModSpec) {
381                return false;
382        }
383
384        @Override
385        public void addPostDescriptionSection(TooltipMakerAPI tooltip, HullSize hullSize, final ShipAPI ship, float width, boolean isForModSpec) {
386                float pad = 3f;
387                float opad = 10f;
388                Color h = Misc.getHighlightColor();
389                Color bad = Misc.getNegativeHighlightColor();
390                
391                
392                tooltip.addPara("Fires rift lightning bolts at locations recently hit by this ship's weapons.", opad);
393                
394                tooltip.addPara("The amount of damage and the flux generated depend on the size of the ship the "
395                                + "hullmod is installed on. The probability of a bolt being fired increases with the number "
396                                + "of hits landed over the previous %s seconds. "
397                                + "Bolts are not triggered by weapons dealing soft flux damage, such as beams.", opad,
398                                Misc.getHighlightColor(), "" + (int)Math.round(RECENT_HIT_DUR));
399                
400                //tooltip.addSectionHeading("Reload capacity", Alignment.MID, opad);
401                
402                if (isForModSpec || (ship == null && !Global.CODEX_TOOLTIP_MODE)) return;
403                
404                tooltip.setBgAlpha(0.9f);
405                
406                HullSize [] sizes = new HullSize[] {HullSize.FRIGATE, HullSize.DESTROYER, HullSize.CRUISER, HullSize.CAPITAL_SHIP}; 
407                
408                float damW = 87f;
409                float empW = 87f;
410                float fluxW = 87f;
411                float sizeW = width - damW - empW - fluxW - 10f;
412                tooltip.beginTable(Misc.getBasePlayerColor(), Misc.getDarkPlayerColor(), Misc.getBrightPlayerColor(),
413                                   20f, true, true, 
414                                   new Object [] {"Ship size", sizeW, "Damage", damW, "EMP", empW, "Flux cost", fluxW});
415                
416                for (HullSize size : sizes) {
417                        float damage = getDamage(size);
418                        float emp = getEMPDamage(size);
419                        float fluxCost = getFluxCost(size);
420                        
421                        Color c = Misc.getGrayColor();
422                        if (size == hullSize || Global.CODEX_TOOLTIP_MODE) {
423                                c = Misc.getHighlightColor();
424                        }
425                        tooltip.addRow(Alignment.MID, c, Misc.getHullSizeStr(size),
426                                                   Alignment.MID, c, "" + (int) Math.round(damage),
427                                                   Alignment.MID, c, "" + (int) Math.round(emp),
428                                                   Alignment.MID, c, "" + "" + (int) Math.round(fluxCost));
429                }
430                tooltip.addTable("", 0, opad);
431                
432                tooltip.addSpacer(5f);
433                
434                addCrewCasualties(tooltip, opad);
435                
436//              if (Global.CODEX_TOOLTIP_MODE) {
437//                      return;
438//              }
439        }       
440        
441
442//      @Override
443//      public String getSModDescriptionParam(int index, HullSize hullSize, ShipAPI ship) {
444//              if (index == 0) return "" + (int) Math.round(SMOD_CR_PENALTY * 100f) + "%";
445//              if (index == 1) return "" + (int) Math.round(SMOD_MAINTENANCE_PENALTY) + "%";
446//              return null;
447//      }
448//      
449//      @Override
450//      public boolean isSModEffectAPenalty() {
451//              return true;
452//      }
453}
454
455
456
457
458
459
460
461
462
463
464
465
466
467