001package com.fs.starfarer.api.impl.combat;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.Comparator;
006import java.util.List;
007
008import java.awt.Color;
009
010import org.lwjgl.util.vector.Vector2f;
011
012import com.fs.starfarer.api.Global;
013import com.fs.starfarer.api.combat.BaseCombatLayeredRenderingPlugin;
014import com.fs.starfarer.api.combat.CollisionClass;
015import com.fs.starfarer.api.combat.CombatEngineAPI;
016import com.fs.starfarer.api.combat.DamageType;
017import com.fs.starfarer.api.combat.EmpArcEntityAPI;
018import com.fs.starfarer.api.combat.FighterLaunchBayAPI;
019import com.fs.starfarer.api.combat.GuidedMissileAI;
020import com.fs.starfarer.api.combat.MissileAPI;
021import com.fs.starfarer.api.combat.MutableShipStatsAPI;
022import com.fs.starfarer.api.combat.ShipAPI;
023import com.fs.starfarer.api.combat.ShipAPI.HullSize;
024import com.fs.starfarer.api.combat.ShipSystemAPI;
025import com.fs.starfarer.api.combat.ShipSystemAPI.SystemState;
026import com.fs.starfarer.api.combat.ShipwideAIFlags.AIFlags;
027import com.fs.starfarer.api.combat.WeaponAPI;
028import com.fs.starfarer.api.util.Misc;
029
030public class DroneStrikeStats extends BaseShipSystemScript implements DroneStrikeStatsAIInfoProvider {
031        
032        public static class DroneMissileScript extends BaseCombatLayeredRenderingPlugin {
033                protected ShipAPI drone;
034                protected MissileAPI missile;
035                protected boolean done;
036                
037                public DroneMissileScript(ShipAPI drone, MissileAPI missile) {
038                        super();
039                        this.drone = drone;
040                        this.missile = missile;
041                        missile.setNoFlameoutOnFizzling(true);
042                        //missile.setFlightTime(missile.getMaxFlightTime() - 1f);
043                }
044
045                @Override
046                public void advance(float amount) {
047                        super.advance(amount);
048                        
049                        if (done) return;
050                        
051                        CombatEngineAPI engine = Global.getCombatEngine();
052                        
053                        missile.setEccmChanceOverride(1f);
054                        missile.setOwner(drone.getOriginalOwner());
055                        
056                        drone.getLocation().set(missile.getLocation());
057                        drone.getVelocity().set(missile.getVelocity());
058                        drone.setCollisionClass(CollisionClass.FIGHTER);
059                        drone.setFacing(missile.getFacing());
060                        drone.getEngineController().fadeToOtherColor(this, new Color(0,0,0,0), new Color(0,0,0,0), 1f, 1f);
061
062                        
063                        float dist = Misc.getDistance(missile.getLocation(), missile.getStart());
064                        float jitterFraction = dist / missile.getMaxRange();
065                        jitterFraction = Math.max(jitterFraction, missile.getFlightTime() / missile.getMaxFlightTime());
066                        
067                        missile.setSpriteAlphaOverride(0f);
068                        float jitterMax = 1f + 10f * jitterFraction;
069                        drone.setJitter(this, new Color(255,100,50, (int)(25 + 50 * jitterFraction)), 1f, 10, 1f, jitterMax);
070                        
071                        
072//                      if (true && !done && missile.getFlightTime() > 1f) {
073//                              Vector2f damageFrom = new Vector2f(drone.getLocation());
074//                              damageFrom = Misc.getPointWithinRadius(damageFrom, 20);
075//                              engine.applyDamage(drone, damageFrom, 1000000f, DamageType.ENERGY, 0, true, false, drone, false);
076//                      }
077                        
078                        boolean droneDestroyed = drone.isHulk() || drone.getHitpoints() <= 0;
079                        if (missile.isFizzling() || (missile.getHitpoints() <= 0 && !missile.didDamage()) || droneDestroyed) {
080                                drone.getVelocity().set(0, 0);
081                                missile.getVelocity().set(0, 0);
082                                
083                                if (!droneDestroyed) {
084                                        Vector2f damageFrom = new Vector2f(drone.getLocation());
085                                        damageFrom = Misc.getPointWithinRadius(damageFrom, 20);
086                                        engine.applyDamage(drone, damageFrom, 1000000f, DamageType.ENERGY, 0, true, false, drone, false);
087                                }
088                                missile.interruptContrail();
089                                engine.removeEntity(drone);
090                                engine.removeEntity(missile);
091                                
092                                missile.explode();
093                                
094                                done = true;
095                                return;
096                        }
097                        if (missile.didDamage()) {
098                                drone.getVelocity().set(0, 0);
099                                missile.getVelocity().set(0, 0);
100                                
101                                Vector2f damageFrom = new Vector2f(drone.getLocation());
102                                damageFrom = Misc.getPointWithinRadius(damageFrom, 20);
103                                engine.applyDamage(drone, damageFrom, 1000000f, DamageType.ENERGY, 0, true, false, drone, false);
104                                missile.interruptContrail();
105                                engine.removeEntity(drone);
106                                engine.removeEntity(missile);
107                                done = true;
108                                return;
109                        }
110                        
111                }
112
113                @Override
114                public boolean isExpired() {
115                        return done;
116                }
117                
118                
119        }
120        
121        
122        
123        protected String getWeaponId() {
124                return "terminator_missile";
125        }
126        protected int getNumToFire() {
127                return 1;
128        }
129        
130        protected WeaponAPI weapon;
131        protected boolean fired = false;
132        
133        public void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel) {
134                ShipAPI ship = null;
135                //boolean player = false;
136                if (stats.getEntity() instanceof ShipAPI) {
137                        ship = (ShipAPI) stats.getEntity();
138                        //player = ship == Global.getCombatEngine().getPlayerShip();
139                } else {
140                        return;
141                }
142                
143                if (weapon == null) {
144                        weapon = Global.getCombatEngine().createFakeWeapon(ship, getWeaponId());
145                }
146                
147                for (ShipAPI drone : getDrones(ship)) {
148                        drone.setExplosionScale(0.67f);
149                        drone.setExplosionVelocityOverride(new Vector2f());
150                        drone.setExplosionFlashColorOverride(new Color(255, 100, 50, 255));
151                }
152                
153                if (effectLevel > 0 && !fired) {
154                        if (!getDrones(ship).isEmpty()) {
155                                ShipAPI target = findTarget(ship);
156                                convertDrones(ship, target);
157                        }
158                } else if (state == State.IDLE){
159                        fired = false;
160                }
161        }
162        
163        public void convertDrones(ShipAPI ship, final ShipAPI target) {
164                CombatEngineAPI engine = Global.getCombatEngine();
165                fired = true;
166                forceNextTarget = null;
167                int num = 0;
168                
169                List<ShipAPI> drones = getDrones(ship);
170                if (target != null) {
171                        Collections.sort(drones, new Comparator<ShipAPI>() {
172                                public int compare(ShipAPI o1, ShipAPI o2) {
173                                        float d1 = Misc.getDistance(o1.getLocation(), target.getLocation());
174                                        float d2 = Misc.getDistance(o2.getLocation(), target.getLocation());
175                                        return (int)Math.signum(d1 - d2);
176                                }
177                        });
178                } else {
179                        Collections.shuffle(drones);
180                }
181                
182                for (ShipAPI drone : drones) {
183                        if (num < getNumToFire()) {
184                                MissileAPI missile = (MissileAPI) engine.spawnProjectile(
185                                                ship, weapon, getWeaponId(), 
186                                                new Vector2f(drone.getLocation()), drone.getFacing(), new Vector2f(drone.getVelocity()));
187                                if (target != null && missile.getAI() instanceof GuidedMissileAI) {
188                                        GuidedMissileAI ai = (GuidedMissileAI) missile.getAI();
189                                        ai.setTarget(target);
190                                }
191                                //missile.setHitpoints(missile.getHitpoints() * drone.getHullLevel());
192                                missile.setEmpResistance(10000);
193                                
194                                float base = missile.getMaxRange();
195                                float max = getMaxRange(ship);
196                                missile.setMaxRange(max);
197                                missile.setMaxFlightTime(missile.getMaxFlightTime() * max/base);
198                                
199                                drone.getWing().removeMember(drone);
200                                drone.setWing(null);
201                                drone.setExplosionFlashColorOverride(new Color(255, 100, 50, 255));
202                                engine.addLayeredRenderingPlugin(new DroneMissileScript(drone, missile));
203                                
204//                              engine.removeEntity(drone);
205//                              drone.getVelocity().set(0, 0);
206//                              drone.setHulk(true);
207//                              drone.setHitpoints(-1f);
208                                
209                                //float thickness = 16f;
210//                              EmpArcParams params = new EmpArcParams();
211//                              params.segmentLengthMult = 4f;
212//                              //params.glowSizeMult = 0.5f;
213//                              params.brightSpotFadeFraction = 0.33f;
214//                              params.brightSpotFullFraction = 1f;
215////                            params.movementDurMax = 0.2f;
216//                              params.flickerRateMult = 0.7f;
217                                
218                                
219                                float thickness = 26f;
220                                float coreWidthMult = 0.67f;
221                                EmpArcEntityAPI arc = engine.spawnEmpArcVisual(ship.getLocation(), ship,
222                                                missile.getLocation(), missile, thickness, new Color(255,100,100,255), Color.white, null);
223                                arc.setCoreWidthOverride(thickness * coreWidthMult);
224                                arc.setSingleFlickerMode();
225                        } else {
226                                if (drone.getShipAI() != null) {
227                                        drone.getShipAI().cancelCurrentManeuver();
228                                }
229                        }
230                        num++;
231                }
232        }
233        
234        
235        public void unapply(MutableShipStatsAPI stats, String id) {
236                // never called
237        }
238        
239        protected ShipAPI forceNextTarget = null;
240        protected ShipAPI findTarget(ShipAPI ship) {
241                if (getDrones(ship).isEmpty()) {
242                        return null;
243                }
244                
245                if (forceNextTarget != null && forceNextTarget.isAlive()) {
246                        return forceNextTarget;
247                }
248                
249                float range = getMaxRange(ship);
250                boolean player = ship == Global.getCombatEngine().getPlayerShip();
251                ShipAPI target = ship.getShipTarget();
252                
253                // If not the player:
254                // The AI sets forceNextTarget, so if we're here, that target got destroyed in the last frame
255                // or it's using a different AI
256                // so, find *something* as a failsafe
257                
258                if (!player) {
259                        Object test = ship.getAIFlags().getCustom(AIFlags.MANEUVER_TARGET);
260                        if (test instanceof ShipAPI) {
261                                target = (ShipAPI) test;
262                                float dist = Misc.getDistance(ship.getLocation(), target.getLocation());
263                                float radSum = ship.getCollisionRadius() + target.getCollisionRadius();
264                                if (dist > range + radSum) target = null;
265                        }
266                        if (target == null) {
267                                target = Misc.findClosestShipEnemyOf(ship, ship.getMouseTarget(), HullSize.FRIGATE, range, true);
268                        }
269                        return target;
270                }
271                
272                // Player ship
273                
274                if (target != null) return target; // was set with R, so, respect that
275                
276                // otherwise, find the nearest thing to the mouse cursor, regardless of if it's in range
277                
278                target = Misc.findClosestShipEnemyOf(ship, ship.getMouseTarget(), HullSize.FIGHTER, Float.MAX_VALUE, true);
279                if (target != null && target.isFighter()) {
280                        ShipAPI nearbyShip = Misc.findClosestShipEnemyOf(ship, target.getLocation(), HullSize.FRIGATE, 100, false);
281                        if (nearbyShip != null) target = nearbyShip;
282                }
283                if (target == null) {
284                        target = Misc.findClosestShipEnemyOf(ship, ship.getLocation(), HullSize.FIGHTER, range, true);
285                }
286                
287                return target;
288        }
289
290        
291        public StatusData getStatusData(int index, State state, float effectLevel) {
292                return null;
293        }
294
295        public List<ShipAPI> getDrones(ShipAPI ship) {
296                List<ShipAPI> result = new ArrayList<ShipAPI>();
297                for (FighterLaunchBayAPI bay : ship.getLaunchBaysCopy()) {
298                        if (bay.getWing() == null) continue;
299                        for (ShipAPI drone : bay.getWing().getWingMembers()) {
300                                result.add(drone);
301                        }
302                }
303                return result;
304        }
305
306        @Override
307        public String getInfoText(ShipSystemAPI system, ShipAPI ship) {
308                if (system.isOutOfAmmo()) return null;
309                if (system.getState() != SystemState.IDLE) return null;
310                
311                if (getDrones(ship).isEmpty()) {
312                        return "NO DRONES";
313                }
314                
315                float range = getMaxRange(ship);
316                
317                ShipAPI target = findTarget(ship);
318                if (target == null) {
319                        if (ship.getMouseTarget() != null) {
320                                float dist = Misc.getDistance(ship.getLocation(), ship.getMouseTarget());
321                                float radSum = ship.getCollisionRadius();
322                                if (dist + radSum > range) {
323                                        return "OUT OF RANGE";
324                                }
325                        }
326                        return "NO TARGET";
327                }
328                
329                float dist = Misc.getDistance(ship.getLocation(), target.getLocation());
330                float radSum = ship.getCollisionRadius() + target.getCollisionRadius();
331                if (dist > range + radSum) {
332                        return "OUT OF RANGE";
333                }
334                
335                return "READY";
336        }
337
338        
339        @Override
340        public boolean isUsable(ShipSystemAPI system, ShipAPI ship) {
341                if (ship != null && ship.getSystem() != null && ship.getSystem().getState() != SystemState.IDLE) {
342                        return true; // preventing out-of-ammo click when launching last drone
343                }
344                return !getDrones(ship).isEmpty();
345//              if (true) return true;
346//              ShipAPI target = findTarget(ship);
347//              return target != null && target != ship;
348        }
349        
350        public float getMaxRange(ShipAPI ship) {
351                if (weapon == null) {
352                        weapon = Global.getCombatEngine().createFakeWeapon(ship, getWeaponId());
353                }
354                //return weapon.getRange();
355                return ship.getMutableStats().getSystemRangeBonus().computeEffective(weapon.getRange());
356        }
357        public boolean dronesUsefulAsPD() {
358                return true;
359        }
360        public boolean droneStrikeUsefulVsFighters() {
361                return false;
362        }
363        public int getMaxDrones() {
364                return 2;
365        }
366        public float getMissileSpeed() {
367                return weapon.getProjectileSpeed();
368        }
369        public void setForceNextTarget(ShipAPI forceNextTarget) {
370                this.forceNextTarget = forceNextTarget;
371        }
372        public ShipAPI getForceNextTarget() {
373                return forceNextTarget;
374        }
375}
376
377
378
379
380
381
382
383