001package com.fs.starfarer.api.impl.combat;
002
003import java.util.ArrayList;
004import java.util.Iterator;
005import java.util.List;
006
007import org.lwjgl.util.vector.Vector2f;
008
009import com.fs.starfarer.api.Global;
010import com.fs.starfarer.api.combat.CollisionGridAPI;
011import com.fs.starfarer.api.combat.CombatEngineAPI;
012import com.fs.starfarer.api.combat.CombatEntityAPI;
013import com.fs.starfarer.api.combat.MissileAIPlugin;
014import com.fs.starfarer.api.combat.MissileAPI;
015import com.fs.starfarer.api.combat.ShipAPI;
016import com.fs.starfarer.api.combat.ShipCommand;
017import com.fs.starfarer.api.impl.combat.MoteControlScript.SharedMoteAIData;
018import com.fs.starfarer.api.util.FaderUtil;
019import com.fs.starfarer.api.util.IntervalUtil;
020import com.fs.starfarer.api.util.Misc;
021
022public class MoteAIScript implements MissileAIPlugin {
023
024        public static float MAX_FLOCK_RANGE = 500;
025        public static float MAX_HARD_AVOID_RANGE = 200;
026        public static float AVOID_RANGE = 50;
027        public static float COHESION_RANGE = 100;
028
029        public static float ATTRACTOR_LOCK_STOP_FLOCKING_ADD = 300f; 
030        
031        protected MissileAPI missile;
032        
033        protected IntervalUtil tracker = new IntervalUtil(0.05f, 0.1f);
034        
035        protected IntervalUtil updateListTracker = new IntervalUtil(0.05f, 0.1f);
036        protected List<MissileAPI> missileList = new ArrayList<MissileAPI>();
037        protected List<CombatEntityAPI> hardAvoidList = new ArrayList<CombatEntityAPI>();
038        
039        protected float r;
040
041        protected CombatEntityAPI target;
042        protected SharedMoteAIData data;
043        
044        public MoteAIScript(MissileAPI missile) {
045                this.missile = missile;
046                r = (float) Math.random();
047                elapsed = -(float) Math.random() * 0.5f;
048                
049                data = MoteControlScript.getSharedData(missile.getSource());
050                
051                updateHardAvoidList();
052        }
053        
054        public void updateHardAvoidList() {
055                hardAvoidList.clear();
056                
057                CollisionGridAPI grid = Global.getCombatEngine().getAiGridShips();
058                Iterator<Object> iter = grid.getCheckIterator(missile.getLocation(), MAX_HARD_AVOID_RANGE * 2f, MAX_HARD_AVOID_RANGE * 2f);
059                while (iter.hasNext()) {
060                        Object o = iter.next();
061                        if (!(o instanceof ShipAPI)) continue;
062                        
063                        ShipAPI ship = (ShipAPI) o;
064                        
065                        if (ship.isFighter()) continue;
066                        hardAvoidList.add(ship);
067                }
068                
069                grid = Global.getCombatEngine().getAiGridAsteroids();
070                iter = grid.getCheckIterator(missile.getLocation(), MAX_HARD_AVOID_RANGE * 2f, MAX_HARD_AVOID_RANGE * 2f);
071                while (iter.hasNext()) {
072                        Object o = iter.next();
073                        if (!(o instanceof CombatEntityAPI)) continue;
074                        
075                        CombatEntityAPI asteroid = (CombatEntityAPI) o;
076                        hardAvoidList.add(asteroid);
077                }
078        }
079        
080        public void doFlocking() {
081                if (missile.getSource() == null) return;
082                
083                ShipAPI source = missile.getSource();
084                CombatEngineAPI engine = Global.getCombatEngine();
085                
086                float avoidRange = AVOID_RANGE;
087                float cohesionRange = COHESION_RANGE;
088                
089                float sourceRejoin = source.getCollisionRadius() + 200f;
090                
091                float sourceRepel = source.getCollisionRadius() + 50f;
092                float sourceCohesion = source.getCollisionRadius() + 600f;
093                
094                float sin = (float) Math.sin(data.elapsed * 1f);
095                float mult = 1f + sin * 0.25f;
096                avoidRange *= mult;
097                
098                Vector2f total = new Vector2f();
099                Vector2f attractor = getAttractorLoc();
100                
101                if (attractor != null) {
102                        float dist = Misc.getDistance(missile.getLocation(), attractor);
103                        Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(missile.getLocation(), attractor));
104                        float f = dist / 200f;
105                        if (f > 1f) f = 1f;
106                        dir.scale(f * 3f);
107                        Vector2f.add(total, dir, total);
108                        
109                        avoidRange *= 3f;
110                }
111                
112                boolean hardAvoiding = false;
113                for (CombatEntityAPI other : hardAvoidList) {
114                        float dist = Misc.getDistance(missile.getLocation(), other.getLocation());
115                        float hardAvoidRange = other.getCollisionRadius() + avoidRange + 50f;
116                        if (dist < hardAvoidRange) {
117                                Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(other.getLocation(), missile.getLocation()));
118                                float f = 1f - dist / (hardAvoidRange);
119                                dir.scale(f * 5f);
120                                Vector2f.add(total, dir, total);
121                                hardAvoiding = f > 0.5f;
122                        }
123                }
124                
125                
126                //for (MissileAPI otherMissile : missileList) {
127                for (MissileAPI otherMissile : data.motes) {
128                        if (otherMissile == missile) continue;
129                        
130                        float dist = Misc.getDistance(missile.getLocation(), otherMissile.getLocation());
131                        
132                        
133                        float w = otherMissile.getMaxHitpoints();
134                        w = 1f;
135                        
136                        float currCohesionRange = cohesionRange;
137                        
138                        if (dist < avoidRange && otherMissile != missile && !hardAvoiding) {
139                                Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(otherMissile.getLocation(), missile.getLocation()));
140                                float f = 1f - dist / avoidRange;
141                                dir.scale(f * w);
142                                Vector2f.add(total, dir, total);
143                        }
144                        
145                        if (dist < currCohesionRange) {
146                                Vector2f dir = new Vector2f(otherMissile.getVelocity());
147                                Misc.normalise(dir);
148                                float f = 1f - dist / currCohesionRange;
149                                dir.scale(f * w);
150                                Vector2f.add(total, dir, total);
151                        }
152                        
153//                      if (dist < cohesionRange && dist > avoidRange) {
154//                              //Vector2f dir = Utils.getUnitVectorAtDegreeAngle(Utils.getAngleInDegrees(missile.getLocation(), mote.getLocation()));
155//                              Vector2f dir = Utils.getUnitVectorAtDegreeAngle(Utils.getAngleInDegrees(mote.getLocation(), missile.getLocation()));
156//                              float f = dist / cohesionRange - 1f;
157//                              dir.scale(f * 0.5f);
158//                              Vector2f.add(total, dir, total);
159//                      }
160                }
161                
162                if (missile.getSource() != null) {
163                        float dist = Misc.getDistance(missile.getLocation(), source.getLocation());
164                        if (dist > sourceRejoin) {
165                                Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(missile.getLocation(), source.getLocation()));
166                                float f = dist / (sourceRejoin  + 400f) - 1f;
167                                dir.scale(f * 0.5f);
168                                
169                                Vector2f.add(total, dir, total);
170                        }
171                        
172                        if (dist < sourceRepel) {
173                                Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(source.getLocation(), missile.getLocation()));
174                                float f = 1f - dist / sourceRepel;
175                                dir.scale(f * 5f);
176                                Vector2f.add(total, dir, total);
177                        }
178                        
179                        if (dist < sourceCohesion && source.getVelocity().length() > 20f) {
180                                Vector2f dir = new Vector2f(source.getVelocity());
181                                Misc.normalise(dir);
182                                float f = 1f - dist / sourceCohesion;
183                                dir.scale(f * 1f);
184                                Vector2f.add(total, dir, total);
185                        }
186                        
187                        // if not strongly going anywhere, circle the source ship; only kicks in for lone motes
188                        if (total.length() <= 0.05f) {
189                                float offset = r > 0.5f ? 90f : -90f;
190                                Vector2f dir = Misc.getUnitVectorAtDegreeAngle(
191                                                Misc.getAngleInDegrees(missile.getLocation(), source.getLocation()) + offset);
192                                float f = 1f;
193                                dir.scale(f * 1f);
194                                Vector2f.add(total, dir, total);
195                        }
196                }
197                
198                if (total.length() > 0) {
199                        float dir = Misc.getAngleInDegrees(total);
200                        engine.headInDirectionWithoutTurning(missile, dir, 10000);
201                        
202                        if (r > 0.5f) {
203                                missile.giveCommand(ShipCommand.TURN_LEFT);
204                        } else {
205                                missile.giveCommand(ShipCommand.TURN_RIGHT);
206                        }
207                        missile.getEngineController().forceShowAccelerating();
208                }
209        }
210        
211        //public void accumulate(FlockingData data, Vector2f )
212
213
214        protected IntervalUtil flutterCheck = new IntervalUtil(2f, 4f);
215        protected FaderUtil currFlutter = null;
216        protected float flutterRemaining = 0f;
217        
218        protected float elapsed = 0f;
219        public void advance(float amount) {
220                if (missile.isFizzling()) return;
221                if (missile.getSource() ==  null) return;
222                
223                elapsed += amount;
224                
225                updateListTracker.advance(amount);
226                if (updateListTracker.intervalElapsed()) {
227                        updateHardAvoidList();
228                }
229                
230                //missile.getEngineController().getShipEngines().get(0).
231                
232                if (flutterRemaining <= 0) {
233                        flutterCheck.advance(amount);
234                        if (flutterCheck.intervalElapsed() && 
235                                        ((float) Math.random() > 0.9f || 
236                                                        (data.attractorLock != null && (float) Math.random() > 0.5f))) {
237                                flutterRemaining = 2f + (float) Math.random() * 2f;
238                        }
239                }
240                
241//              if (flutterRemaining > 0) {
242//                      flutterRemaining -= amount;
243//                      if (currFlutter == null) {
244//                              float min = 1/15f;
245//                              float max = 1/4f;
246//                              float dur = min + (max - min) * (float) Math.random();
247//                              //dur *= 0.5f;
248//                              currFlutter = new FaderUtil(0f, dur/2f, dur/2f, false, true);
249//                              currFlutter.fadeIn();
250//                      }
251//                      currFlutter.advance(amount);
252//                      if (currFlutter.isFadedOut()) {
253//                              currFlutter = null;
254//                      }
255//              } else {
256//                      currFlutter = null;
257//              }
258//              
259//              if (currFlutter != null) {
260//                      missile.setGlowRadius(currFlutter.getBrightness() * 30f);
261//              } else {
262//                      missile.setGlowRadius(0f);
263//              }
264//              if (true) {
265//                      doFlocking();
266//                      return;
267//              }
268                
269                
270                if (elapsed >= 0.5f) {
271                        
272                        boolean wantToFlock = !isTargetValid();
273                        if (data.attractorLock != null) {
274                                float dist = Misc.getDistance(missile.getLocation(), data.attractorLock.getLocation());
275                                if (dist > data.attractorLock.getCollisionRadius() + ATTRACTOR_LOCK_STOP_FLOCKING_ADD) {
276                                        wantToFlock = true;
277                                }
278                        }
279                        
280                        if (wantToFlock) {
281                                doFlocking();
282                        } else {
283                                CombatEngineAPI engine = Global.getCombatEngine();
284                                Vector2f targetLoc = engine.getAimPointWithLeadForAutofire(missile, 1.5f, target, 50);
285                                engine.headInDirectionWithoutTurning(missile, 
286                                                                                                 Misc.getAngleInDegrees(missile.getLocation(), targetLoc),
287                                                                                                 10000);
288                                //AIUtils.turnTowardsPointV2(missile, targetLoc);
289                                if (r > 0.5f) {
290                                        missile.giveCommand(ShipCommand.TURN_LEFT);
291                                } else {
292                                        missile.giveCommand(ShipCommand.TURN_RIGHT);
293                                }
294                                missile.getEngineController().forceShowAccelerating();
295                        }
296                }
297                
298                tracker.advance(amount);
299                if (tracker.intervalElapsed()) {
300                        if (elapsed >= 0.5f) {
301                                acquireNewTargetIfNeeded();
302                        }
303                        //causeEnemyMissilesToTargetThis();
304                }
305        }
306        
307        
308        @SuppressWarnings("unchecked")
309        protected boolean isTargetValid() {
310                if (target == null || (target instanceof ShipAPI && ((ShipAPI)target).isPhased())) {
311                        return false;
312                }
313                CombatEngineAPI engine = Global.getCombatEngine();
314                
315                if (target != null && target instanceof ShipAPI && ((ShipAPI)target).isHulk()) return false;
316                
317                List list = null;
318                if (target instanceof ShipAPI) {
319                        list = engine.getShips();
320                } else {
321                        list = engine.getMissiles();
322                }
323                return target != null && list.contains(target) && target.getOwner() != missile.getOwner();
324        }
325        
326        protected void acquireNewTargetIfNeeded() {
327                if (data.attractorLock != null) {
328                        target = data.attractorLock;
329                        return;
330                }
331                
332                CombatEngineAPI engine = Global.getCombatEngine();
333                
334                // want to: target nearest missile that is not targeted by another two motes already
335                int owner = missile.getOwner();
336                
337                int maxMotesPerMissile = 2;
338                float maxDistFromSourceShip = MoteControlScript.MAX_DIST_FROM_SOURCE_TO_ENGAGE_AS_PD;
339                float maxDistFromAttractor = MoteControlScript.MAX_DIST_FROM_ATTRACTOR_TO_ENGAGE_AS_PD;
340                
341                float minDist = Float.MAX_VALUE;
342                CombatEntityAPI closest = null;
343                for (MissileAPI other : engine.getMissiles()) {
344                        if (other.getOwner() == owner) continue;
345                        if (other.getOwner() == 100) continue;
346                        float distToTarget = Misc.getDistance(missile.getLocation(), other.getLocation());
347                        
348                        if (distToTarget > minDist) continue;
349                        if (distToTarget > 3000 && !engine.isAwareOf(owner, other)) continue;
350                        
351                        float distFromAttractor = Float.MAX_VALUE;
352                        if (data.attractorTarget != null) {
353                                distFromAttractor = Misc.getDistance(other.getLocation(), data.attractorTarget);
354                        }
355                        float distFromSource = Misc.getDistance(other.getLocation(), missile.getSource().getLocation());
356                        if (distFromSource > maxDistFromSourceShip &&
357                                        distFromAttractor > maxDistFromAttractor) continue;
358                        
359                        if (getNumMotesTargeting(other) >= maxMotesPerMissile) continue;
360                        if (distToTarget < minDist) {
361                                closest = other;
362                                minDist = distToTarget;
363                        }
364                }
365                
366                for (ShipAPI other : engine.getShips()) {
367                        if (other.getOwner() == owner) continue;
368                        if (other.getOwner() == 100) continue;
369                        if (!other.isFighter()) continue;
370                        float distToTarget = Misc.getDistance(missile.getLocation(), other.getLocation());
371                        if (distToTarget > minDist) continue;
372                        if (distToTarget > 3000 && !engine.isAwareOf(owner, other)) continue;
373                        
374                        float distFromAttractor = Float.MAX_VALUE;
375                        if (data.attractorTarget != null) {
376                                distFromAttractor = Misc.getDistance(other.getLocation(), data.attractorTarget);
377                        }
378                        float distFromSource = Misc.getDistance(other.getLocation(), missile.getSource().getLocation());
379                        if (distFromSource > maxDistFromSourceShip &&
380                                        distFromAttractor > maxDistFromAttractor) continue;
381                        
382                        if (getNumMotesTargeting(other) >= maxMotesPerMissile) continue;
383                        if (distToTarget < minDist) {
384                                closest = other;
385                                minDist = distToTarget;
386                        }
387                }
388                
389                target = closest;
390        }
391        
392        protected int getNumMotesTargeting(CombatEntityAPI other) {
393                int count = 0;
394                for (MissileAPI mote : data.motes) {
395                        if (mote == missile) continue;
396                        if (mote.getUnwrappedMissileAI() instanceof MoteAIScript) {
397                                MoteAIScript ai = (MoteAIScript) mote.getUnwrappedMissileAI();
398                                if (ai.getTarget() == other) {
399                                        count++;
400                                }
401                        }
402                }
403                return count;
404        }
405        
406        public Vector2f getAttractorLoc() {
407                Vector2f attractor = null;
408                if (data.attractorTarget != null) {
409                        attractor = data.attractorTarget;
410                        if (data.attractorLock != null) {
411                                attractor = data.attractorLock.getLocation();
412                        }
413                }
414                return attractor;
415        }
416
417        public CombatEntityAPI getTarget() {
418                return target;
419        }
420
421        public void setTarget(CombatEntityAPI target) {
422                this.target = target;
423        }
424        public void render() {
425                
426        }
427}