001package com.fs.starfarer.api.impl.combat.dweller;
002
003import java.util.Iterator;
004
005import java.awt.Color;
006
007import org.lwjgl.util.vector.Vector2f;
008
009import com.fs.starfarer.api.Global;
010import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType;
011import com.fs.starfarer.api.campaign.CargoStackAPI;
012import com.fs.starfarer.api.campaign.SpecialItemData;
013import com.fs.starfarer.api.combat.CollisionClass;
014import com.fs.starfarer.api.combat.CombatEngineAPI;
015import com.fs.starfarer.api.combat.CombatEntityAPI;
016import com.fs.starfarer.api.combat.DamageType;
017import com.fs.starfarer.api.combat.DamagingProjectileAPI;
018import com.fs.starfarer.api.combat.EmpArcEntityAPI;
019import com.fs.starfarer.api.combat.EmpArcEntityAPI.EmpArcParams;
020import com.fs.starfarer.api.combat.MissileAPI;
021import com.fs.starfarer.api.combat.ShipAPI;
022import com.fs.starfarer.api.combat.ShipAPI.HullSize;
023import com.fs.starfarer.api.impl.campaign.ids.Items;
024import com.fs.starfarer.api.impl.combat.NegativeExplosionVisual.NEParams;
025import com.fs.starfarer.api.impl.combat.RiftCascadeMineExplosion;
026import com.fs.starfarer.api.impl.combat.dweller.DwellerShroud.DwellerShroudParams;
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.WeightedRandomPicker;
032
033public class ShroudedLensHullmod extends HumanShipShroudedHullmod {
034        
035        public static float MAX_RANGE = 400f;
036        public static float RADIUS = 50f;
037        
038        public static float MIN_REFIRE_DELAY = 0.9f;
039        public static float MAX_REFIRE_DELAY = 1.1f;
040
041        public static float FLUX_PER_DAMAGE = 1f;
042        
043        public static float DAMAGE = 75f;
044        public static float MIN_ROF_MULT = 1f;
045        public static float MAX_ROF_MULT = 4f;
046        
047        public String getDescriptionParam(int index, HullSize hullSize) {
048                return null;
049        }
050        
051        @Override
052        public CargoStackAPI getRequiredItem() {
053                return Global.getSettings().createCargoStack(CargoItemType.SPECIAL, 
054                                                                new SpecialItemData(Items.SHROUDED_LENS, null), null);
055        }
056        
057        public static String DATA_KEY = "core_ShroudedLensHullmod_data_key";
058        
059        public static class ShroudedLensHullmodData {
060                //IntervalUtil interval = new IntervalUtil(0.75f, 1.25f);
061                float untilAttack = 0f;
062                float sinceAttack = 1000f;
063        }
064        
065        public static ShroudedLensHullmodData getData(ShipAPI ship) {
066                CombatEngineAPI engine = Global.getCombatEngine();
067                String key = DATA_KEY + "_" + ship.getId();
068                ShroudedLensHullmodData data = (ShroudedLensHullmodData) engine.getCustomData().get(key);
069                if (data == null) {
070                        data = new ShroudedLensHullmodData();
071                        engine.getCustomData().put(key, data);
072                }
073                return data;
074        }
075        
076        @Override
077        public void advanceInCombat(ShipAPI ship, float amount) {
078                super.advanceInCombat(ship, amount);
079                
080                if (!ship.isAlive()) return;
081                if (amount <= 0f) return;
082                
083                ShroudedLensHullmodData data = getData(ship);
084                data.untilAttack -= amount * getRoF(ship.getHullSize());
085                if (data.untilAttack <= 0f) {
086                        CombatEntityAPI target = findTarget(ship);
087                        if (target != null) {
088                                spawnExplosion(ship, target);
089                        }
090                        data.untilAttack = MIN_REFIRE_DELAY + (float) Math.random() * (MAX_REFIRE_DELAY - MIN_REFIRE_DELAY);
091                }
092        }
093        
094        public static float getPowerMult(HullSize size) {
095                switch (size) {
096                case CAPITAL_SHIP: return 1f;
097                case CRUISER: return 0.6666666667f;
098                case DESTROYER: return 0.3333333333f;
099                case FIGHTER:
100                case FRIGATE:
101                        return 0f;
102                }
103                return 1f;
104        }
105        
106        public static float getRoF(HullSize size) {
107                float mult = getPowerMult(size);
108                return MIN_ROF_MULT + (MAX_ROF_MULT - MIN_ROF_MULT) * mult;     
109        }
110        public static float getFluxCost(HullSize size) {
111                return DAMAGE * FLUX_PER_DAMAGE;        
112        }
113        public static float getDamage(HullSize size) {
114                return DAMAGE;  
115        }
116        
117        public void spawnExplosion(ShipAPI ship, CombatEntityAPI target) {
118                CombatEngineAPI engine = Global.getCombatEngine();
119                
120                
121                float angle = Misc.getAngleInDegrees(target.getLocation(), ship.getLocation());
122                angle += 45f - 90f * (float) Math.random();
123                Vector2f from = Misc.getUnitVectorAtDegreeAngle(angle);
124                from.scale(10000f);
125                
126                float targetRadius = Misc.getTargetingRadius(from, target, false);              
127                Vector2f point = Misc.getUnitVector(target.getLocation(), from);
128                point.scale(targetRadius * (0.8f + (float) Math.random() * 0.4f));
129                Vector2f.add(target.getLocation(), point, point);
130
131                float dist = Misc.getDistance(from, point);
132                
133                //float mult = getPowerMult(ship.getHullSize());
134                float damage = getDamage(ship.getHullSize());
135                
136                if (FLUX_PER_DAMAGE > 0f) {
137                        float fluxCost = getFluxCost(ship.getHullSize());
138                        //if (!ship.getFluxTracker().increaseFlux(fluxCost, false)) {
139                        if (!deductFlux(ship, fluxCost)) {
140                                return;
141                        }
142                }
143                
144                DwellerShroud shroud = DwellerShroud.getShroudFor(ship);
145                if (shroud != null) {
146                        angle = Misc.getAngleInDegrees(ship.getLocation(), point);
147                        from = Misc.getUnitVectorAtDegreeAngle(angle + 90f - 180f * (float) Math.random());
148                        from.scale((0.5f + (float) Math.random() * 0.25f) * shroud.getShroudParams().maxOffset * shroud.getShroudParams().overloadArcOffsetMult);
149                        Vector2f.add(ship.getLocation(), from, from);
150                }
151                
152                Color color = RiftLightningEffect.RIFT_LIGHTNING_COLOR;
153                
154                if (shroud != null) {
155                        DwellerShroudParams shroudParams = shroud.getShroudParams();
156                        EmpArcParams params = new EmpArcParams();
157                        params.segmentLengthMult = 4f;
158                        params.glowSizeMult = 4f;
159                        params.flickerRateMult = 0.5f + (float) Math.random() * 0.5f;
160                        params.flickerRateMult *= 1.5f;
161                        
162                        //Color fringe = shroudParams.overloadArcFringeColor;
163                        Color fringe = color;
164                        Color core = Color.white;
165
166                        float thickness = shroudParams.overloadArcThickness;
167                        
168                        //Vector2f to = Misc.getPointAtRadius(from, 1f);
169                        
170                        angle = Misc.getAngleInDegrees(from, ship.getLocation());
171                        angle = angle + 90f * ((float) Math.random() - 0.5f);
172                        Vector2f dir = Misc.getUnitVectorAtDegreeAngle(angle);
173                        dist = shroudParams.maxOffset * shroud.getShroudParams().overloadArcOffsetMult;
174                        dist = dist * 0.5f + dist * 0.5f * (float) Math.random();
175                        //dist *= 1.5f;
176                        dist *= 0.5f;
177                        dir.scale(dist);
178                        Vector2f to = Vector2f.add(from, dir, new Vector2f());
179                        
180                        EmpArcEntityAPI arc = (EmpArcEntityAPI)engine.spawnEmpArcVisual(
181                                        from, ship, to, ship, thickness, fringe, core, params);
182                        
183                        arc.setCoreWidthOverride(shroudParams.overloadArcCoreThickness);
184                        arc.setSingleFlickerMode(false);
185                        //arc.setRenderGlowAtStart(false);
186                }
187                
188                //float explosionRadius = 40f + mult * 40f;
189                float explosionRadius = RADIUS;
190                
191                DamagingExplosionSpec spec = new DamagingExplosionSpec(
192                                0.1f, // duration
193                                explosionRadius, // radius
194                                explosionRadius * 0.5f, // coreRadius
195                                damage, // maxDamage
196                                damage, // / 2f, // minDamage - no damage dropoff with range
197                                CollisionClass.PROJECTILE_NO_FF, // collisionClass
198                                CollisionClass.PROJECTILE_FIGHTER, // collisionClassByFighter - using to flag it as from this effect
199                                3f, // particleSizeMin
200                                3f, // particleSizeRange
201                                0.5f, // particleDuration
202                                0, // particleCount
203                                new Color(255,255,255,0), // particleColor
204                                new Color(255,100,100,0)  // explosionColor
205                                );
206
207                spec.setDamageType(DamageType.ENERGY);
208                spec.setUseDetailedExplosion(false);
209                spec.setSoundSetId("abyssal_glare_explosion");
210                //spec.setSoundVolume(0.5f + 0.5f * mult);
211                spec.setSoundVolume(0.33f);
212
213                DamagingProjectileAPI explosion = engine.spawnDamagingExplosion(spec, ship, point);
214                
215                //explosion.addDamagedAlready(target);
216                //color = new Color(255,75,75,255);
217
218                float baseSize = 7f;
219
220                NEParams p = RiftCascadeMineExplosion.createStandardRiftParams(
221                                color, baseSize);
222                //p.hitGlowSizeMult = 0.5f;
223                p.noiseMult = 6f;
224                p.thickness = 25f;
225                p.fadeOut = 0.5f;
226                p.spawnHitGlowAt = 1f;
227                p.additiveBlend = true;
228                p.blackColor = Color.white;
229                p.underglow = null;
230                p.withNegativeParticles = false;
231                p.withHitGlow = false;
232                p.fadeIn = 0f;
233                //p.numRiftsToSpawn = 1;
234
235                RiftCascadeMineExplosion.spawnStandardRift(explosion, p);
236                
237                
238                // the "beam"
239                
240                float thickness = 30f;
241                //Color color = weapon.getSpec().getGlowColor();
242                Color coreColor = Color.white;
243                coreColor = Misc.zeroColor;
244                coreColor = color;
245                
246                color = new Color(255,0,30,255);
247                coreColor = new Color(255,10,255,255);
248                
249                color = DwellerShroud.SHROUD_GLOW_COLOR;
250                coreColor = color;
251                //coreColor = Color.white;
252                
253                float coreWidthMult = 1f;
254                
255                
256//              from = ship.getLocation();
257//              if (shroud != null) {
258//                      angle = Misc.getAngleInDegrees(ship.getLocation(), target.getLocation());
259//                      from = Misc.getUnitVectorAtDegreeAngle(angle + 90f - 180f * (float) Math.random());
260//                      from.scale((0.5f + (float) Math.random() * 0.25f) * shroud.getShroudParams().maxOffset);
261//                      Vector2f.add(ship.getLocation(), from, from);
262//              }
263                
264                EmpArcParams params = new EmpArcParams();
265                //params.segmentLengthMult = 10000f;
266                params.segmentLengthMult = 4f;
267                
268//              params.maxZigZagMult = 0f;
269//              params.zigZagReductionFactor = 1f;
270                
271                params.maxZigZagMult = 0.25f;
272                //params.maxZigZagMult = 0f;
273                params.zigZagReductionFactor = 1f;
274                
275                //params.glowColorOverride = new Color(255,10,155,255);
276                
277                //params.zigZagReductionFactor = 0.25f;
278                //params.maxZigZagMult = 0f;
279                //params.flickerRateMult = 0.75f;
280//              params.flickerRateMult = 1f;
281//              params.flickerRateMult = 0.75f;
282                params.flickerRateMult = 0.75f + 0.25f * (float) Math.random();
283                
284                params.fadeOutDist = 150f;
285                params.minFadeOutMult = 5f;
286                
287                params.glowSizeMult = 0.5f;
288                
289                //params.glowAlphaMult = 0.5f;
290                //params.flamesOutMissiles = false;
291                
292//              params.movementDurOverride = 0.1f;
293//              params.flickerRateMult = 0.5f;
294//              params.glowSizeMult = 1f;
295//              params.brightSpotFadeFraction = 0.1f;
296//              params.brightSpotFullFraction = 0.9f;
297                
298//              params.maxZigZagMult = 1f;
299//              params.zigZagReductionFactor = 0f;
300//              params.flickerRateMult = 0.25f;
301                
302                Vector2f to = point;
303                EmpArcEntityAPI arc = engine.spawnEmpArcVisual(from, ship, to, explosion, thickness, color, coreColor, params);
304                arc.setCoreWidthOverride(thickness * coreWidthMult);
305                arc.setSingleFlickerMode();
306                arc.setRenderGlowAtStart(false);
307                if (shroud != null) {
308                        arc.setFadedOutAtStart(true);
309                }
310                arc.setWarping(0.2f);
311                
312        }
313        
314        @Override
315        public boolean shouldAddDescriptionToTooltip(HullSize hullSize, ShipAPI ship, boolean isForModSpec) {
316                return false;
317        }
318
319        @Override
320        public void addPostDescriptionSection(TooltipMakerAPI tooltip, HullSize hullSize, final ShipAPI ship, float width, boolean isForModSpec) {
321                float pad = 3f;
322                float opad = 10f;
323                Color h = Misc.getHighlightColor();
324                Color bad = Misc.getNegativeHighlightColor();
325                
326                
327                tooltip.addPara("The \"lens\" (in the loosest sense of the word) focuses on nearby objects, "
328                                + "seemingly at random. Deals %s damage and generates %s flux.", opad, h,
329                                "" + (int) getDamage(hullSize) + " Energy", "" + (int) getFluxCost(hullSize));
330                
331                tooltip.addPara("The rate of fire depends on the size of the ship the "
332                                          + "hullmod is installed on. Can not be turned off during combat operations, and continues to "
333                                          + "function even if the ship is venting flux or overloaded.", opad);
334                
335                //tooltip.addSectionHeading("Reload capacity", Alignment.MID, opad);
336                
337                if (isForModSpec || (ship == null && !Global.CODEX_TOOLTIP_MODE)) return;
338                
339                tooltip.setBgAlpha(0.9f);
340                
341                HullSize [] sizes = new HullSize[] {HullSize.FRIGATE, HullSize.DESTROYER, HullSize.CRUISER, HullSize.CAPITAL_SHIP}; 
342                
343                float rofW = 130f;
344                float fluxW = 130f;
345                float sizeW = width - rofW - fluxW - 10f;
346                tooltip.beginTable(Misc.getBasePlayerColor(), Misc.getDarkPlayerColor(), Misc.getBrightPlayerColor(),
347                                   20f, true, true, 
348                                   new Object [] {"Ship size", sizeW, "Attacks / sec", rofW, "Flux / sec", fluxW});
349                
350                for (HullSize size : sizes) {
351                        float rof = getRoF(size);
352                        float flux = getFluxCost(size) * rof;
353                        Color c = Misc.getGrayColor();
354                        if (size == hullSize || Global.CODEX_TOOLTIP_MODE) {
355                                c = Misc.getHighlightColor();
356                        }
357                        tooltip.addRow(Alignment.MID, c, Misc.getHullSizeStr(size),
358                                                   Alignment.MID, c, "" + (int) Misc.getRoundedValueFloat(rof) + "",
359                                                   Alignment.MID, c, "" + (int) Misc.getRoundedValueFloat(flux) + "");
360                }
361                tooltip.addTable("", 0, opad);
362                
363                tooltip.addSpacer(5f);
364                
365                addCrewCasualties(tooltip, opad);
366                
367//              if (Global.CODEX_TOOLTIP_MODE) {
368//                      return;
369//              }
370        }       
371        
372
373//      @Override
374//      public String getSModDescriptionParam(int index, HullSize hullSize, ShipAPI ship) {
375//              if (index == 0) return "" + (int) Math.round(SMOD_CR_PENALTY * 100f) + "%";
376//              if (index == 1) return "" + (int) Math.round(SMOD_MAINTENANCE_PENALTY) + "%";
377//              return null;
378//      }
379//      
380//      @Override
381//      public boolean isSModEffectAPenalty() {
382//              return true;
383//      }
384        
385        
386        public CombatEntityAPI findTarget(ShipAPI ship) {
387                float range = MAX_RANGE;
388                Vector2f from = ship.getLocation();
389                
390                Iterator<Object> iter = Global.getCombatEngine().getAllObjectGrid().getCheckIterator(from,
391                                                                                                                                                        range * 2f, range * 2f);
392                int owner = ship.getOwner();
393                CombatEntityAPI best = null;
394                float minScore = Float.MAX_VALUE;
395                
396                //boolean ignoreFlares = ship != null && ship.getMutableStats().getDynamic().getValue(Stats.PD_IGNORES_FLARES, 0) >= 1;
397                //ignoreFlares |= weapon.hasAIHint(AIHints.IGNORES_FLARES);
398                boolean ignoreFlares = false; // doesn't care one way or another
399                
400                WeightedRandomPicker<CombatEntityAPI> picker = new WeightedRandomPicker<>();
401                
402                while (iter.hasNext()) {
403                        Object o = iter.next();
404                        if (!(o instanceof MissileAPI) &&
405                                        //!(o instanceof CombatAsteroidAPI) &&
406                                        !(o instanceof ShipAPI)) continue;
407                        CombatEntityAPI other = (CombatEntityAPI) o;
408                        if (other.getOwner() == owner) continue;
409                        
410                        if (other instanceof ShipAPI) {
411                                ShipAPI otherShip = (ShipAPI) other;
412                                //if (otherShip.isHulk()) continue;
413                                //if (!otherShip.isAlive()) continue;
414                                if (otherShip.isPhased()) continue;
415                                if (!otherShip.isTargetable()) continue;
416                        }
417                        
418                        if (other.getCollisionClass() == CollisionClass.NONE) continue;
419                        
420                        if (ignoreFlares && other instanceof MissileAPI) {
421                                MissileAPI missile = (MissileAPI) other;
422                                if (missile.isFlare()) continue;
423                        }
424
425                        float targetRadius = Misc.getTargetingRadius(from, other, false);
426                        float shipRadius = Misc.getTargetingRadius(other.getLocation(), ship, false);
427                        float dist = Misc.getDistance(from, other.getLocation()) - targetRadius - shipRadius;
428                        if (dist > range) continue;
429                        
430                        float score = dist;
431                        
432                        if (score < minScore) {
433                                minScore = score;
434                                best = other;
435                        }
436                        
437                        picker.add(other, 100f / Math.max(100f, score));
438                }
439                
440                //return best;
441                return picker.pick();
442        }       
443}
444
445
446
447
448
449
450
451
452
453
454
455
456
457