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.BoundsAPI.SegmentAPI;
013import com.fs.starfarer.api.combat.CombatEngineAPI;
014import com.fs.starfarer.api.combat.CombatFleetManagerAPI;
015import com.fs.starfarer.api.combat.ShipAPI;
016import com.fs.starfarer.api.impl.combat.RiftLanceEffect;
017import com.fs.starfarer.api.impl.combat.threat.ConstructionSwarmSystemScript.SwarmConstructableVariant;
018import com.fs.starfarer.api.impl.combat.threat.RoilingSwarmEffect.SwarmMember;
019import com.fs.starfarer.api.input.InputEventAPI;
020import com.fs.starfarer.api.util.IntervalUtil;
021import com.fs.starfarer.api.util.Misc;
022import com.fs.starfarer.api.util.WeightedRandomPicker;
023
024public class ThreatShipReclamationScript extends BaseEveryFrameCombatPlugin {
025        
026        public static float CR_PER_RECLAMATION_SWARM = 0.02f;
027        
028        public static float RECLAMATION_SWARM_SPEED_MULT = 0.67f;
029        public static float RECLAMATION_SWARM_COLLISION_MULT = 1.5f;
030        public static float RECLAMATION_SWARM_RADIUS_MULT = 2f;
031        public static float RECLAMATION_SWARM_HP_MULT = 2f;
032        public static float RECLAMATION_SWARM_FRAGMENT_SIZE_MULT = 0.67f;
033        
034        
035        
036        protected float elapsed = 0f;
037        protected ShipAPI primary = null;
038        protected List<ShipAPI> pieces = new ArrayList<>();
039        protected List<ShipAPI> swarms = new ArrayList<>();
040        protected float delay;
041        protected float fadeOutTime;
042        protected float origMaxSpeed = 500f;
043        
044        protected IntervalUtil interval = new IntervalUtil(0.075f, 0.125f);
045        protected IntervalUtil interval2 = new IntervalUtil(0.075f, 0.125f);
046        protected boolean spawnedSwarms = false;
047        
048        public ThreatShipReclamationScript(ShipAPI ship, float delay) {
049                this.delay = delay;
050                
051                this.primary = ship;
052                for (ShipAPI curr : Global.getCombatEngine().getShips()) {
053                        if (curr.getFleetMember() == ship.getFleetMember()) {
054                                pieces.add(curr);
055                        }
056                }
057                
058                switch (ship.getHullSize()) {
059                case CAPITAL_SHIP:
060                        this.fadeOutTime = 15f;
061                        break;
062                case CRUISER:
063                        this.fadeOutTime = 12f;
064                        break;
065                case DESTROYER:
066                        this.fadeOutTime = 9f;
067                        break;
068                case FRIGATE:
069                        this.fadeOutTime = 7f;
070                        break;
071                default:
072                        this.fadeOutTime = 7f;
073                        break;
074                }
075                
076                this.fadeOutTime += 3f;
077                
078                interval.forceIntervalElapsed();
079                
080                for (ShipAPI curr : pieces) {
081                        curr.addTag(ThreatHullmod.SHIP_BEING_RECLAIMED);
082                }
083        }
084
085        public List<ShipAPI> getPieces() {
086                return pieces;
087        }
088
089        @Override
090        public void advance(float amount, List<InputEventAPI> events) {
091                if (Global.getCombatEngine().isPaused()) return;
092        
093                elapsed += amount;
094                if (elapsed < delay) return;
095                
096                
097                CombatEngineAPI engine = Global.getCombatEngine();
098
099                float progress = (elapsed - delay) / fadeOutTime;
100                if (progress < 0f) progress = 0f;
101                if (progress > 1f) progress = 1f;
102                
103                
104                float remaining = fadeOutTime - (elapsed - delay);
105                
106                
107                if (elapsed > delay + 2f && remaining > 4f) {
108                        spawnSwarms(amount);
109                }
110                
111                boolean first = true;
112                boolean anyInEngine = false;
113                for (ShipAPI ship : pieces) {
114                        if (!engine.isInEngine(ship)) continue;
115                        anyInEngine = true;
116                        
117                        Vector2f vel = ship.getVelocity();
118                        Vector2f acc = new Vector2f(vel);
119                        if (acc.length() != 0) {
120                                acc.normalise();
121                                acc.scale(-1f);
122                                acc.scale(amount * ship.getDeceleration());
123                                Vector2f.add(vel, acc, vel);
124                                float speed = vel.length();
125                                if (speed <= 1 || speed < acc.length()) {
126                                        vel.set(0, 0);
127                                }
128                        }
129                        
130                        float alpha = 1f;
131                        if (progress > 0.5f) {
132                                alpha = (1f - progress) * 2f;
133                        }
134                        
135                        ship.setAlphaMult(alpha);
136                        
137                        if (first) {
138                                Global.getSoundPlayer().playLoop("reclamation_loop", ship, 1f, 1f, ship.getLocation(), ship.getVelocity());
139                                first = false;
140                        }
141                        
142                        if (false) {
143                                float jitterLevel = 1f - progress;
144                                if (fadeOutTime <= 4f) {
145                                        if (jitterLevel < 0.5f) {
146                                                jitterLevel *= 2f;
147                                        } else {
148                                                jitterLevel = (1f - jitterLevel) * 2f;
149                                        }
150                                } else {
151                                        if (jitterLevel < 0.5f) {
152                                                jitterLevel *= 2f;
153                                        } else if (remaining <= 2f) {
154                                                jitterLevel = remaining / 2f;
155                                        } else {
156                                                jitterLevel = 1f;
157                                        }
158                                }
159                                jitterLevel = (float) Math.sqrt(jitterLevel);
160                                
161                                //float jitterRange = 1f - progress;
162                                float jitterRange = 1f;
163                                if (remaining < 2f) {
164                                        jitterRange = remaining / 2f;
165                                } else {
166                                        jitterRange = (elapsed - delay) / Math.max(1f, fadeOutTime - 2f);
167                                }
168                                float maxRangeBonus = 25f;
169                                float jitterRangeBonus = jitterRange * maxRangeBonus;
170                                
171                                Color c = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR;
172                                c = Misc.setAlpha(c, 127);
173                                
174                                ship.setJitter(this, c, jitterLevel, 3, 0f, jitterRangeBonus);
175                        }
176                        
177                        spawnParticles(ship, amount, progress);
178                }
179
180                if (elapsed > fadeOutTime + delay || !anyInEngine) {
181                        for (ShipAPI ship : pieces) {
182                                engine.removeEntity(ship);
183                                ship.setAlphaMult(0f);
184                        }
185                        engine.removePlugin(this);
186                }
187        }
188        
189        protected void spawnParticles(ShipAPI ship, float amount, float progress) {
190                if (ship == null) return;
191                
192                float remaining = fadeOutTime - (elapsed - delay);
193                
194                interval.advance(amount);
195                if (interval.intervalElapsed()) {
196                        CombatEngineAPI engine = Global.getCombatEngine();
197                        
198                        Color c = RiftLanceEffect.getColorForDarkening(VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR);
199                        c = Misc.setAlpha(c, 50);
200                        float baseDuration = 2f;
201                        Vector2f vel = new Vector2f(ship.getVelocity());
202                        //float size = ship.getCollisionRadius() * 0.35f;
203                        float size = ship.getCollisionRadius() * 0.33f;
204                        
205                        float extraDur = 0f;
206                        if (remaining < 1f) extraDur = 1f;
207                        
208                        //for (int i = 0; i < 3; i++) {
209                        for (int i = 0; i < 11; i++) {
210                                Vector2f point = new Vector2f(ship.getLocation());
211                                point = Misc.getPointWithinRadiusUniform(point, ship.getCollisionRadius() * 0.75f, Misc.random);
212                                float dur = baseDuration + baseDuration * (float) Math.random();
213                                dur += extraDur;
214                                float nSize = size;
215                                Vector2f pt = Misc.getPointWithinRadius(point, nSize * 0.5f);
216                                Vector2f v = Misc.getUnitVectorAtDegreeAngle((float) Math.random() * 360f);
217                                v.scale(nSize + nSize * (float) Math.random() * 0.5f);
218                                v.scale(0.2f);
219                                Vector2f.add(vel, v, v);
220                                
221                                float maxSpeed = nSize * 1.5f * 0.2f; 
222                                float minSpeed = nSize * 1f * 0.2f; 
223                                float overMin = v.length() - minSpeed;
224                                if (overMin > 0) {
225                                        float durMult = 1f - overMin / (maxSpeed - minSpeed);
226                                        if (durMult < 0.1f) durMult = 0.1f;
227                                        dur *= 0.5f + 0.5f * durMult;
228                                }
229                                engine.addNegativeNebulaParticle(pt, v, nSize * 1f, 2f,
230                                                                                                0.5f / dur, 0f, dur, c);
231                        }
232                }
233        }
234        
235        protected void spawnSwarms(float amount) {
236                if (!spawnedSwarms) {
237                        int numSwarms = 3;
238                        
239                        for (SwarmConstructableVariant curr : ConstructionSwarmSystemScript.CONSTRUCTABLE) {
240                                if (curr.variantId.equals(primary.getVariant().getHullVariantId())) {
241                                        numSwarms = (int) Math.round(curr.cr * 100f);
242                                        break;
243                                }
244                        }
245                        
246                        for (int i = 0; i < numSwarms; i++) {
247                                ShipAPI curr = launchSwarm();
248                                swarms.add(curr);
249                        }
250                        spawnedSwarms = true;
251                }
252                
253                interval2.advance(amount * 2f);
254                if (interval2.intervalElapsed()) {
255                        WeightedRandomPicker<ShipAPI> picker = new WeightedRandomPicker<>();
256                        picker.addAll(pieces);
257                        
258                        for (ShipAPI curr : swarms) {
259                                RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(curr);
260                                if (swarm == null) continue;
261                                if (swarm.getNumActiveMembers() > swarm.params.baseMembersToMaintain) continue;
262                                
263                                SwarmMember p = swarm.addMember();
264                                
265                                ShipAPI piece = picker.pick();
266                                if (piece == null) continue;
267                                
268                                Vector2f loc = Misc.getPointWithinRadius(piece.getLocation(), piece.getCollisionRadius() * 0.5f);
269                                p.loc.set(loc);
270                                p.fader.setDurationIn(0.3f);
271                        }
272                        
273                }
274        }
275        
276        protected ShipAPI launchSwarm() {
277                String wingId = SwarmLauncherEffect.RECLAMATION_SWARM_WING;
278
279                CombatEngineAPI engine = Global.getCombatEngine();
280                CombatFleetManagerAPI manager = engine.getFleetManager(primary.getOriginalOwner());
281                manager.setSuppressDeploymentMessages(true);
282                
283                Vector2f loc = primary.getLocation();
284                float facing = (float) Math.random() * 360f;
285                
286                ShipAPI fighter = manager.spawnShipOrWing(wingId, loc, facing, 0f, null);
287                fighter.getWing().setSourceShip(primary);
288                
289                manager.setSuppressDeploymentMessages(false);
290                
291                fighter.getMutableStats().getMaxSpeed().modifyMult("construction_swarm", RECLAMATION_SWARM_SPEED_MULT);
292                
293                Vector2f takeoffVel = Misc.getUnitVectorAtDegreeAngle(facing);
294                takeoffVel.scale(fighter.getMaxSpeed() * 1f);
295                
296                fighter.setDoNotRender(true);
297                fighter.setExplosionScale(0f);
298                fighter.setHulkChanceOverride(0f);
299                fighter.setImpactVolumeMult(SwarmLauncherEffect.IMPACT_VOLUME_MULT);
300                fighter.getArmorGrid().clearComponentMap(); // no damage to weapons/engines
301                Vector2f.add(fighter.getVelocity(), takeoffVel, fighter.getVelocity());
302                
303                RoilingSwarmEffect swarm = FragmentSwarmHullmod.createSwarmFor(fighter);
304                RoilingSwarmEffect.getFlockingMap().remove(swarm.params.flockingClass, swarm);
305                swarm.params.flockingClass = FragmentSwarmHullmod.RECLAMATION_SWARM_FLOCKING_CLASS;
306                RoilingSwarmEffect.getFlockingMap().add(swarm.params.flockingClass, swarm);
307                swarm.params.memberExchangeClass = FragmentSwarmHullmod.RECLAMATION_SWARM_EXCHANGE_CLASS;
308                
309                
310                //swarm.params.flashFringeColor = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR;
311                swarm.params.flashFrequency = 5f;
312                swarm.params.flashProbability = 1f;
313                
314                // brownish/rusty
315                //swarm.params.flashFringeColor = new Color(255,95,50,50);
316                
317                swarm.params.flashFringeColor = new Color(255,70,30,50);
318                swarm.params.flashCoreRadiusMult = 0f;
319                
320                swarm.params.springStretchMult = 1f;
321                
322                //swarm.params.baseSpriteSize *= RECLAMATION_SWARM_FRAGMENT_SIZE_MULT;
323                //swarm.params.flashFringeColor = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR;
324
325                float collisionMult = RECLAMATION_SWARM_COLLISION_MULT;
326                float hpMult = RECLAMATION_SWARM_HP_MULT;
327                
328                for (SegmentAPI s : fighter.getExactBounds().getOrigSegments()) {
329                        s.getP1().scale(collisionMult);
330                        s.getP2().scale(collisionMult);
331                        s.set(s.getP1().x, s.getP1().y, s.getP2().x, s.getP2().y);
332                }
333                fighter.setCollisionRadius(fighter.getCollisionRadius() * collisionMult);
334                
335                fighter.setMaxHitpoints(fighter.getMaxHitpoints() * hpMult);
336                fighter.setHitpoints(fighter.getHitpoints() * hpMult);
337                
338                swarm.params.maxOffset *= RECLAMATION_SWARM_RADIUS_MULT;
339                
340                swarm.params.initialMembers = 0;
341                swarm.params.baseMembersToMaintain = 50;
342                
343//              int transfer = Math.min(numFragments, sourceSwarm.getNumActiveMembers());
344//              if (transfer > 0) {
345//                      loc = new Vector2f(takeoffVel);
346//                      loc.scale(0.5f);
347//                      Vector2f.add(loc, fighter.getLocation(), loc);
348//                      sourceSwarm.transferMembersTo(swarm, transfer, loc, 100f);
349//              }
350//              
351//              int add = numFragments - transfer;
352//              if (add > 0) {
353//                      swarm.addMembers(add);
354//              }
355                
356                return fighter;
357                
358        }
359}
360
361
362
363
364
365
366
367