001package com.fs.starfarer.api.impl.combat.threat;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import java.awt.Color;
007
008import org.lwjgl.util.vector.Vector2f;
009
010import com.fs.starfarer.api.Global;
011import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin;
012import com.fs.starfarer.api.combat.CombatEngineAPI;
013import com.fs.starfarer.api.combat.DamageType;
014import com.fs.starfarer.api.combat.EmpArcEntityAPI;
015import com.fs.starfarer.api.combat.EmpArcEntityAPI.EmpArcParams;
016import com.fs.starfarer.api.combat.MutableShipStatsAPI;
017import com.fs.starfarer.api.combat.ShipAPI;
018import com.fs.starfarer.api.combat.ShipAPI.HullSize;
019import com.fs.starfarer.api.combat.ShipSystemAPI;
020import com.fs.starfarer.api.combat.ShipSystemAPI.SystemState;
021import com.fs.starfarer.api.combat.ShipwideAIFlags.AIFlags;
022import com.fs.starfarer.api.combat.WeaponAPI.WeaponSize;
023import com.fs.starfarer.api.impl.combat.BaseShipSystemScript;
024import com.fs.starfarer.api.input.InputEventAPI;
025import com.fs.starfarer.api.loading.WeaponSlotAPI;
026import com.fs.starfarer.api.util.Misc;
027import com.fs.starfarer.api.util.Misc.FindShipFilter;
028
029public class EnergyLashSystemScript extends BaseShipSystemScript {
030        
031        public static float MAX_LASH_RANGE = 1500f;
032        
033        public static float DAMAGE = 0;
034        public static float EMP_DAMAGE = 1500;
035        
036        public static float MIN_COOLDOWN = 2f;
037        public static float MAX_COOLDOWN = 10f;
038        public static float COOLDOWN_DP_MULT = 0.33f;
039        
040        public static float MIN_HIT_ENEMY_COOLDOWN = 2f;
041        public static float MAX_HIT_ENEMY_COOLDOWN = 5f;
042        public static float HIT_PHASE_ENEMY_COOLDOWN_MULT = 2f;
043        
044        public static float SWARM_TIMEOUT = 10f;
045        
046        public static float PHASE_OVERLOAD_DUR = 1f;
047        
048
049        
050        public static class DelayedCombatActionPlugin extends BaseEveryFrameCombatPlugin {
051                float elapsed = 0f;
052                float delay;
053                Runnable r;
054                
055                public DelayedCombatActionPlugin(float delay, Runnable r) {
056                        this.delay = delay;
057                        this.r = r;
058                }
059                        
060                @Override
061                public void advance(float amount, List<InputEventAPI> events) {
062                        if (Global.getCombatEngine().isPaused()) return;
063                
064                        elapsed += amount;
065                        if (elapsed < delay) return;
066                        
067                        r.run();
068        
069                        CombatEngineAPI engine = Global.getCombatEngine();
070                        engine.removePlugin(this);
071                }
072        }
073        
074        
075        
076        protected WeaponSlotAPI mainSlot;
077        protected List<WeaponSlotAPI> slots;
078        protected boolean readyToFire = true;
079        protected float sinceSwarmTargeted = SWARM_TIMEOUT;
080        protected float cooldownToSet = -1f;
081        
082        protected void findSlots(ShipAPI ship) {
083                if (slots != null) return;
084                slots = new ArrayList<>();
085                for (WeaponSlotAPI slot : ship.getHullSpec().getAllWeaponSlotsCopy()) {
086                        if (slot.isSystemSlot()) {
087                                slots.add(slot);
088                                if (slot.getSlotSize() == WeaponSize.MEDIUM) {
089                                        mainSlot = slot;
090                                }
091                        }
092                }
093        }
094        
095        public void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel) {
096                ShipAPI ship = null;
097                //boolean player = false;
098                if (stats.getEntity() instanceof ShipAPI) {
099                        ship = (ShipAPI) stats.getEntity();
100                        //player = ship == Global.getCombatEngine().getPlayerShip();
101                } else {
102                        return;
103                }
104                
105                sinceSwarmTargeted += Global.getCombatEngine().getElapsedInLastFrame();
106                
107                if ((state == State.COOLDOWN || state == State.IDLE) && cooldownToSet >= 0f) {
108                        ship.getSystem().setCooldown(cooldownToSet);
109                        ship.getSystem().setCooldownRemaining(cooldownToSet);
110                        cooldownToSet = -1f;
111                        
112                }
113                
114                if (state == State.IDLE || state == State.COOLDOWN || effectLevel <= 0f) {
115                        readyToFire = true;
116                }
117                
118                if (state == State.IN || state == State.OUT) {
119                        float jitterLevel = effectLevel;
120
121                        float maxRangeBonus = 150f;
122                        //float jitterRangeBonus = jitterLevel * maxRangeBonus;
123                        float jitterRangeBonus = (1f - effectLevel * effectLevel) * maxRangeBonus;
124                        
125                        float brightness = 0f;
126                        float threshold = 0.1f;
127                        if (effectLevel < threshold) {
128                                brightness = effectLevel / threshold;
129                        } else {
130                                brightness = 1f - (effectLevel - threshold) / (1f - threshold);
131                        }
132                        if (brightness < 0) brightness = 0;
133                        if (brightness > 1) brightness = 1;
134                        if (state == State.OUT) {
135                                jitterRangeBonus = 0f;
136                                brightness = effectLevel * effectLevel;
137                        }
138                        Color color = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR;
139                        //color = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR_BRIGHT;
140                        //ship.setJitterUnder(this, color, jitterLevel, 21, 0f, 3f + jitterRangeBonus);
141                        //ship.setJitter(this, JITTER_COLOR, jitterLevel, 4, 0f, 0 + jitterRangeBonus * 0.67f);
142                        //ship.setJitter(this, color, jitterLevel, 1, 0f, 3f);
143                        ship.setJitter(this, color, jitterLevel, 5, 0f, 3f + jitterRangeBonus);
144                }
145                
146                if (effectLevel == 1 && readyToFire) {
147                        ShipAPI target = findTarget(ship);
148                        readyToFire = false;
149                        if (target != null) {
150                                CombatEngineAPI engine = Global.getCombatEngine();
151                                findSlots(ship);
152
153                                Vector2f slotLoc = mainSlot.computePosition(ship);
154
155                                EmpArcParams params = new EmpArcParams();
156                                params.segmentLengthMult = 8f;
157                                params.zigZagReductionFactor = 0.15f;
158                                params.fadeOutDist = 500f;
159                                params.minFadeOutMult = 2f;
160                                params.flickerRateMult = 0.7f;
161                                
162                                //params.movementDurMax = 0.1f;
163//                              params.movementDurMin = 0.25f;
164//                              params.movementDurMax = 0.25f;
165                                
166                                
167                                if (ship.getOwner() == target.getOwner()) {
168                                        //params.flickerRateMult = 0.6f;
169                                        params.flickerRateMult = 0.3f;
170                                        
171                                        Color color = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR;
172                                        if (ThreatSwarmAI.isAttackSwarm(target)) {
173                                                color = VoltaicDischargeOnFireEffect.PHASE_FRINGE_COLOR;
174                                        }
175                                        float emp = 0;
176                                        float dam = 0;
177                                        EmpArcEntityAPI arc = (EmpArcEntityAPI)engine.spawnEmpArcPierceShields(ship, slotLoc, ship, target,
178                                                        DamageType.ENERGY, 
179                                                        dam,
180                                                        emp, // emp 
181                                                        100000f, // max range 
182                                                        "energy_lash_friendly_impact",
183                                                        100f, // thickness
184                                                        //new Color(100,165,255,255),
185                                                        color,
186                                                        new Color(255,255,255,255),
187                                                        params
188                                                        );
189                                        arc.setTargetToShipCenter(slotLoc, target);
190                                        arc.setCoreWidthOverride(50f);
191        
192                                        arc.setSingleFlickerMode(true);
193                                        //arc.setFadedOutAtStart(true);
194                                        Global.getSoundPlayer().playSound("energy_lash_fire", 1f, 1f, ship.getLocation(), ship.getVelocity());
195                                } else {
196                                        params.flickerRateMult = 0.4f;
197                                        
198                                        int numArcs = slots.size();
199                                        //numArcs = 1;
200                                        
201                                        float emp = EMP_DAMAGE;
202                                        float dam = DAMAGE;
203                                        
204                                        for (int i = 0; i < numArcs; i++) {
205                                                float delay = 0.03f * i;
206                                                //delay = 0f;
207                                                
208//                                              EmpArcParams params2 = new EmpArcParams();
209//                                              params2.segmentLengthMult = 8f;
210//                                              params2.zigZagReductionFactor = 0.15f;
211//                                              params2.fadeOutDist = 500f;
212//                                              params2.minFadeOutMult = 2f;
213//                                              params2.flickerRateMult = 0.8f - i * 0.1f;
214//                                              params2.flickerRateMult = 0.8f;
215                                                
216                                                int index = i;
217                                                ShipAPI ship2 = ship;
218                                                Runnable r = new Runnable() {
219                                                        @Override
220                                                        public void run() {
221                                                                Vector2f slotLoc = slots.get(index).computePosition(ship2);
222                                                                Color color = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR;
223                                                                Color core = new Color(255,255,255,255);
224                                                                if (target.isPhased()) {
225                                                                        color = VoltaicDischargeOnFireEffect.PHASE_FRINGE_COLOR;
226                                                                        core = VoltaicDischargeOnFireEffect.PHASE_CORE_COLOR;
227                                                                }
228                                                                //color = Misc.interpolateColor(color, new Color(255,0,255), 0.25f);
229                                                                EmpArcEntityAPI arc = (EmpArcEntityAPI)engine.spawnEmpArc(ship2, slotLoc, ship2, target,
230                                                                                DamageType.ENERGY, 
231                                                                                dam,
232                                                                                emp, // emp 
233                                                                                100000f, // max range 
234                                                                                "energy_lash_enemy_impact",
235                                                                                60f, // thickness
236                                                                                //new Color(100,165,255,255),
237                                                                                color,
238                                                                                core,
239                                                                                params
240                                                                                );
241                                                                arc.setCoreWidthOverride(40f);
242                                                                arc.setSingleFlickerMode(true);
243                                                        }
244                                                };
245                                                if (delay <= 0f) {
246                                                        r.run();
247                                                } else {
248                                                        Global.getCombatEngine().addPlugin(new DelayedCombatActionPlugin(delay, r));
249                                                }
250                                                
251                                                Global.getSoundPlayer().playSound("energy_lash_fire_at_enemy", 1f, 1f, ship.getLocation(), ship.getVelocity());
252                                                
253//                                              arc.setFadedOutAtStart(true);
254//                                              arc.setRenderGlowAtStart(false);
255                                        }
256                                }
257                                
258                                applyEffectToTarget(ship, target);
259                        }
260                }
261        }
262        
263        
264        
265        
266        protected void applyEffectToTarget(ShipAPI ship, ShipAPI target) {
267                boolean isSwarm = ThreatSwarmAI.isAttackSwarm(target);
268                if (!isSwarm) {
269                        if (target == null || target.getSystem() == null || target.isHulk()) return;
270                }
271                if (ship == null || ship.getSystem() == null || ship.isHulk()) return;
272                
273                if (ship.getOwner() == target.getOwner()) {
274                        if (target.getSystem() != null && target.getSystem().getScript() instanceof EnergyLashActivatedSystem) {
275                                EnergyLashActivatedSystem script = (EnergyLashActivatedSystem) target.getSystem().getScript();
276                                script.hitWithEnergyLash(ship, target);
277                        } else if (isSwarm) {
278                                VoltaicDischargeOnFireEffect.setSwarmPhaseMode(target);
279                                sinceSwarmTargeted = 0f;
280                        }
281                        
282                        float cooldown = target.getHullSpec().getSuppliesToRecover();
283                        //float cooldown = target.getMutableStats().getSuppliesToRecover().getBaseValue();
284                        //cooldown = (int)Math.round(target.getMutableStats().getDynamic().getMod(Stats.DEPLOYMENT_POINTS_MOD).computeEffective(cooldown));
285                        
286                        cooldown = MIN_COOLDOWN + cooldown * COOLDOWN_DP_MULT;
287                        if (cooldown > MAX_COOLDOWN) cooldown = MAX_COOLDOWN;
288                        if (target.isFighter()) cooldown = MIN_COOLDOWN;
289//                      ship.getSystem().setCooldown(cooldown);
290//                      ship.getSystem().setCooldownRemaining(cooldown);
291                        cooldownToSet = cooldown;
292                } else {
293                        boolean hitPhase = false;
294                        if (target.isPhased()) {
295                                target.setOverloadColor(VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR_BRIGHT);
296                                target.getFluxTracker().beginOverloadWithTotalBaseDuration(PHASE_OVERLOAD_DUR);
297                                if (target.getFluxTracker().showFloaty() || 
298                                                ship == Global.getCombatEngine().getPlayerShip() ||
299                                                target == Global.getCombatEngine().getPlayerShip()) {
300                                        target.getFluxTracker().playOverloadSound();
301                                        target.getFluxTracker().showOverloadFloatyIfNeeded("Phase Field Disruption!",
302                                                                        VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR, 4f, true);
303                                }
304                                
305                                Global.getCombatEngine().addPlugin(new BaseEveryFrameCombatPlugin() {
306                                        @Override
307                                        public void advance(float amount, List<InputEventAPI> events) {
308                                                if (!target.getFluxTracker().isOverloadedOrVenting()) {
309                                                        target.resetOverloadColor();
310                                                        Global.getCombatEngine().removePlugin(this);
311                                                }
312                                        }
313                                });
314                                
315                                hitPhase = true;
316                        }
317                        
318                        float cooldown = MIN_HIT_ENEMY_COOLDOWN + 
319                                        (MAX_HIT_ENEMY_COOLDOWN - MIN_HIT_ENEMY_COOLDOWN) * (float) Math.random();
320                        if (hitPhase) {
321                                cooldown *= HIT_PHASE_ENEMY_COOLDOWN_MULT;
322                        }
323                        if (cooldown > MAX_COOLDOWN) cooldown = MAX_COOLDOWN;
324//                      ship.getSystem().setCooldown(cooldown);
325//                      ship.getSystem().setCooldownRemaining(cooldown);
326                        cooldownToSet = cooldown;
327                }
328                
329//              ship.getSystem().setCooldown(0.2f);
330//              ship.getSystem().setCooldownRemaining(0.2f);
331        }
332
333        public void unapply(MutableShipStatsAPI stats, String id) {
334        }
335        
336        public StatusData getStatusData(int index, State state, float effectLevel) {
337                return null;
338        }
339        
340        @Override
341        public String getInfoText(ShipSystemAPI system, ShipAPI ship) {
342                if (system.isOutOfAmmo()) return null;
343                if (system.getState() != SystemState.IDLE) return null;
344                
345                ShipAPI target = findTarget(ship);
346                if (target != null && target != ship) {
347                        return "READY";
348                }
349                if ((target == null || target == ship) && ship.getShipTarget() != null) {
350                        return "OUT OF RANGE";
351                }
352                return "NO TARGET";
353        }
354
355        public boolean isInRange(ShipAPI ship, ShipAPI target) {
356                float range = getRange(ship);
357                float dist = Misc.getDistance(ship.getLocation(), target.getLocation());
358                float radSum = ship.getCollisionRadius() + target.getCollisionRadius();
359                return dist <= range + radSum;
360        }
361        
362        public boolean isValidLashTarget(ShipAPI ship, ShipAPI other) {
363                if (other == null) return false;
364                if (other.isHulk() || other.getOwner() == 100) return false;
365                if (other.isShuttlePod()) return false;
366                if (other.hasTag(ThreatShipConstructionScript.SHIP_UNDER_CONSTRUCTION)) return false;
367                if (other.isFighter() && other.getOwner() == ship.getOwner()) {
368                        return ThreatSwarmAI.isAttackSwarm(other) && sinceSwarmTargeted > SWARM_TIMEOUT;
369                }
370                
371                if (other.isFighter()) return false;
372                if (other.getOwner() == ship.getOwner()) {
373                        if (other.getSystem() == null) return false;
374                        if (!(other.getSystem().getScript() instanceof EnergyLashActivatedSystem)) return false;
375                        if (other.getSystem().getCooldownRemaining() > 0) return false;
376                        if (other.getSystem().isActive()) return false;
377                        if (other.getFluxTracker().isOverloadedOrVenting()) return false;
378                }
379                return true;
380                //return !other.isFighter();
381        }
382        
383        
384        protected ShipAPI findTarget(ShipAPI ship) {
385                float range = getRange(ship);
386                boolean player = ship == Global.getCombatEngine().getPlayerShip();
387                ShipAPI target = ship.getShipTarget();
388
389                float extraRange = 0f;
390                if (ship.getShipAI() != null && ship.getAIFlags().hasFlag(AIFlags.CUSTOM1)){
391                        target = (ShipAPI) ship.getAIFlags().getCustom(AIFlags.CUSTOM1);
392                        extraRange += 500f;
393                }
394                
395                
396                if (target != null) {
397                        float dist = Misc.getDistance(ship.getLocation(), target.getLocation());
398                        float radSum = ship.getCollisionRadius() + target.getCollisionRadius();
399                        if (dist > range + radSum + extraRange) target = null;
400                } else {
401                        FindShipFilter filter = s -> isValidLashTarget(ship, s);
402                        
403                        if (target == null || target.getOwner() == ship.getOwner()) {
404                                if (player) {
405                                        target = Misc.findClosestShipTo(ship, ship.getMouseTarget(), HullSize.FIGHTER, range, true, false, filter);
406                                } else {
407                                        Object test = ship.getAIFlags().getCustom(AIFlags.MANEUVER_TARGET);
408                                        if (test instanceof ShipAPI) {
409                                                target = (ShipAPI) test;
410                                                float dist = Misc.getDistance(ship.getLocation(), target.getLocation());
411                                                float radSum = ship.getCollisionRadius() + target.getCollisionRadius();
412                                                if (dist > range + radSum) target = null;
413                                        }
414                                }
415                        }
416                        if (target == null) {
417                                target = Misc.findClosestShipTo(ship, ship.getLocation(), HullSize.FIGHTER, range, true, false, filter);
418                        }
419                }
420                
421                return target;
422        }
423        
424        @Override
425        public boolean isUsable(ShipSystemAPI system, ShipAPI ship) {
426                ShipAPI target = findTarget(ship);
427                return target != null && target != ship;
428                //return super.isUsable(system, ship);
429        }
430        
431        public static float getRange(ShipAPI ship) {
432                if (ship == null) return MAX_LASH_RANGE;
433                return ship.getMutableStats().getSystemRangeBonus().computeEffective(MAX_LASH_RANGE);
434        }
435        
436}
437
438
439
440
441
442
443
444