001package com.fs.starfarer.api.impl.combat.dem;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import java.awt.Color;
007
008import org.json.JSONArray;
009import org.json.JSONException;
010import org.json.JSONObject;
011import org.lwjgl.util.vector.Vector2f;
012
013import com.fs.starfarer.api.Global;
014import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin;
015import com.fs.starfarer.api.combat.CollisionClass;
016import com.fs.starfarer.api.combat.CombatEngineLayers;
017import com.fs.starfarer.api.combat.CombatEntityAPI;
018import com.fs.starfarer.api.combat.DamageType;
019import com.fs.starfarer.api.combat.GuidedMissileAI;
020import com.fs.starfarer.api.combat.MissileAIPlugin;
021import com.fs.starfarer.api.combat.MissileAPI;
022import com.fs.starfarer.api.combat.ShipAPI;
023import com.fs.starfarer.api.combat.ShipCommand;
024import com.fs.starfarer.api.combat.ShipHullSpecAPI;
025import com.fs.starfarer.api.combat.ShipVariantAPI;
026import com.fs.starfarer.api.combat.ShipwideAIFlags.AIFlags;
027import com.fs.starfarer.api.combat.WeaponAPI;
028import com.fs.starfarer.api.input.InputEventAPI;
029import com.fs.starfarer.api.loading.WeaponGroupSpec;
030import com.fs.starfarer.api.loading.WeaponGroupType;
031import com.fs.starfarer.api.util.Misc;
032
033/**
034 * 
035 */
036public class DEMScript extends BaseEveryFrameCombatPlugin implements MissileAIPlugin {
037        
038        public static enum State {
039                WAIT,
040                TURN_TO_TARGET,
041                SIGNAL,
042                FIRE,
043                DONE,
044        }
045        
046        protected State state = State.WAIT;
047        protected MissileAPI missile;
048        protected ShipAPI ship;
049        protected WeaponAPI weapon;
050        protected CombatEntityAPI fireTarget;
051        protected ShipAPI demDrone;
052        
053        //protected Vector2f targetingLaserFireOffset = new Vector2f();
054        protected List<Vector2f> targetingLaserFireOffset = new ArrayList<Vector2f>();
055        protected List<Vector2f> targetingLaserSweepAngles = new ArrayList<Vector2f>();
056        protected List<Vector2f> payloadSweepAngles = new ArrayList<Vector2f>();
057        protected List<Float> payloadSweepPhaseShift = new ArrayList<Float>();
058        protected float minDelayBeforeTriggering = 0f;
059        protected boolean useTriggerAngle = false;
060        protected float triggerAngle = 0f;
061        protected float allowedDriftFraction = 0f;
062        protected float triggerDistance = 0f;
063        protected float turnRateBoost = 0f;
064        protected float turnRateMultOnSignal = 1f;
065        protected float targetingLaserArc = 0f;
066        protected float targetingTime = 1f;
067        protected float firingTime = 1f;
068        protected String targetingLaserId;
069        protected String payloadWeaponId;
070        protected float preferredMinFireDistance;
071        protected float preferredMaxFireDistance;
072        protected float targetingLaserRange;
073        protected float payloadSweepRateMult;
074        protected boolean bombPumped;
075        protected boolean fadeOutEngineWhenFiring;
076        protected boolean destroyMissleWhenDoneFiring;
077        protected boolean randomStrafe;
078        protected boolean randomPayloadSweepPhaseShift;
079        protected boolean payloadCenterSweepOnOriginalOffset;
080        protected boolean snapFacingToTargetIfCloseEnough = true;
081        protected Color destroyedExplosionColor;
082        
083        protected float elapsedWaiting = 0f;
084        protected float elapsedTargeting = 0f;
085        protected float elapsedFiring = 0f;
086        //protected DamagingProjectileAPI explosion;
087        protected int explosionDelayFrames = 0;
088        protected float strafeDur = 0f;
089        protected float strafeDir = 0f;
090        protected boolean exploded = false;
091        
092        protected ShapedExplosionParams p;
093        
094        public DEMScript(MissileAPI missile, ShipAPI ship, WeaponAPI weapon) {
095                this.missile = missile;
096                this.ship = ship;
097                this.weapon = weapon;
098                
099                JSONObject json = missile.getSpec().getBehaviorJSON();
100                //minDelayBeforeTriggering = (float) json.optDouble("minDelayBeforeTriggering", 1f); 
101                minDelayBeforeTriggering = getValue(json, "minDelayBeforeTriggering", 1f); 
102                allowedDriftFraction = (float) json.optDouble("allowedDriftFraction", 0.33f); 
103                //triggerDistance = (float) json.optDouble("triggerDistance", 500f);
104                //preferredMinFireDistance = (float) json.optDouble("preferredMinFireDistance", 0f);
105                triggerDistance = getValue(json, "triggerDistance", 500f);
106                
107                try {
108                        if (json.optBoolean("withShapedExplosion")) {
109                                p = new ShapedExplosionParams();
110                                p.load(json);;
111                        }
112                } catch (Exception e) {
113                        throw new RuntimeException(e);
114                }
115                
116                snapFacingToTargetIfCloseEnough = json.optBoolean("snapFacingToTargetIfCloseEnough", false);
117                
118                if (json.has("triggerAngle")) {
119                        useTriggerAngle = true;
120                        triggerAngle = getValue(json, "triggerAngle", 0f);
121                }
122                
123                preferredMaxFireDistance = getValue(json, "preferredMaxFireDistance", triggerDistance);
124                preferredMinFireDistance = getValue(json, "preferredMinFireDistance", 0f);
125                if (json.has("targetingLaserRange")) {
126                        targetingLaserRange = (float) json.optDouble("targetingLaserRange", 600f);
127                } else {
128                        targetingLaserRange = Math.max(triggerDistance, preferredMinFireDistance) + 200f;
129                }
130                turnRateBoost = (float) json.optDouble("turnRateBoost", 100f);
131                turnRateMultOnSignal = (float) json.optDouble("turnRateMultOnSignal", 1f);
132                //targetingTime = (float) json.optDouble("targetingTime", 1f);
133                targetingTime = getValue(json, "targetingTime", 1f);
134                firingTime = (float) json.optDouble("firingTime", 1.25f);
135                targetingLaserId = json.optString("targetingLaserId", null);
136                payloadWeaponId = json.optString("payloadWeaponId", null);
137                targetingLaserArc = (float) json.optDouble("targetingLaserArc", 10f);
138                payloadSweepRateMult = (float) json.optDouble("payloadSweepRateMult", 1f);
139                bombPumped = json.optBoolean("bombPumped", false);
140                fadeOutEngineWhenFiring = json.optBoolean("fadeOutEngineWhenFiring", false);
141                destroyMissleWhenDoneFiring = json.optBoolean("destroyMissleWhenDoneFiring", false);
142                randomStrafe = json.optBoolean("randomStrafe", false);
143                randomPayloadSweepPhaseShift = json.optBoolean("randomPayloadSweepPhaseShift", false);
144                payloadCenterSweepOnOriginalOffset = json.optBoolean("payloadCenterSweepOnOriginalOffset", false);
145                if (json.has("destroyedExplosionColor")) {
146                        try {
147                                destroyedExplosionColor = Misc.optColor(json, "destroyedExplosionColor", null);
148                        } catch (Exception e) {
149                                throw new RuntimeException(e);
150                        }
151                }
152                
153                JSONArray arr = json.optJSONArray("targetingLaserFireOffset");
154                if (arr != null) {
155                        for (int i = 0; i < arr.length(); i += 2) {
156                                Vector2f v = new Vector2f((float) arr.optDouble(i), (float) arr.optDouble(i + 1));
157                                targetingLaserFireOffset.add(v);
158                        }
159                }
160                arr = json.optJSONArray("targetingLaserSweepAngles");
161                if (arr != null) {
162                        for (int i = 0; i < arr.length(); i += 2) {
163                                Vector2f v = new Vector2f((float) arr.optDouble(i), (float) arr.optDouble(i + 1));
164                                targetingLaserSweepAngles.add(v);
165                        }
166                }
167                arr = json.optJSONArray("payloadSweepAngles");
168                if (arr != null) {
169                        for (int i = 0; i < arr.length(); i += 2) {
170                                Vector2f v = new Vector2f((float) arr.optDouble(i), (float) arr.optDouble(i + 1));
171                                
172                                if (payloadCenterSweepOnOriginalOffset) {
173                                        float orig = Global.getSettings().getWeaponSpec(payloadWeaponId).getTurretAngleOffsets().get(i/2);
174                                        v.x += orig;
175                                        v.y += orig;
176                                }
177                                
178                                payloadSweepAngles.add(v);
179                        }
180                }
181                if (randomPayloadSweepPhaseShift) {
182                        for (int i = 0; i < payloadSweepAngles.size(); i++) {
183                                payloadSweepPhaseShift.add((float) Math.random());
184                        }
185                }
186                float maxSpeed = Math.max(50f, missile.getMaxSpeed());
187                float etaMod = -1f * triggerDistance / maxSpeed;
188                missile.setEtaModifier(etaMod);
189        }
190        
191        public static float getValue(JSONObject json, String key, float defaultValue) {
192                JSONArray arr = json.optJSONArray(key);
193                if (arr != null) {
194                        Vector2f v = new Vector2f((float) arr.optDouble(0), (float) arr.optDouble(1));
195                        return v.x + (v.y - v.x) * (float) Math.random();
196                }
197                return (float) json.optDouble(key, defaultValue);
198        }
199
200
201        @Override
202        public void advance(float amount, List<InputEventAPI> events) {
203                if (Global.getCombatEngine().isPaused()) return;
204
205                // so that the AI doesn't treat fizzled missiles as a threat due to the drone still being there
206                if (missile.isFizzling()) {
207                        if (demDrone != null) {
208                                Global.getCombatEngine().removeEntity(demDrone);
209                        }
210                }
211                
212                boolean doCleanup = state == State.DONE || 
213                                (!bombPumped || state.ordinal() < State.FIRE.ordinal()) && 
214                                (missile.isExpired() || missile.didDamage() || 
215                                !Global.getCombatEngine().isEntityInPlay(missile));
216                if (doCleanup) {
217                        if (demDrone != null) {
218                                Global.getCombatEngine().removeEntity(demDrone);
219                        }
220                        Global.getCombatEngine().removePlugin(this);
221                        return;
222                }
223                
224                if (state == State.WAIT && missile.isArmed() && !missile.isFizzling() && !missile.isFading()) {
225                        CombatEntityAPI target = null;
226                        if (missile.getAI() instanceof GuidedMissileAI) {
227                                GuidedMissileAI ai = (GuidedMissileAI) missile.getAI();
228                                target = ai.getTarget();
229                        }
230                        elapsedWaiting += amount;
231                        
232                        if (useTriggerAngle && target != null) {
233                                Vector2f from = target.getLocation();
234                                if (target instanceof ShipAPI) {
235                                        from = ((ShipAPI) target).getShieldCenterEvenIfNoShield();
236                                }
237                                float toMissile = Misc.getAngleInDegrees(from, missile.getLocation());
238                                //float diff = Misc.getAngleDiff(target.getFacing(), toMissile);
239                                //float toShip = Misc.getAngleInDegrees(from, ship.getLocation());
240                                float toShip = Misc.getAngleInDegrees(from, missile.getSpawnLocation());
241                                float diff = Misc.getAngleDiff(toShip, toMissile);
242                                if (diff >= triggerAngle) {
243                                        elapsedWaiting = minDelayBeforeTriggering;
244                                }
245                        }
246                        
247                        if (target != null && elapsedWaiting >= minDelayBeforeTriggering) {
248                                float dist = Misc.getDistance(target.getLocation(), missile.getLocation());
249                                dist -= Global.getSettings().getTargetingRadius(missile.getLocation(), target, false);
250                                
251                                if (dist < triggerDistance) {
252                                        missile.setMaxFlightTime(10000f);
253                                        state = State.TURN_TO_TARGET;
254                                        fireTarget = target;
255                                        
256                                        // turn off the normal missile AI; this script is taking over
257                                        missile.setMissileAI(this);
258                                        
259                                        missile.getEngineStats().getMaxTurnRate().modifyFlat("dem", turnRateBoost);
260                                        missile.getEngineStats().getTurnAcceleration().modifyFlat("dem", turnRateBoost * 2f);
261                                        
262                                        ShipHullSpecAPI spec = Global.getSettings().getHullSpec("dem_drone");
263                                        ShipVariantAPI v = Global.getSettings().createEmptyVariant("dem_drone", spec);
264                                        v.addWeapon("WS 000", targetingLaserId);
265                                        WeaponGroupSpec g = new WeaponGroupSpec(WeaponGroupType.LINKED);
266                                        g.addSlot("WS 000");
267                                        v.addWeaponGroup(g);
268                                        v.addWeapon("WS 001", payloadWeaponId);
269                                        g = new WeaponGroupSpec(WeaponGroupType.LINKED);
270                                        g.addSlot("WS 001");
271                                        v.addWeaponGroup(g);
272                                        
273                                        demDrone = Global.getCombatEngine().createFXDrone(v);
274                                        demDrone.setLayer(CombatEngineLayers.ABOVE_SHIPS_AND_MISSILES_LAYER);
275                                        demDrone.setOwner(ship.getOriginalOwner());
276                                        demDrone.getMutableStats().getBeamWeaponRangeBonus().modifyFlat("dem", targetingLaserRange);
277                                        demDrone.getMutableStats().getHullDamageTakenMult().modifyMult("dem", 0f); // so it's non-targetable
278                                        demDrone.setDrone(true);
279                                        demDrone.getAIFlags().setFlag(AIFlags.DRONE_MOTHERSHIP, 100000f, ship);
280                                        demDrone.getMutableStats().getEnergyWeaponDamageMult().applyMods(ship.getMutableStats().getMissileWeaponDamageMult());
281                                        demDrone.getMutableStats().getMissileWeaponDamageMult().applyMods(ship.getMutableStats().getMissileWeaponDamageMult());
282                                        demDrone.getMutableStats().getBallisticWeaponDamageMult().applyMods(ship.getMutableStats().getMissileWeaponDamageMult());
283                                        
284                                        demDrone.getMutableStats().getDamageToCapital().applyMods(ship.getMutableStats().getDamageToCapital());
285                                        demDrone.getMutableStats().getDamageToCruisers().applyMods(ship.getMutableStats().getDamageToCruisers());
286                                        demDrone.getMutableStats().getDamageToDestroyers().applyMods(ship.getMutableStats().getDamageToDestroyers());
287                                        demDrone.getMutableStats().getDamageToFrigates().applyMods(ship.getMutableStats().getDamageToFrigates());
288                                        demDrone.getMutableStats().getDamageToFighters().applyMods(ship.getMutableStats().getDamageToFighters());
289                                        demDrone.getMutableStats().getDamageToMissiles().applyMods(ship.getMutableStats().getDamageToMissiles());
290                                        demDrone.getMutableStats().getDamageToTargetEnginesMult().applyMods(ship.getMutableStats().getDamageToTargetEnginesMult());
291                                        demDrone.getMutableStats().getDamageToTargetHullMult().applyMods(ship.getMutableStats().getDamageToTargetHullMult());
292                                        demDrone.getMutableStats().getDamageToTargetShieldsMult().applyMods(ship.getMutableStats().getDamageToTargetShieldsMult());
293                                        demDrone.getMutableStats().getDamageToTargetWeaponsMult().applyMods(ship.getMutableStats().getDamageToTargetWeaponsMult());
294                                        
295                                        demDrone.setCollisionClass(CollisionClass.NONE);
296                                        demDrone.giveCommand(ShipCommand.SELECT_GROUP, null, 0);
297                                        Global.getCombatEngine().addEntity(demDrone);
298                                        
299                                        if (targetingLaserFireOffset.size() > 0) {
300                                                WeaponAPI tLaser = demDrone.getWeaponGroupsCopy().get(0).getWeaponsCopy().get(0);
301                                                tLaser.ensureClonedSpec();
302                                                tLaser.getSpec().getTurretFireOffsets().clear();
303                                                tLaser.getSpec().getTurretFireOffsets().addAll(targetingLaserFireOffset);
304                                        }
305                                }
306                        }
307                } else if (state == State.TURN_TO_TARGET) {
308                        float angle = Misc.getAngleInDegrees(missile.getLocation(), fireTarget.getLocation());
309                        
310                        if (Misc.isInArc(missile.getFacing(), targetingLaserArc, angle)) {
311                                missile.getEngineStats().getMaxTurnRate().modifyMult("dem_mult", turnRateMultOnSignal);
312                                //missile.getEngineStats().getTurnAcceleration().modifyMult("dem_mult", turnRateMultOnSignal);
313                                
314                                state = State.SIGNAL;
315                        }
316                } else if (state == State.SIGNAL) {
317                        
318                        if (targetingLaserSweepAngles.size() > 0) {
319                                float progress = elapsedTargeting / targetingTime;
320                                WeaponAPI tLaser = demDrone.getWeaponGroupsCopy().get(0).getWeaponsCopy().get(0);
321                                tLaser.ensureClonedSpec();
322                                tLaser.getSpec().getTurretAngleOffsets().clear();
323                                for (Vector2f curr : targetingLaserSweepAngles) {
324                                        float angle = 0f;
325                                        if (progress < 0.5f) {
326                                                angle = curr.x + (curr.y - curr.x) * progress * 2f;
327                                        } else {
328                                                angle = curr.x + (curr.y - curr.x) * (1f - progress) * 2f;
329                                        }
330                                        tLaser.getSpec().getTurretAngleOffsets().add(angle);
331                                }
332                        }
333                        
334                        if (targetingLaserRange > 0 && targetingTime > 0) {
335                                demDrone.giveCommand(ShipCommand.FIRE, fireTarget.getLocation(), 0);
336                        }
337                        
338                        elapsedTargeting += amount;
339                        if (elapsedTargeting >= targetingTime) {
340                                state = State.FIRE;
341                                demDrone.giveCommand(ShipCommand.SELECT_GROUP, null, 1);
342                                
343                                if (!bombPumped) {
344                                        //missile.flameOut();
345                                        missile.setFlightTime(0f);
346                                        missile.setMaxFlightTime(firingTime);
347                                        missile.setNoFlameoutOnFizzling(true);
348                                        missile.setNoGlowTime(0f);
349                                        missile.setFizzleTime(0.5f);
350                                        missile.setFadeTime(0.5f);
351                                        missile.setEtaModifier(0f);
352                                }
353                        }
354                } else if (state == State.FIRE) {
355                        //Global.getCombatEngine().setPaused(true);
356                        
357                        if (bombPumped && !exploded && explosionDelayFrames >= 1) {
358                                missile.explode();
359                                Global.getCombatEngine().removeEntity(missile);
360                                
361//                              ShapedExplosionParams p = new ShapedExplosionParams();
362//                              p.shapedExplosionNumParticles = 50;
363//                              p.shapedExplosionMinParticleDur = 0.7f;
364//                              p.shapedExplosionMaxParticleDur = 1.1f;
365//                              p.shapedExplosionMinParticleSize = 50f;
366//                              p.shapedExplosionMaxParticleSize = 70f;
367//                              p.shapedExplosionColor = new Color(255,40,40,155);
368//                              p.shapedExplosionArc = 45f;
369//                              p.shapedExplosionMinParticleVel = 50f;
370//                              p.shapedExplosionMaxParticleVel = 250f;
371//                              
372////                            p.shapedExplosionMinParticleDur = 1f;
373////                            p.shapedExplosionMaxParticleDur = 3f;
374//                              
375//                              float speedMult = 1f;
376//                              p.shapedExplosionMinParticleVel *= speedMult;
377//                              p.shapedExplosionMaxParticleVel *= speedMult;
378//                              p.shapedExplosionMinParticleDur /= speedMult;
379//                              p.shapedExplosionMaxParticleDur /= speedMult;
380                                if (p != null) {
381                                        spawnShapedExplosion(missile.getLocation(), missile.getFacing(), p);
382                                }
383                                exploded = true;
384                        }
385                        explosionDelayFrames++;
386                        
387                        if (fadeOutEngineWhenFiring) {
388                                float progress = elapsedFiring / firingTime;
389                                progress *= 2f;
390                                if (progress > 1f) progress = 1f;
391                                missile.getEngineController().fadeToOtherColor(this, Misc.zeroColor, Misc.zeroColor, progress, 1f);
392                        }
393                        
394                        if (payloadSweepAngles.size() > 0) {
395                                WeaponAPI payload = demDrone.getWeaponGroupsCopy().get(1).getWeaponsCopy().get(0);
396                                payload.ensureClonedSpec();
397                                payload.getSpec().getTurretAngleOffsets().clear();
398                                int index = 0;
399                                for (Vector2f curr : payloadSweepAngles) {
400                                        float angle = 0f;
401                                        float progress = elapsedFiring / firingTime;
402                                        if (progress < 0.5f) {
403                                                angle = curr.x + (curr.y - curr.x) * progress * 2f;
404                                        } else {
405                                                angle = curr.x + (curr.y - curr.x) * (1f - progress) * 2f;
406                                        }
407                                        if (randomPayloadSweepPhaseShift) {
408                                                progress += payloadSweepPhaseShift.get(index);
409                                                progress = (float) Math.sin(progress * Math.PI * payloadSweepRateMult);
410                                                progress = Math.abs(progress);
411                                                
412                                                angle = curr.x + (curr.y - curr.x) * progress;
413                                        }
414                                        
415                                        
416                                        payload.getSpec().getTurretAngleOffsets().add(angle);
417                                        index++;
418                                }
419                        }
420                        
421                        
422                        // use payload's normal range as defined in weapon_data.csv
423                        demDrone.getMutableStats().getBeamWeaponRangeBonus().unmodifyFlat("dem");
424                        demDrone.giveCommand(ShipCommand.FIRE, fireTarget.getLocation(), 0);
425                        
426                        elapsedFiring += amount;
427                        if (elapsedFiring >= firingTime) {
428                                missile.setNoGlowTime(10f);
429                                state = State.DONE;
430                                
431                                if (destroyMissleWhenDoneFiring) {
432                                        missile.getVelocity().set(0, 0);
433                                        if (destroyedExplosionColor != null) {
434                                                missile.setDestroyedExplosionColorOverride(destroyedExplosionColor);
435                                        }
436                                        Global.getCombatEngine().applyDamage(missile, missile.getLocation(), 100000f, DamageType.ENERGY, 0f, false, false, demDrone, false);
437                                } else {
438                                        missile.setFizzleTime(1f);
439                                        missile.setArmedWhileFizzling(false);
440                                }
441                        }
442                }
443                
444                doMissileControl(amount);
445                updateDroneState(amount);
446                
447        }
448        
449        protected void updateDroneState(float amount) {
450                if (demDrone != null) {
451                        //System.out.println("FIRE FACING: " + missile.getFacing());
452                        //if (explosion == null) {
453                                demDrone.setOwner(missile.getOwner());
454                                demDrone.getLocation().set(missile.getLocation());
455                                demDrone.setFacing(missile.getFacing());
456                                demDrone.getVelocity().set(missile.getVelocity());
457                                demDrone.setAngularVelocity(missile.getAngularVelocity());
458                                //demDrone.getMouseTarget().set(fireTarget.getLocation());
459                        //}
460                        Vector2f dir = Misc.getUnitVectorAtDegreeAngle(missile.getFacing());
461                        dir.scale(1000f);
462                        Vector2f.add(dir, missile.getLocation(), dir);
463                        demDrone.getMouseTarget().set(dir);
464                        
465                        //demDrone.getMutableStats().getWeaponTurnRateBonus().modifyMult("dem", 0f);
466                        
467                        WeaponAPI tLaser = demDrone.getWeaponGroupsCopy().get(0).getWeaponsCopy().get(0);
468                        WeaponAPI payload = demDrone.getWeaponGroupsCopy().get(1).getWeaponsCopy().get(0);
469                        tLaser.setFacing(missile.getFacing());
470                        payload.setFacing(missile.getFacing());
471                        tLaser.setKeepBeamTargetWhileChargingDown(true);
472                        payload.setKeepBeamTargetWhileChargingDown(true);
473                        tLaser.setScaleBeamGlowBasedOnDamageEffectiveness(false);
474                        if (firingTime <= 2f) {
475                                payload.setScaleBeamGlowBasedOnDamageEffectiveness(false);
476                        }
477                        tLaser.updateBeamFromPoints();
478                        payload.updateBeamFromPoints();
479                }
480        }
481        
482        protected void doMissileControl(float amount) {
483                if (state == State.TURN_TO_TARGET || state == State.SIGNAL ||
484                                (state == State.FIRE && !bombPumped && !fadeOutEngineWhenFiring)) {
485                        
486                        float dist = Misc.getDistance(fireTarget.getLocation(), missile.getLocation());
487                        dist -= Global.getSettings().getTargetingRadius(missile.getLocation(), fireTarget, false);
488                        if (dist < preferredMinFireDistance) {
489                                missile.giveCommand(ShipCommand.ACCELERATE_BACKWARDS);
490                        } else if (dist > preferredMaxFireDistance) {
491                                missile.giveCommand(ShipCommand.ACCELERATE);
492                        } else if (missile.getVelocity().length() > missile.getMaxSpeed() * allowedDriftFraction) {
493                                missile.giveCommand(ShipCommand.DECELERATE);
494                        }
495                        float dir = Misc.getAngleInDegrees(missile.getLocation(), fireTarget.getLocation());
496                        float diff = Misc.getAngleDiff(missile.getFacing(), dir);
497                        float rate = missile.getMaxTurnRate() * amount;
498//                      float turnDir1 = Misc.getClosestTurnDirection(missile.getFacing(), dir);
499//                      boolean turningTowardsDesiredFacing = Math.signum(turnDir1) == Math.signum(missile.getAngularVelocity());
500                        boolean turningTowardsDesiredFacing = true;
501                        //snapFacingToTargetIfCloseEnough = true;
502                        boolean phased = fireTarget instanceof ShipAPI && ((ShipAPI)fireTarget).isPhased();
503                        if (!phased) {
504                                if (diff <= rate * 0.25f && turningTowardsDesiredFacing && snapFacingToTargetIfCloseEnough) {
505                                        missile.setFacing(dir);
506                                } else {
507                                        Misc.turnTowardsPointV2(missile, fireTarget.getLocation(), 0f);
508                                }
509                        }
510                        
511                        if (randomStrafe) {
512                                if (strafeDur <= 0) {
513                                        float r = (float) Math.random();
514                                        
515                                        if (strafeDir == 0) {
516                                                if (r < 0.4f) {
517                                                        strafeDir = 1f;
518                                                } else if (r < 0.8f) {
519                                                        strafeDir = -1f;
520                                                } else {
521                                                        strafeDir = 0f;
522                                                }
523                                        } else {
524                                                if (r < 0.8f) {
525                                                        strafeDir = -strafeDir;
526                                                } else {
527                                                        strafeDir = 0f;
528                                                }
529                                        }
530                                        
531                                        strafeDur = 0.5f + (float) Math.random() * 0.5f;
532                                        //strafeDur *= 0.5f;
533                                }
534                                
535                                Vector2f driftDir = Misc.getUnitVectorAtDegreeAngle(missile.getFacing() + 90f);
536                                if (strafeDir == 1f) driftDir.negate();
537                                
538                                float distToShip = Misc.getDistance(ship.getLocation(), missile.getLocation());
539                                float shipToFireTarget = Misc.getDistance(ship.getLocation(), fireTarget.getLocation());
540                                float extra = 0f;
541                                if (dist > shipToFireTarget) extra = dist - shipToFireTarget;
542                                if (distToShip < ship.getCollisionRadius() * 1f + extra) {
543                                        float away = Misc.getAngleInDegrees(ship.getLocation(), missile.getLocation());
544                                        float turnDir = Misc.getClosestTurnDirection(away, missile.getFacing());
545                                        strafeDir = turnDir;
546                                }
547                                
548                                float maxDrift = missile.getMaxSpeed() * allowedDriftFraction;
549                                float speedInDir = Vector2f.dot(driftDir, missile.getVelocity());
550                                
551                                if (speedInDir < maxDrift) {
552                                        if (strafeDir == 1f) {
553                                                missile.giveCommand(ShipCommand.STRAFE_RIGHT);
554                                        } else if (strafeDir == -1f) {
555                                                missile.giveCommand(ShipCommand.STRAFE_LEFT);
556                                        }
557                                }
558                                
559                                strafeDur -= amount;
560                        }
561                }
562        }
563
564        public void advance(float amount) {
565                // MissileAIPlugin.advance()
566                // unused, but just want the missile to have a non-null AI
567        }
568        
569        
570        
571        public static class ShapedExplosionParams {
572                public float shapedExplosionEndSizeMin = 1f;
573                public float shapedExplosionEndSizeMax = 2f;
574                public Color shapedExplosionColor = new Color(255,150,130,155);
575                public int shapedExplosionNumParticles = 200;
576                public float shapedExplosionMinParticleSize = 80;
577                public float shapedExplosionMaxParticleSize = 100;
578                public float shapedExplosionScatter = 100f;
579                public float shapedExplosionMinParticleVel = 100;
580                public float shapedExplosionMaxParticleVel = 350f;
581                public float shapedExplosionMinParticleDur = 1f;
582                public float shapedExplosionMaxParticleDur = 2f;
583                public float shapedExplosionArc = 90f;
584                
585                public void load(JSONObject json) throws JSONException {
586                        shapedExplosionEndSizeMin = (float)json.optDouble("shapedExplosionEndSizeMin", 1f);
587                        shapedExplosionEndSizeMax = (float)json.optDouble("shapedExplosionEndSizeMax", 2f);
588                        shapedExplosionNumParticles = json.optInt("shapedExplosionNumParticles");
589                        shapedExplosionMinParticleSize = (float)json.optDouble("shapedExplosionMinParticleSize", 80f);
590                        shapedExplosionMaxParticleSize = (float)json.optDouble("shapedExplosionMaxParticleSize", 100f);
591                        shapedExplosionScatter = (float)json.optDouble("shapedExplosionScatter", 100f);
592                        shapedExplosionMinParticleVel = (float)json.optDouble("shapedExplosionMinParticleVel", 100f);
593                        shapedExplosionMaxParticleVel = (float)json.optDouble("shapedExplosionMaxParticleVel", 350f);
594                        shapedExplosionMinParticleDur = (float)json.optDouble("shapedExplosionMinParticleDur", 1f);
595                        shapedExplosionMaxParticleDur = (float)json.optDouble("shapedExplosionMaxParticleDur", 2f);
596                        shapedExplosionArc = (float)json.optDouble("shapedExplosionArc", 90f);
597                        shapedExplosionColor = Misc.optColor(json, "shapedExplosionColor", null);
598                }
599        }
600        
601        public void spawnShapedExplosion(Vector2f loc, float angle, ShapedExplosionParams p) {
602                
603                if (Global.getCombatEngine().getViewport().isNearViewport(ship.getLocation(), 800f)) {
604                        int numParticles = p.shapedExplosionNumParticles;
605                        float minSize = p.shapedExplosionMinParticleSize;
606                        float maxSize = p.shapedExplosionMaxParticleSize;
607                        Color pc = p.shapedExplosionColor;
608                        
609                        float minDur = p.shapedExplosionMinParticleDur;
610                        float maxDur = p.shapedExplosionMaxParticleDur;
611                        
612                        float arc = p.shapedExplosionArc; 
613                        float scatter = p.shapedExplosionScatter;
614                        float minVel = p.shapedExplosionMinParticleVel;
615                        float maxVel = p.shapedExplosionMaxParticleVel;
616                        
617                        float endSizeMin = p.shapedExplosionEndSizeMin;
618                        float endSizeMax = p.shapedExplosionEndSizeMax;
619                        
620                        Vector2f spawnPoint = new Vector2f(loc);
621                        for (int i = 0; i < numParticles; i++) {
622                                //p.setMaxAge(500 + (int)(Math.random() * 1000f));
623                                float angleOffset = (float) Math.random();
624                                if (angleOffset > 0.2f) {
625                                        angleOffset *= angleOffset;
626                                }
627                                float speedMult = 1f - angleOffset;
628                                speedMult = 0.5f + speedMult * 0.5f;
629                                angleOffset *= Math.signum((float) Math.random() - 0.5f);
630                                angleOffset *= arc/2f;
631                                float theta = (float) Math.toRadians(angle + angleOffset);
632                                float r = (float) (Math.random() * Math.random() * scatter);
633                                float x = (float)Math.cos(theta) * r;
634                                float y = (float)Math.sin(theta) * r;
635                                Vector2f pLoc = new Vector2f(spawnPoint.x + x, spawnPoint.y + y);
636
637                                float speed = minVel + (maxVel - minVel) * (float) Math.random();
638                                speed *= speedMult;
639                                
640                                Vector2f pVel = Misc.getUnitVectorAtDegreeAngle((float) Math.toDegrees(theta));
641                                pVel.scale(speed);
642                                
643                                float pSize = minSize + (maxSize - minSize) * (float) Math.random();
644                                float pDur = minDur + (maxDur - minDur) * (float) Math.random();
645                                float endSize = endSizeMin + (endSizeMax - endSizeMin) * (float) Math.random();
646                                //Global.getCombatEngine().addSmoothParticle(pLoc, pVel, pSize, 1f, pDur, pc);
647                                Global.getCombatEngine().addNebulaParticle(pLoc, pVel, pSize, endSize, 0.1f, 0.5f, pDur, pc);
648                                //Global.getCombatEngine().addNebulaSmoothParticle(pLoc, pVel, pSize, endSize, 0.1f, 0.5f, pDur, pc);
649                                //Global.getCombatEngine().addSwirlyNebulaParticle(pLoc, pVel, pSize, endSize, 0.1f, 0.5f, pDur, pc, false);
650                        }
651                }
652        }
653}
654
655
656
657
658
659
660
661