001package com.fs.starfarer.api.impl.combat;
002
003import java.awt.Color;
004import java.util.ArrayList;
005import java.util.List;
006
007import org.lwjgl.util.vector.Vector2f;
008
009import com.fs.starfarer.api.Global;
010import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin;
011import com.fs.starfarer.api.combat.CollisionClass;
012import com.fs.starfarer.api.combat.CombatEngineAPI;
013import com.fs.starfarer.api.combat.EveryFrameCombatPlugin;
014import com.fs.starfarer.api.combat.MissileAPI;
015import com.fs.starfarer.api.combat.MutableShipStatsAPI;
016import com.fs.starfarer.api.combat.ShipAPI;
017import com.fs.starfarer.api.combat.ShipCommand;
018import com.fs.starfarer.api.combat.ShipSystemAPI;
019import com.fs.starfarer.api.combat.WeaponAPI;
020import com.fs.starfarer.api.combat.WeaponAPI.WeaponType;
021import com.fs.starfarer.api.input.InputEventAPI;
022import com.fs.starfarer.api.loading.WeaponSlotAPI;
023import com.fs.starfarer.api.util.Misc;
024
025/**
026 *  The way to provide custom params is to have a derived class that sets p = <params> in its constructor. 
027 *  
028 * @author Alex
029 *
030 * Copyright 2022 Fractal Softworks, LLC
031 */
032public class OrionDeviceStats extends BaseShipSystemScript {
033
034        public static class PusherPlateImpulse {
035                public float force;
036                public float dur;
037                public float elapsed;
038        }
039        public static class PusherPlateState {
040                public float compression;
041                public float vel;
042                public List<PusherPlateImpulse> impulses = new ArrayList<PusherPlateImpulse>();
043                
044                public void addImpulse(float force, float dur) {
045                        PusherPlateImpulse ppi = new PusherPlateImpulse();
046                        ppi.force = force;
047                        ppi.dur = dur;
048                        impulses.add(ppi);
049                
050                }
051                public void advance(float amount) {
052                        List<PusherPlateImpulse> remove = new ArrayList<PusherPlateImpulse>();
053                        float totalForce = 0f;
054                        for (PusherPlateImpulse curr : impulses) {
055                                totalForce += curr.force;
056                                curr.elapsed += amount;
057                                if (curr.elapsed >= curr.dur) {
058                                        remove.add(curr);
059                                }
060                        }
061                        impulses.removeAll(remove);
062                        
063                        // assuming k of 1, and a mass of 1
064                        float springForce = compression;
065                        float netForce = totalForce - springForce;
066                        
067                        vel += netForce * amount;
068                        compression += vel * amount;
069                        
070                        if (compression > 1f) {
071                                compression = 1f;
072                                vel = 0f;
073                        }
074                        float min = 0f;
075                        //min = -0.5f;
076                        if (compression < min) {
077                                compression = min;
078                                vel = 0f;
079                        }
080                        
081                }
082        }
083        
084        
085        public static class OrionDeviceParams {
086                public float bombFadeInTime = 0.15f;
087                public float bombLiveTime = 0.25f;
088                public float bombSpeed = 50f;
089                public float bombInheritedVelocityFraction = 0.5f;
090                
091                public float shapedExplosionOffset = 50f;
092                public float shapedExplosionEndSizeMin = 1f;
093                public float shapedExplosionEndSizeMax = 2f;
094                public Color shapedExplosionColor = new Color(255,125,25,155);
095                public int shapedExplosionNumParticles = 200;
096                public float shapedExplosionMinParticleSize = 80;
097                public float shapedExplosionMaxParticleSize = 100;
098                public float shapedExplosionScatter = 100f;
099                public float shapedExplosionMinParticleVel = 100;
100                public float shapedExplosionMaxParticleVel = 350f;
101                public float shapedExplosionMinParticleDur = 1f;
102                public float shapedExplosionMaxParticleDur = 2f;
103                public float shapedExplosionArc = 90f;
104                public Color jitterColor = new Color(255,125,25,55);
105                public float maxJitterDur = 2f;
106                
107                public float pusherPlateMaxOffset = 14f;
108                public float pusherPlateImpulseForce = 10f;
109                public float pusherPlateImpulseDuration = 0.2f;
110                
111                public float impactAccel = 5000f;
112                public float impactRateMult = 4f;
113                
114                public boolean recolorTowardsEngineColor = false;
115                
116                public String bombWeaponId = "od_bomblauncher";
117        }
118        
119        
120        protected OrionDeviceParams p = new OrionDeviceParams();
121        protected PusherPlateState pusherState = new PusherPlateState();
122        
123        public OrionDeviceStats() {
124                p = new OrionDeviceParams();
125                //p.recolorTowardsEngineColor = true;
126        }
127        
128
129        protected Color orig = null;
130        protected void recolor(ShipAPI ship) {
131                if (ship == null) return;
132                if (!p.recolorTowardsEngineColor) return;
133                
134                if (orig == null) orig = p.shapedExplosionColor;
135                
136                Color curr = ship.getEngineController().getFlameColorShifter().getCurr();
137                
138                p.shapedExplosionColor = Misc.interpolateColor(orig, curr, 0.75f);
139                p.shapedExplosionColor = Misc.setAlpha(p.shapedExplosionColor, orig.getAlpha());
140        }
141        
142        protected boolean wasIdle = false;
143        protected boolean deployedBomb = false;
144        public void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel) {
145                ShipAPI ship = null;
146                //boolean player = false;
147                if (stats.getEntity() instanceof ShipAPI) {
148                        ship = (ShipAPI) stats.getEntity();
149                } else {
150                        return;
151                }
152                
153                recolor(ship);
154                
155                if (effectLevel >= 1 && !deployedBomb) {
156                        for (WeaponSlotAPI slot : ship.getHullSpec().getAllWeaponSlotsCopy()) {
157                                if (slot.isSystemSlot()) {
158                                        spawnBomb(ship, slot);
159                                }
160                        }
161                        deployedBomb = true;
162                        //pusherPlateOffset = 1f;
163                } else if (state == State.COOLDOWN) {
164                        deployedBomb = false;
165                }
166                
167                
168                float amount = Global.getCombatEngine().getElapsedInLastFrame();
169                pusherState.advance(amount);
170                
171                for (WeaponAPI w : ship.getAllWeapons()) {
172                        Vector2f offset = new Vector2f(p.pusherPlateMaxOffset, 0f);
173                        //pusherPlateOffset = 1f;
174                        offset.scale(pusherState.compression);
175                        if (w.getSpec().hasTag("pusherplate")) {
176                                w.setRenderOffsetForDecorativeBeamWeaponsOnly(offset);
177                        }
178                }
179                
180                advanceImpl(amount, ship, state, effectLevel);
181        }
182        
183        protected void advanceImpl(float amount, ShipAPI ship, State state, float effectLevel) {
184                
185        }
186        
187        
188        public void unapply(MutableShipStatsAPI stats, String id) {
189        }
190        
191        public void spawnBomb(ShipAPI source, WeaponSlotAPI slot) {
192                CombatEngineAPI engine = Global.getCombatEngine();
193                Vector2f loc = slot.computePosition(source);
194                float angle = slot.computeMidArcAngle(source);
195                
196                if (pusherState.compression > 0) {
197                        Vector2f offset = new Vector2f(p.pusherPlateMaxOffset, 0f);
198                        offset.scale(pusherState.compression);
199                        offset = Misc.rotateAroundOrigin(offset, source.getFacing());
200                        Vector2f.add(loc, offset, loc);
201                }
202                
203                MissileAPI bomb = (MissileAPI) engine.spawnProjectile(source, null, 
204                                                                                                                          p.bombWeaponId, 
205                                                                                                                          loc, 
206                                                                                                                          angle, source.getVelocity());
207                if (source != null) {
208                        Global.getCombatEngine().applyDamageModifiersToSpawnedProjectileWithNullWeapon(
209                                                                                        source, WeaponType.MISSILE, false, bomb.getDamage());
210                }
211                
212                float fadeInTime = p.bombFadeInTime;
213                Vector2f inheritedVel = new Vector2f(source.getVelocity());
214                inheritedVel.scale(p.bombInheritedVelocityFraction);
215                float speed = p.bombSpeed;
216                
217                Vector2f vel = Misc.getUnitVectorAtDegreeAngle(angle);
218                vel.scale(speed);
219                Vector2f.add(vel, inheritedVel, vel);
220                bomb.getVelocity().set(vel);
221                bomb.fadeOutThenIn(fadeInTime);
222                
223                bomb.setCollisionClass(CollisionClass.NONE);
224                bomb.setEmpResistance(1000);
225                bomb.setEccmChanceOverride(1f);
226                
227                
228                float liveTime = p.bombLiveTime;
229                bomb.setMaxFlightTime(liveTime);
230                
231
232                Global.getCombatEngine().addPlugin(createBombImpactPlugin(source, slot, bomb, loc, angle));
233        }
234        
235        protected void notifySpawnedExplosionParticles(Vector2f bombLoc) {
236                
237        }
238        
239        protected EveryFrameCombatPlugin createBombImpactPlugin(final ShipAPI ship, final WeaponSlotAPI launchSlot,
240                        final MissileAPI bomb, final Vector2f launchLoc, final float launchAngle) {
241                
242                return new BaseEveryFrameCombatPlugin() {
243                        float elapsed = 0f;
244                        float impactTime = 0f;
245                        float brakingTime = 0f;
246                        float forceAngle;
247                        float jitterTime = 0f;
248                        boolean triggered = false;
249                        boolean braking = false;
250                        boolean done = false;
251                        
252                        @Override
253                        public void advance(float amount, List<InputEventAPI> events) {
254                                if (Global.getCombatEngine().isPaused()) return;
255                        
256                                elapsed += amount;
257                                
258                                String impactCounterId = "od_system_counter";
259                                
260                                if (bomb.isFizzling()) {
261                                        if (!triggered) {
262                                                
263                                                pusherState.addImpulse(p.pusherPlateImpulseForce, p.pusherPlateImpulseDuration);
264                                                
265                                                forceAngle = Misc.getAngleInDegrees(bomb.getLocation(), launchSlot.computePosition(ship));
266                                                float angleToShip = Misc.getAngleInDegrees(bomb.getLocation(), ship.getLocation());
267                                                if (Misc.getAngleDiff(angleToShip, forceAngle) > 90f) {
268                                                        forceAngle += 180f;
269                                                }
270                                                
271                                                float diff = Misc.getAngleDiff(angleToShip, forceAngle);
272                                                float turnDir = Misc.getClosestTurnDirection(angleToShip, forceAngle);
273                                                forceAngle = angleToShip + diff * turnDir * 0.2f;
274                                                
275                                                triggered = true;
276                                                ship.getMutableStats().getDynamic().getMod(impactCounterId).modifyFlat("od_launch_" + launchLoc, 1f);
277                                                
278                                                if (Global.getCombatEngine().getViewport().isNearViewport(ship.getLocation(), 800f)) {
279                                                        float angle = forceAngle + 180f;
280                                                        
281                                                        int numParticles = p.shapedExplosionNumParticles;
282                                                        float minSize = p.shapedExplosionMinParticleSize;
283                                                        float maxSize = p.shapedExplosionMaxParticleSize;
284                                                        Color pc = p.shapedExplosionColor;
285                                                        
286                                                        float minDur = p.shapedExplosionMinParticleDur;
287                                                        float maxDur = p.shapedExplosionMaxParticleDur;
288                                                        
289                                                        float arc = p.shapedExplosionArc; 
290                                                        float scatter = p.shapedExplosionScatter;
291                                                        float minVel = p.shapedExplosionMinParticleVel;
292                                                        float maxVel = p.shapedExplosionMaxParticleVel;
293                                                        
294                                                        float launchOffset = p.shapedExplosionOffset;
295                                                        float endSizeMin = p.shapedExplosionEndSizeMin;
296                                                        float endSizeMax = p.shapedExplosionEndSizeMax;
297                                                        
298                                                        Vector2f spawnPoint = Misc.getUnitVectorAtDegreeAngle(forceAngle);
299                                                        spawnPoint.scale(launchOffset);
300                                                        Vector2f.add(bomb.getLocation(), spawnPoint, spawnPoint);
301                                                        for (int i = 0; i < numParticles; i++) {
302                                                                //p.setMaxAge(500 + (int)(Math.random() * 1000f));
303                                                                float angleOffset = (float) Math.random();
304                                                                if (angleOffset > 0.2f) {
305                                                                        angleOffset *= angleOffset;
306                                                                }
307                                                                float speedMult = 1f - angleOffset;
308                                                                speedMult = 0.5f + speedMult * 0.5f;
309                                                                angleOffset *= Math.signum((float) Math.random() - 0.5f);
310                                                                angleOffset *= arc/2f;
311                                                                float theta = (float) Math.toRadians(angle + angleOffset);
312                                                                float r = (float) (Math.random() * Math.random() * scatter);
313                                                                float x = (float)Math.cos(theta) * r;
314                                                                float y = (float)Math.sin(theta) * r;
315                                                                Vector2f pLoc = new Vector2f(spawnPoint.x + x, spawnPoint.y + y);
316        
317                                                                float speed = minVel + (maxVel - minVel) * (float) Math.random();
318                                                                speed *= speedMult;
319                                                                
320                                                                Vector2f pVel = Misc.getUnitVectorAtDegreeAngle((float) Math.toDegrees(theta));
321                                                                pVel.scale(speed);
322                                                                
323                                                                float pSize = minSize + (maxSize - minSize) * (float) Math.random();
324                                                                float pDur = minDur + (maxDur - minDur) * (float) Math.random();
325                                                                float endSize = endSizeMin + (endSizeMax - endSizeMin) * (float) Math.random();
326                                                                //Global.getCombatEngine().addSmoothParticle(pLoc, pVel, pSize, 1f, pDur, pc);
327                                                                Global.getCombatEngine().addNebulaParticle(pLoc, pVel, pSize, endSize, 0.1f, 0.5f, pDur, pc);
328                                                                //Global.getCombatEngine().addNebulaSmoothParticle(pLoc, pVel, pSize, endSize, 0.1f, 0.5f, pDur, pc);
329                                                                //Global.getCombatEngine().addSwirlyNebulaParticle(pLoc, pVel, pSize, endSize, 0.1f, 0.5f, pDur, pc, false);
330                                                        }
331                                                        //Global.getCombatEngine().setPaused(true);
332                                                        
333                                                        notifySpawnedExplosionParticles(bomb.getLocation());
334                                                }
335                                        }
336                                }
337                                
338                                boolean multipleImpacts = ship.getMutableStats().getDynamic().getMod(impactCounterId).computeEffective(0) > 1;
339                                
340                                String id = "od_system_mod";
341                                ship.getMutableStats().getMaxSpeed().unmodifyFlat(id);
342                                //float maxSpeedWithoutBonus = ship.getMaxSpeedWithoutBoost();
343                                float maxSpeedWithoutBonus = ship.getMutableStats().getMaxSpeed().getModifiedValue();
344                                
345//                              if (!triggered) {
346//                                      ship.giveCommand(ShipCommand.ACCELERATE, null, 0);
347//                              }
348                                
349                                if (triggered) {
350                                        jitterTime += amount;
351                                        float intensity = bomb.getFlightTime() / bomb.getMaxFlightTime();
352                                        if (intensity > 1f) intensity = 1f;
353                                        if (triggered) intensity = 1f;
354                                        if (braking) { 
355                                                intensity = 1f - brakingTime * 2f;
356                                                if (intensity < 0) intensity = 0;
357                                        }
358                                        float alt = 1f - (jitterTime / p.maxJitterDur);
359                                        if (alt < intensity) {
360                                                intensity = Math.max(alt, 0f);
361                                        }
362                                        Color jc = p.jitterColor;
363                                        ship.setJitter(this, jc, intensity, 3, 0f, 0f);
364                                }
365                                
366                                if (triggered && !braking) {
367                                        impactTime += amount * p.impactRateMult;
368                                        
369                                        float mag = (1f - impactTime) * (1f - impactTime);
370                                        
371                                        //mag = (float) Math.sin(impactTime * Math.PI);
372                                        //mag *= mag;
373                                        if (mag > 0) mag = (float) Math.sqrt(mag);
374                                        
375                                        Vector2f forcePoint = launchSlot.computePosition(ship);
376                                        
377                                        float dirToCenter = Misc.getAngleInDegrees(forcePoint, ship.getLocation());
378                                        float angleDiff = Misc.getAngleDiff(forceAngle, dirToCenter);
379//                                      if (angleDiff > 180f) { 
380//                                              angleDiff = 360f - angleDiff;
381//                                      }
382                                        
383                                        float totalAccel = p.impactAccel;
384                                        float portionAppliedToAngularVelocity = angleDiff * 1f / 90f;
385                                        if (portionAppliedToAngularVelocity > 1f) portionAppliedToAngularVelocity = 1f;
386                                        
387                                        Vector2f acc = Misc.getUnitVectorAtDegreeAngle(forceAngle);
388                                        acc.scale(totalAccel * (1f - portionAppliedToAngularVelocity * 0.2f));
389                                        acc.scale(mag * amount);
390                                        
391                                        Vector2f.add(ship.getVelocity(), acc, ship.getVelocity());
392                                        
393                                        float angVelChange = portionAppliedToAngularVelocity * ship.getMaxTurnRate() * 0.25f;
394                                        angVelChange *= mag;
395                                        angVelChange *= Misc.getClosestTurnDirection(forceAngle, dirToCenter);
396                                        ship.setAngularVelocity(ship.getAngularVelocity() + angVelChange * amount);
397                                        
398                                        float maxSpeedBoost = 1000f * Math.max(0f, (1f - portionAppliedToAngularVelocity) * 0.5f);
399                                        
400                                        if (maxSpeedBoost > 0) {
401                                                ship.getMutableStats().getMaxSpeed().modifyFlat(id, maxSpeedBoost);
402                                        } else {
403                                                ship.getMutableStats().getMaxSpeed().unmodifyFlat(id);
404                                        }
405                                        ship.getMutableStats().getDeceleration().modifyFlat(id, 1f * Math.max(maxSpeedWithoutBonus, ship.getVelocity().length() - maxSpeedWithoutBonus));
406                                        //ship.getMutableStats().getTurnAcceleration().modifyFlat(id, 100f * (1f - mag));
407                                        //ship.giveCommand(ShipCommand.ACCELERATE, null, 0);
408                                        ship.blockCommandForOneFrame(ShipCommand.ACCELERATE);
409                                        ship.blockCommandForOneFrame(ShipCommand.ACCELERATE_BACKWARDS);
410                                        ship.giveCommand(ShipCommand.DECELERATE, null, 0);
411                                        //ship.getEngineController().forceShowAccelerating();
412                                        if (impactTime >= 1f) {
413                                                braking = true;
414                                                //ship.getMutableStats().getTurnAcceleration().unmodify(id);
415                                                ship.getMutableStats().getMaxSpeed().unmodify(id);
416                                        }
417                                }
418                                
419                                if (braking) {
420                                        if (!multipleImpacts) {
421                                                ship.getMutableStats().getDeceleration().modifyFlat(id, 2f * Math.max(maxSpeedWithoutBonus, ship.getVelocity().length() - maxSpeedWithoutBonus));
422                                                //ship.giveCommand(ShipCommand.ACCELERATE, null, 0);
423                                                ship.blockCommandForOneFrame(ShipCommand.ACCELERATE);
424                                                ship.blockCommandForOneFrame(ShipCommand.ACCELERATE_BACKWARDS);
425                                                ship.giveCommand(ShipCommand.DECELERATE, null, 0);
426                                                //ship.getEngineController().forceShowAccelerating();
427                                        }
428                                        brakingTime += amount;
429                                        float threshold = 3f;
430                                        if (multipleImpacts) threshold = 0.1f;
431                                        if (brakingTime >= threshold || ship.getVelocity().length() <= maxSpeedWithoutBonus) {
432                                                done = true;
433                                        }
434                                }
435                                
436                                
437                                if ((!triggered && elapsed > 1f) || done) {
438                                        Global.getCombatEngine().removePlugin(this);
439                                        
440                                        ship.getMutableStats().getDeceleration().unmodify(id);
441                                        //ship.getMutableStats().getTurnAcceleration().unmodify(id);
442                                        ship.getMutableStats().getMaxSpeed().unmodify(id);
443                                        
444                                        ship.getMutableStats().getDynamic().getMod(impactCounterId).unmodifyFlat("od_launch_" + launchLoc);
445                                }
446                        }
447                };
448        }
449
450
451        @Override
452        public boolean isUsable(ShipSystemAPI system, ShipAPI ship) {
453                if (ship.getEngineController().isFlamedOut() || ship.getEngineController().isFlamingOut()) {
454                        return false;
455                }
456                return super.isUsable(system, ship);
457        }
458        
459        
460        
461}
462
463
464
465
466
467
468
469