001package com.fs.starfarer.api.impl.combat.threat;
002
003import java.util.HashMap;
004import java.util.Map;
005
006import org.lwjgl.util.vector.Vector2f;
007
008import com.fs.starfarer.api.GameState;
009import com.fs.starfarer.api.Global;
010import com.fs.starfarer.api.combat.CombatEngineAPI;
011import com.fs.starfarer.api.combat.CombatFleetManagerAPI;
012import com.fs.starfarer.api.combat.DamagingProjectileAPI;
013import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin;
014import com.fs.starfarer.api.combat.FighterWingAPI;
015import com.fs.starfarer.api.combat.OnFireEffectPlugin;
016import com.fs.starfarer.api.combat.ShipAPI;
017import com.fs.starfarer.api.combat.WeaponAPI;
018import com.fs.starfarer.api.impl.campaign.ids.HullMods;
019import com.fs.starfarer.api.impl.campaign.ids.Stats;
020import com.fs.starfarer.api.util.Misc;
021
022public class SwarmLauncherEffect implements OnFireEffectPlugin, EveryFrameWeaponEffectPlugin, SwarmLaunchingWeapon {
023
024        public static String CONSTRUCTION_SWARM_WING = "construction_swarm_wing";
025        public static String CONSTRUCTION_SWARM_VARIANT = "attack_swarm_Construction";
026        public static String RECLAMATION_SWARM_WING = "reclamation_swarm_wing";
027        public static String RECLAMATION_SWARM_VARIANT = "attack_swarm_Reclamation";
028        
029        public static String ATTACK_SWARM_HULL = "attack_swarm";
030        public static String ATTACK_SWARM_WING = "attack_swarm_wing";
031        public static String ATTACK_SWARM_VARIANT = "attack_swarm_Attack";
032        public static String SWARM_LAUNCHER = "swarm_launcher";
033        public static float IMPACT_VOLUME_MULT = 0.33f;
034        
035        public static float INITIAL_SPAWN_DELAY = 1f;
036        
037        public static Map<String, Integer> FRAGMENT_NUM = new HashMap<>();
038        public static Map<String, Integer> SWARM_RADIUS = new HashMap<>();
039        public static Map<String, Integer> WING_SIZE = new HashMap<>();
040        public static Map<String, String> WING_IDS = new HashMap<>();
041        
042        static {
043                FRAGMENT_NUM.put(ATTACK_SWARM_WING, 50);
044                SWARM_RADIUS.put(ATTACK_SWARM_WING, 20);
045                WING_SIZE.put(ATTACK_SWARM_WING, 4);
046                WING_IDS.put(SWARM_LAUNCHER, ATTACK_SWARM_WING);
047                
048                FRAGMENT_NUM.put(CONSTRUCTION_SWARM_WING, 50);
049                SWARM_RADIUS.put(CONSTRUCTION_SWARM_WING, 50);
050        }
051        
052        protected FighterWingAPI currWing = null;
053        protected boolean waitUntilOneLeft = false;
054        protected float elapsed = 0f;
055        
056        public SwarmLauncherEffect() {
057                
058        }
059        
060        @Override
061        public void advance(float amount, CombatEngineAPI engine, WeaponAPI weapon) {
062                ShipAPI ship = weapon.getShip();
063                if (ship == null) return;
064
065                elapsed += amount;
066                if (elapsed < INITIAL_SPAWN_DELAY || ship.hasTag(ThreatShipConstructionScript.SHIP_UNDER_CONSTRUCTION)) {
067                        weapon.setForceDisabled(true);
068                        return;
069                }
070                
071                RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship);
072                int active = swarm == null ? 0 : swarm.getNumActiveMembers();
073                int required = FRAGMENT_NUM.get(getWingId(weapon));
074                boolean disable = active < required;
075                //disable = false;
076                int max = getWingSize(weapon);
077                int swarmsActive = 0;
078                if (currWing != null) swarmsActive = currWing.getWingMembers().size();
079                waitUntilOneLeft |= currWing != null && swarmsActive >= max;
080                if (currWing != null && currWing.getWingMembers().size() == 1 && currWing.getLeader() != null) {
081                        float dist = Misc.getDistance(currWing.getLeader().getLocation(), weapon.getLocation());
082                        if (dist > 1000f) {
083                                waitUntilOneLeft = true;
084                        }
085                }
086                if (waitUntilOneLeft) {
087                        if (currWing != null && ((swarmsActive <= 1 && max > 1) || swarmsActive <= 0 && max <= 1)) {
088                                waitUntilOneLeft = false;
089                                currWing = null;
090                        } else {
091                                disable = true;
092                        }
093                }
094                
095                if (currWing == null && swarm != null) {
096                        int preferred = getPreferredNumFragmentsToFireConsideringAllWeapons(ship);
097                        preferred = (int) Math.min(preferred, swarm.params.baseMembersToMaintain * 0.9f);
098                        if (active < preferred ) {
099                                disable = true;
100                        }
101                }
102                
103                weapon.setForceDisabled(disable);
104                
105                boolean playerShip = Global.getCurrentState() == GameState.COMBAT &&
106                                Global.getCombatEngine() != null && Global.getCombatEngine().getPlayerShip() == ship;
107                if (playerShip) {
108                        Global.getCombatEngine().maintainStatusForPlayerShip(this,
109                                        Global.getSettings().getSpriteName("ui", "icon_tactical_swarm_launcher"),
110                                        weapon.getDisplayName(), 
111                                        "SWARMS ACTIVE: " + swarmsActive, // + " / " + max,
112                                        swarmsActive <= 0);
113                }
114                
115                weapon.setCustom(currWing);
116                
117                if (ship.getVariant().hasHullMod(HullMods.THREAT_HULLMOD)) {
118                        weapon.setAmmo(weapon.getMaxAmmo());
119                }
120                
121                if (disable) {
122                        return;
123                }
124                
125                weapon.setForceFireOneFrame(true);
126        }
127        
128        public String getWingId(WeaponAPI weapon) {
129                //String wingId = WING_IDS.get(SWARM_LAUNCHER);
130                String wingId = WING_IDS.get(weapon.getId());
131                if (wingId != null) return wingId;
132                return ATTACK_SWARM_WING;
133        }
134        
135        public int getWingSize(WeaponAPI weapon) {
136                float wingSize = WING_SIZE.get(getWingId(weapon));
137                if (weapon.getShip() != null) {
138                        wingSize = weapon.getShip().getMutableStats().getDynamic().getValue(
139                                        Stats.SWARM_LAUNCHER_WING_SIZE_MOD, wingSize);
140                }
141                //wingSize = 20;
142                //wingSize = 1;
143                return (int) Math.round(wingSize);
144        }
145        
146        public void onFire(DamagingProjectileAPI projectile, WeaponAPI weapon, CombatEngineAPI engine) {
147                
148                //FighterWingSpecAPI spec = Global.getSettings().getFighterWingSpec(ATTACK_SWARM_WING);
149
150                CombatFleetManagerAPI manager = engine.getFleetManager(projectile.getOwner());
151                manager.setSuppressDeploymentMessages(true);
152                ShipAPI leader = manager.spawnShipOrWing(getWingId(weapon), 
153                                                                projectile.getLocation(), projectile.getFacing(), 0f, null);
154                leader.getWing().setSourceShip(projectile.getSource());
155                manager.setSuppressDeploymentMessages(false);
156                
157                Vector2f takeoffVel = Misc.getUnitVectorAtDegreeAngle(projectile.getFacing());
158                takeoffVel.scale(leader.getMaxSpeed() * 1f);
159                
160                //Global.getSoundPlayer().playSound("threat_swarm_launched", 1f, 1f, projectile.getLocation(), takeoffVel);
161                
162                for (ShipAPI curr : leader.getWing().getWingMembers()) {
163                        curr.setDoNotRender(true);
164                        curr.setExplosionScale(0f);
165                        curr.setHulkChanceOverride(0f);
166                        curr.setImpactVolumeMult(IMPACT_VOLUME_MULT);
167                        curr.getArmorGrid().clearComponentMap(); // no damage to weapons/engines
168
169                        if (currWing != null) {
170                                if (curr.getWing() != null) {
171                                        curr.getWing().removeMember(curr);
172                                        // really important, otherwise this doesn't get cleaned up
173                                        manager.removeDeployed(curr.getWing(), false);
174                                }
175                                curr.setWing(currWing);
176                                currWing.addMember(curr);
177                        }
178                        
179                        Vector2f.add(curr.getVelocity(), takeoffVel, curr.getVelocity());
180                }
181                
182                currWing = leader.getWing();
183                
184                RoilingSwarmEffect sourceSwarm = RoilingSwarmEffect.getSwarmFor(weapon.getShip());
185                if (sourceSwarm != null) {
186                        RoilingSwarmEffect swarm = FragmentSwarmHullmod.createSwarmFor(leader);
187                        
188                        int required = FRAGMENT_NUM.get(getWingId(weapon));
189                        int transfer = required;
190                        
191                        if (transfer > 0) {
192                                Vector2f loc = new Vector2f(takeoffVel);
193                                loc.scale(0.5f);
194                                Vector2f.add(loc, leader.getLocation(), loc);
195                                sourceSwarm.transferMembersTo(swarm, transfer, loc, 100f);
196                        }
197                        
198                }
199                
200//              if (currWing.getWingMembers().size() >= getWingSize(weapon)) {
201//                      currWing = null;
202//              }
203                
204                engine.removeEntity(projectile);
205        }
206        
207        public int getPreferredNumFragmentsToFireConsideringAllWeapons(ShipAPI ship) {
208                RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship);
209                if (swarm == null) return 0;
210                
211                int req = 0;
212                for (WeaponAPI w : ship.getAllWeapons()) {
213                        if (w.getEffectPlugin() instanceof SwarmLaunchingWeapon) {
214                                if (w.isFiring()) continue;
215                                SwarmLaunchingWeapon effect = (SwarmLaunchingWeapon) w.getEffectPlugin();
216                                req += effect.getPreferredNumFragmentsToFire(w);
217                        }
218                }
219                return req;
220        }
221
222        @Override
223        public int getPreferredNumFragmentsToFire(WeaponAPI weapon) {
224                Integer required = FRAGMENT_NUM.get(getWingId(weapon));
225                if (required == null) return 0;
226                int wingSize = getWingSize(weapon);
227                int num = Math.min(wingSize, 2);
228                return required * num;
229        }
230        
231        
232//      
233//      public static RoilingSwarmEffect createTestDwellerSwarmFor(ShipAPI ship) {
234//              RoilingSwarmEffect existing = RoilingSwarmEffect.getSwarmFor(ship);
235//              if (existing != null) {
236//                      if (!"dweller_pieces".equals(existing.params.spriteKey)) {
237//                              existing.setForceDespawn(true);
238//                      }
239//                      //return existing;
240//              }
241//              
242//              RoilingSwarmParams params = new RoilingSwarmParams();
243//              float radius = 20f;
244//              int numMembers = 50;
245//
246//              
247////            "fx_particles1":"graphics/fx/fx_clouds00.png",
248////            "fx_particles2":"graphics/fx/fx_clouds01.png",
249////            "nebula_particles":"graphics/fx/nebula_colorless.png",
250////            "nebula_particles2":"graphics/fx/cleaner_clouds00.png",
251////            "dust_particles":"graphics/fx/dust_clouds_colorless.png",
252//              
253////            params.spriteKey = "dust_particles";
254//              params.spriteKey = "fx_particles1";
255//              params.spriteKey = "nebula_particles";
256//              params.spriteKey = "dweller_pieces";
257//              
258//              params.baseDur = 1f;
259//              params.durRange = 2f;
260//              params.memberRespawnRate = 100f;
261//              
262//              params.memberExchangeClass = null;
263//              params.flockingClass = null;
264//              params.maxSpeed = ship.getMaxSpeedWithoutBoost() + 
265//                                      Math.max(ship.getMaxSpeedWithoutBoost() * 0.25f + 50f, 100f);
266//              
267//              params.baseSpriteSize = 256f;
268//              params.baseSpriteSize = 128f * 1.5f * 0.67f;
269//              params.maxTurnRate = 120f;
270//              
271//              radius = 100f;
272//              numMembers = 100;
273//              
274//              params.flashCoreRadiusMult = 0f;
275//              //params.flashRadius = 0f;
276//              params.flashRadius = 200f;
277//              params.flashRateMult = 0.1f;
278//              params.flashFrequency = 10f;
279//              params.flashProbability = 1f;
280//              
281//              
282//              params.alphaMult = 1f;
283//              params.alphaMultBase = 0.1f;
284//              params.alphaMultFlash = 1f;
285//              params.color = RiftCascadeEffect.STANDARD_RIFT_COLOR;
286//              params.color = Misc.setAlpha(RiftCascadeEffect.EXPLOSION_UNDERCOLOR, 255);
287//              params.color = Misc.setBrightness(params.color, 155);
288//              params.flashFringeColor = params.color;
289//              params.flashCoreColor = params.color;
290//              
291//              params.color = new Color(100, 50, 255, 255);
292//              params.flashFringeColor = new Color(100, 100, 255, 255);
293//              params.flashCoreColor = new Color(100, 100, 255, 255);
294//              
295//              //params.renderFlashOnSameLayer = true;
296//              
297//              params.maxOffset = radius;
298//              
299//              //params.despawnDist = params.maxOffset + 300f;
300//              
301//              params.initialMembers = numMembers;
302//              params.baseMembersToMaintain = params.initialMembers;
303//              
304//              //params.springStretchMult = 1f;
305//              
306//              
307//              return new RoilingSwarmEffect(ship, params) {
308//                      protected IntervalUtil interval = new IntervalUtil(0.075f, 0.125f);
309//                      
310//                      @Override
311//                      public int getNumMembersToMaintain() {
312//                              return super.getNumMembersToMaintain();
313//                      }
314//
315//                      @Override
316//                      public void advance(float amount) {
317//                              super.advance(amount);
318//
319//                              interval.advance(amount);
320//                              if (interval.intervalElapsed() && false) {
321//                                      
322//                                      CombatEngineAPI engine = Global.getCombatEngine();
323//                                      
324//                                      //Color c = RiftLanceEffect.getColorForDarkening(VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR);
325//                                      Color c = RiftLanceEffect.getColorForDarkening(params.color);
326//                                      c = Misc.setAlpha(c, 50);
327//                                      float baseDuration = 2f;
328//                                      Vector2f vel = new Vector2f(ship.getVelocity());
329//                                      
330//                                      float baseSize = params.maxOffset * 2f;
331//                                      
332//                                      //float size = ship.getCollisionRadius() * 0.35f;
333//                                      float size = baseSize * 0.33f;
334//                                      
335//                                      float extraDur = 0f;
336//                                      
337//                                      //for (int i = 0; i < 3; i++) {
338//                                      for (int i = 0; i < 11; i++) {
339//                                              Vector2f point = new Vector2f(ship.getLocation());
340//                                              point = Misc.getPointWithinRadiusUniform(point, baseSize * 0.75f, Misc.random);
341//                                              float dur = baseDuration + baseDuration * (float) Math.random();
342//                                              dur += extraDur;
343//                                              float nSize = size;
344//                                              Vector2f pt = Misc.getPointWithinRadius(point, nSize * 0.5f);
345//                                              Vector2f v = Misc.getUnitVectorAtDegreeAngle((float) Math.random() * 360f);
346//                                              v.scale(nSize + nSize * (float) Math.random() * 0.5f);
347//                                              v.scale(0.2f);
348//                                              Vector2f.add(vel, v, v);
349//                                              
350//                                              float maxSpeed = nSize * 1.5f * 0.2f; 
351//                                              float minSpeed = nSize * 1f * 0.2f; 
352//                                              float overMin = v.length() - minSpeed;
353//                                              if (overMin > 0) {
354//                                                      float durMult = 1f - overMin / (maxSpeed - minSpeed);
355//                                                      if (durMult < 0.1f) durMult = 0.1f;
356//                                                      dur *= 0.5f + 0.5f * durMult;
357//                                              }
358//                                              engine.addNegativeNebulaParticle(pt, v, nSize * 1f, 2f,
359//                                                                                                              0.5f / dur, 0f, dur, c);
360//                                      }
361//                              }
362//                              
363//                      }
364//                      
365//              };
366//      }
367}
368
369
370
371
372
373
374
375