001package com.fs.starfarer.api.impl.combat.threat;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import org.lwjgl.util.vector.Vector2f;
007
008import com.fs.starfarer.api.Global;
009import com.fs.starfarer.api.combat.CombatEngineAPI;
010import com.fs.starfarer.api.combat.DamageType;
011import com.fs.starfarer.api.combat.FighterWingAPI;
012import com.fs.starfarer.api.combat.MutableShipStatsAPI;
013import com.fs.starfarer.api.combat.ShipAIConfig;
014import com.fs.starfarer.api.combat.ShipAIPlugin;
015import com.fs.starfarer.api.combat.ShipAPI;
016import com.fs.starfarer.api.combat.ShipCommand;
017import com.fs.starfarer.api.combat.ShipwideAIFlags;
018import com.fs.starfarer.api.combat.ShipwideAIFlags.AIFlags;
019import com.fs.starfarer.api.combat.WeaponGroupAPI;
020import com.fs.starfarer.api.impl.combat.threat.ConstructionSwarmSystemScript.SwarmConstructionData;
021import com.fs.starfarer.api.util.IntervalUtil;
022import com.fs.starfarer.api.util.Misc;
023import com.fs.starfarer.api.util.WeightedRandomPicker;
024
025public class ThreatSwarmAI implements ShipAIPlugin {
026
027        public static float ATTRACTOR_RANGE_MAX_SAME_WING = 1000000f;
028        public static float ATTRACTOR_RANGE_MAX = 500f;
029        public static float COHESION_RANGE_MIN = 150f;
030        public static float COHESION_RANGE_MAX = 300f;
031        public static float REPEL_RANGE_MIN = 0f;
032        public static float REPEL_RANGE_MAX = 150f;
033        
034        public static float MAX_TARGET_RANGE = 3000f;
035        
036        public static boolean isAttackSwarm(ShipAPI ship) {
037                return ship != null && ship.getVariant().getHullVariantId().equals(SwarmLauncherEffect.ATTACK_SWARM_VARIANT);
038        }
039        public static boolean isConstructionSwarm(ShipAPI ship) {
040                return ship != null && ship.getVariant().getHullVariantId().equals(SwarmLauncherEffect.CONSTRUCTION_SWARM_VARIANT);
041        }
042        public static boolean isReclamationSwarm(ShipAPI ship) {
043                return ship != null && ship.getVariant().getHullVariantId().equals(SwarmLauncherEffect.RECLAMATION_SWARM_VARIANT);
044        }
045        
046        public static class SharedSwarmWingData {
047                public ShipAPI target = null;
048        }
049        
050        public static class FlockingData {
051                public Vector2f loc;
052                public Vector2f vel;
053                public float minA;
054                public float maxA;
055                public float minR;
056                public float maxR;
057                public float repelAtAngleDist;
058                public float minC;
059                public float maxC;
060                public float attractWeight;
061                public float repelWeight;
062                public float cohesionWeight;
063                public float facing;
064        }
065        
066        public static float PROB_ENABLE_OTHER_GROUP = 0.5f;
067        
068        protected ShipwideAIFlags flags = new ShipwideAIFlags();
069        protected ShipAPI ship;
070        
071        protected IntervalUtil updateInterval = new IntervalUtil(0.5f, 1.5f);
072        protected IntervalUtil headingInterval = new IntervalUtil(0.5f, 1.5f);
073        protected IntervalUtil attackRangeMultInterval = new IntervalUtil(0.2f, 1.8f);
074        protected IntervalUtil reclamationReturnInterval = new IntervalUtil(0.2f, 1.8f);
075        
076        protected float sinceTurnedOffFlash = 0f;
077        protected ShipAPI fabricator = null;
078        
079        protected List<FlockingData> flockingData = new ArrayList<>();
080        protected float desiredHeading = 0f;
081        protected float headingChangeRate = 0f;
082        protected float elapsedSincePrevHeadingUpdate = 0f;
083        protected float attackRangeMult = 1f;
084        
085        protected IntervalUtil enableOtherWeaponInterval = new IntervalUtil(5f, 15f);
086        protected IntervalUtil priorityTargetPickerInterval = new IntervalUtil(1f, 3f);
087        protected float enableOtherWeaponDuration = 0f;
088        protected float elapsed = 0f;
089        
090        protected boolean startedConstruction = false;
091        private SwarmConstructionData constructionData;
092        private ThreatShipConstructionScript constructionScript;
093        
094        protected boolean attackSwarm = false;
095        protected boolean constructionSwarm = false;
096        protected boolean reclamationSwarm = false;
097        
098        public ThreatSwarmAI(ShipAPI ship) {
099                this.ship = ship;
100                
101                attackSwarm = isAttackSwarm(ship);
102                constructionSwarm = isConstructionSwarm(ship);
103                reclamationSwarm = isReclamationSwarm(ship);
104                
105                doInitialSetup();
106                
107                updateInterval.forceIntervalElapsed();
108                headingInterval.forceIntervalElapsed();
109                attackRangeMultInterval.forceIntervalElapsed();
110                priorityTargetPickerInterval.forceIntervalElapsed();
111        }
112        
113        public SharedSwarmWingData getShared() {
114                if (ship.getWing() == null) return new SharedSwarmWingData();
115                
116                String key = "SharedSwarmWingData";
117                SharedSwarmWingData data = (SharedSwarmWingData) ship.getWing().getCustomData().get(key);
118                if (data == null) {
119                        data = new SharedSwarmWingData();
120                        ship.getWing().getCustomData().put(key, data);
121                }
122                return data;
123        }
124        
125        protected void doInitialSetup() {
126                if (attackSwarm) {
127                        // 0: voltaic
128                        // 1: unstable
129                        // 2: kinetic
130                        // 3: seeker
131                        // 4: defabrication
132                        
133                        toggleOn(0);
134                        toggleOn(1);
135                        toggleOff(2);
136                        toggleOff(3);
137                        toggleOff(4);
138                        
139                        ship.giveCommand(ShipCommand.SELECT_GROUP, null, 6);
140                }
141        }
142        
143        protected void toggleOn(int groupNum) {
144                List<WeaponGroupAPI> groups = ship.getWeaponGroupsCopy();
145                if (groups.size() <= groupNum) return;
146                groups.get(groupNum).toggleOn();
147        }
148        protected void toggleOff(int groupNum) {
149                List<WeaponGroupAPI> groups = ship.getWeaponGroupsCopy();
150                if (groups.size() <= groupNum) return;
151                groups.get(groupNum).toggleOff();
152        }
153        
154        
155        protected void advanceForSpecificSwarmType(float amount) {
156                if (attackSwarm) {
157                        if (ship.isWingLeader()) {
158                                priorityTargetPickerInterval.advance(amount);
159                                if (priorityTargetPickerInterval.intervalElapsed()) {
160                                        pickPriorityTarget();
161                                }
162                        }
163                        
164                        attackRangeMultInterval.advance(amount * 0.1f);
165                        if (attackRangeMultInterval.intervalElapsed()) {
166                                updateAttackRangeMult();
167                        }
168                        
169                        // 0: voltaic, always on
170                        // 1: unstable, always on
171                        // 2: kinetic
172                        // 3: seeker
173                        // 4: unused (was defab at some point)
174                        if (enableOtherWeaponDuration > 0) {
175                                enableOtherWeaponDuration -= amount;
176                                if (enableOtherWeaponDuration <= 0) {
177                                        toggleOff(2);
178                                        toggleOff(3);
179                                        toggleOff(4);
180                                }
181                        } else {
182                                //amount *= 10f;
183                                boolean phaseMode = VoltaicDischargeOnFireEffect.isSwarmPhaseMode(ship);
184                                
185                                
186                                enableOtherWeaponInterval.advance(amount * 5f);
187                                if (enableOtherWeaponInterval.intervalElapsed()) {
188                                        if ((float) Math.random() < PROB_ENABLE_OTHER_GROUP) {
189                                                toggleOff(2);
190                                                toggleOff(3);
191                                                toggleOff(4);
192                                                
193                                                ShipAPI target = (ShipAPI) flags.getCustom(AIFlags.MANEUVER_TARGET);
194                                                
195//                                              boolean targetShieldsFacingUs = false;
196//                                              if (target != null) {
197//                                                      ShieldAPI targetShield = target.getShield();
198//                                                      targetShieldsFacingUs = targetShield != null &&
199//                                                                              targetShield.isOn() &&
200//                                                                              Misc.isInArc(targetShield.getFacing(), Math.max(30f, targetShield.getActiveArc()),
201//                                                                                              target.getLocation(), ship.getLocation());
202//                                              }
203//                                              if (targetShieldsFacingUs) {
204//                                                      toggleOn(2);
205//                                              } else {
206//                                                      toggleOn(3);
207//                                              }
208                                                
209                                                
210                                                // use Seeker only when it will be destroyed by using seeker, as a "final attack"
211                                                boolean useSeeker = ship.getHullLevel() < 0.22f;
212                                                boolean useKinetic = true;
213                                                if (target == null || target.isFighter()) {
214                                                        useSeeker = false;
215                                                        useKinetic = false;
216                                                }
217                                                
218                                                if (useSeeker) {
219                                                        toggleOn(3);
220                                                } else if (useKinetic) {
221                                                        toggleOn(2);
222                                                }
223                                                
224                                                enableOtherWeaponDuration = 0.5f + 0.5f * (float) Math.random();
225                                        }
226                                }
227                        }
228                }
229                
230                if (constructionSwarm) {
231                        if (constructionData == null) {
232                                RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship);
233                                if (swarm != null) {
234                                        constructionData = (SwarmConstructionData) swarm.custom1;
235                                }
236                        }
237                        if (constructionData != null) {
238                                if (elapsed > constructionData.preConstructionTravelTime && !startedConstruction) {
239                                        startedConstruction = true;
240                                        RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship);
241                                        if (swarm != null) {
242                                                constructionScript = new ThreatShipConstructionScript(
243                                                                                        constructionData.variantId, ship, 0f, constructionData.constructionTime);
244                                                Global.getCombatEngine().addPlugin(constructionScript);
245                                        }
246                                }
247                        }
248                }
249                
250                if (reclamationSwarm) {
251                        if (fabricator == null) {
252                                reclamationReturnInterval.advance(amount);
253                                if (reclamationReturnInterval.intervalElapsed()) {
254                                        int owner = ship.getOriginalOwner();
255                                        CombatEngineAPI engine = Global.getCombatEngine();
256                                        for (ShipAPI curr : engine.getShips()) {
257                                                if (curr == ship || curr.getOwner() != owner) continue;
258                                                if (curr.isHulk() || curr.getOwner() == 100) continue;
259                                                if (!ThreatCombatStrategyAI.isFabricator(curr)) continue;
260                                                
261                                                float dist = Misc.getDistance(curr.getLocation(), ship.getLocation());
262                                                if (dist < curr.getCollisionRadius() + 200f) {
263                                                        // turn off flash and return to fabricator
264                                                        RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship);
265                                                        swarm.params.flashFrequency = 0f;
266                                                        swarm.params.flashProbability = 0f;
267                                                        
268                                                        fabricator = curr;
269                                                        break;
270                                                }
271                                        }
272                                }                               
273                        } else {
274                                sinceTurnedOffFlash += amount;
275                                if (sinceTurnedOffFlash > 3f) {
276                                        CombatEngineAPI engine = Global.getCombatEngine();
277                                        if (fabricator.isAlive()) {
278                                                fabricator.setCurrentCR(Math.min(1f, fabricator.getCurrentCR() + 0.01f * ship.getHullLevel()));
279                                                
280                                                RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship);
281                                                RoilingSwarmEffect swarmFabricator = RoilingSwarmEffect.getSwarmFor(fabricator);
282                                                if (swarm != null && swarmFabricator != null) {
283                                                        swarm.transferMembersTo(swarmFabricator, swarm.getNumActiveMembers());
284                                                }
285                                        }
286                                        ship.setHitpoints(0f);
287                                        ship.setSpawnDebris(false);
288                                        engine.applyDamage(ship, ship.getLocation(), 100f, DamageType.ENERGY, 0f, true, false, ship, false);
289                                }
290                        }
291                }
292        }
293        
294        protected void pickPriorityTarget() {
295                SharedSwarmWingData data = getShared();
296                if (data.target != null && data.target.isAlive()) {
297                        return;
298                }
299                
300                WeightedRandomPicker<ShipAPI> picker = new WeightedRandomPicker<>();
301                CombatEngineAPI engine = Global.getCombatEngine();
302                int owner = ship.getOriginalOwner();
303                
304                for (ShipAPI curr : engine.getShips()) {
305                        if (curr == ship) continue;
306                        if (curr.isFighter()) continue;
307                        if (curr.isHulk() || curr.getOwner() == 100) continue;
308                        
309                        if (curr.getOwner() != owner && engine.isAwareOf(owner, curr)) {
310                                float weight = getShipWeight(curr);
311                                if (curr.isFrigate()) {
312                                        weight *= 0.0001f;
313                                }
314                                picker.add(curr, weight);
315                        } 
316                }
317                
318                data.target = picker.pick();
319        }
320        protected void updateAttackRangeMult() {
321                //attackRangeMult = 0.75f + 0.5f * (float) Math.random();
322                attackRangeMult = 0.5f + 1f * (float) Math.random();
323        }
324
325        @Override
326        public void advance(float amount) {
327                //if (true) return;
328                
329                elapsed += amount;
330                advanceForSpecificSwarmType(amount);
331                
332                updateInterval.advance(amount);
333                if (updateInterval.intervalElapsed()) {
334                        updateFlockingData();
335                }
336                
337                headingInterval.advance(amount * 5f);
338                if (headingInterval.intervalElapsed()) {
339                        computeDesiredHeading();
340                        elapsedSincePrevHeadingUpdate = 0f;
341                }
342                
343                giveMovementCommands();
344                
345                elapsedSincePrevHeadingUpdate += amount;
346        }
347        
348        protected void giveMovementCommands() {
349                if (constructionScript != null && constructionScript.getShip() != null) {
350                        ship.giveCommand(ShipCommand.DECELERATE, null, 0);
351                        return;
352                }
353                
354                String source = "swarm_wingman_catch_up_speed_bonus";
355                MutableShipStatsAPI stats = ship.getMutableStats();
356                if (ship.isWingLeader() || ship.getWingLeader() == null) {
357                        stats.getMaxSpeed().unmodifyMult(source);
358                        stats.getAcceleration().unmodifyMult(source);
359                        stats.getDeceleration().unmodifyMult(source);
360                } else {
361                        ShipAPI leader = ship.getWingLeader();
362                        float dist = Misc.getDistance(ship.getLocation(), leader.getLocation());
363                        float mult = (dist - COHESION_RANGE_MAX * 0.5f - 
364                                        ship.getCollisionRadius() * 0.5f - leader.getCollisionRadius() * 0.5f) / COHESION_RANGE_MAX;
365                        if (mult < 0f) mult = 0f;
366                        if (mult > 1f) mult = 1f;
367                        stats.getMaxSpeed().modifyMult(source, 1f + .25f * mult);
368                        stats.getAcceleration().modifyMult(source, 1f + 0.5f * mult);
369                        stats.getDeceleration().modifyMult(source, 1f + 0.5f * mult);
370                }
371                
372                float useHeading = desiredHeading;
373                //useHeading += headingChangeRate * elapsedSincePrevHeadingUpdate;
374                
375                CombatEngineAPI engine = Global.getCombatEngine();
376                engine.headInDirectionWithoutTurning(ship, useHeading, 10000);
377                Misc.turnTowardsFacingV2(ship, useHeading, 0f);
378        }
379        
380        protected void computeDesiredHeading() {
381                
382                Vector2f loc = ship.getLocation();
383                Vector2f vel = ship.getVelocity();
384                float facing = ship.getFacing();
385                
386                Vector2f total = new Vector2f();
387                
388                for (FlockingData curr : flockingData) {
389                        float dist = Misc.getDistance(curr.loc, loc);
390                        if (curr.maxR > 0 && dist < curr.maxR) {
391                                float repelWeight = curr.repelWeight;
392                                if (dist > curr.minR && curr.maxR > curr.minR) {
393                                        repelWeight = (dist - curr.minR)  / (curr.maxR - curr.minR);
394                                        if (repelWeight > 1f) repelWeight = 1f;
395                                        repelWeight = 1f - repelWeight;
396                                        repelWeight *= curr.repelWeight;
397                                }
398                                
399                                Vector2f dir = Misc.getUnitVector(curr.loc, loc);
400                                
401                                float distIntoRepel = curr.maxR - dist;
402                                float repelAdjustmentAngle = 0f;
403                                if (distIntoRepel < curr.repelAtAngleDist && curr.repelAtAngleDist > 0) {
404                                        float repelMult = (1f - distIntoRepel / curr.repelAtAngleDist);
405                                        repelAdjustmentAngle = 90f * repelMult;
406                                        repelWeight *= (1f - repelMult);
407
408                                        float repelAngle = Misc.getAngleInDegrees(dir);
409                                        float turnDir = Misc.getClosestTurnDirection(dir, vel);
410                                        repelAdjustmentAngle *= turnDir;
411                                        dir = Misc.getUnitVectorAtDegreeAngle(repelAngle + repelAdjustmentAngle);
412                                }
413                                
414                                dir.scale(repelWeight);
415                                Vector2f.add(total, dir, total);
416                        }
417                        
418                        if (curr.maxA > 0 && dist < curr.maxA) {
419                                float attractWeight = curr.attractWeight;
420                                if (dist > curr.minA && curr.maxA > curr.minA) {
421                                        attractWeight = (dist - curr.minA)  / (curr.maxA - curr.minA);
422                                        if (attractWeight > 1f) attractWeight = 1f;
423                                        attractWeight = 1f - attractWeight;
424                                        attractWeight *= curr.attractWeight;
425                                }
426                                
427                                Vector2f dir = Misc.getUnitVector(loc, curr.loc);
428                                dir.scale(attractWeight);
429                                Vector2f.add(total, dir, total);
430                        }
431                        
432                        if (curr.maxC > 0 && dist < curr.maxC) {
433                                float cohesionWeight = curr.cohesionWeight;
434                                if (dist > curr.minC && curr.maxC > curr.minC) {
435                                        cohesionWeight = (dist - curr.minC)  / (curr.maxC - curr.minC);
436                                        if (cohesionWeight > 1f) cohesionWeight = 1f;
437                                        cohesionWeight = 1f - cohesionWeight;
438                                        cohesionWeight *= curr.cohesionWeight;
439                                }
440                                
441                                Vector2f dir = new Vector2f(curr.vel);
442                                Misc.normalise(dir);
443                                dir.scale(cohesionWeight);
444                                Vector2f.add(total, dir, total);
445                        }
446                }
447                
448                if (total.length() <= 0) {
449                        desiredHeading = ship.getFacing();
450                        headingChangeRate = ship.getAngularVelocity() * 0.5f;
451                } else {
452//                      Vector2f currDir = new Vector2f(vel);
453//                      Misc.normalise(currDir);
454//                      currDir.scale(total.length() * 0.25f);
455//                      Vector2f.add(total, currDir, total);
456                        
457                        float prev = desiredHeading;
458                        desiredHeading = Misc.getAngleInDegrees(total);
459                        if (elapsedSincePrevHeadingUpdate > 0) {
460                                headingChangeRate = Misc.getAngleDiff(prev, desiredHeading) / elapsedSincePrevHeadingUpdate;
461                        } else {
462                                headingChangeRate = ship.getAngularVelocity() * 0.5f;
463                        }
464                }
465        }
466        
467        
468        protected void updateFlockingData() {
469                flockingData.clear();
470                
471                CombatEngineAPI engine = Global.getCombatEngine();
472                
473                if (constructionScript != null && constructionScript.getShip() != null) {
474                        return;
475                }
476                
477                
478                int owner = ship.getOriginalOwner();
479                FighterWingAPI wing = ship.getWing();
480                if (wing == null) return;
481                
482                Vector2f loc = ship.getLocation();
483                boolean wingLeader = ship.isWingLeader();
484                
485                float attackRange = wing.getSpec().getAttackRunRange();
486                attackRange *= attackRangeMult;
487                float radius = ship.getCollisionRadius() * 0.5f;
488                
489                RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship);
490                if (swarm != null) { // && (constructionSwarm) {
491                        radius = swarm.params.maxOffset;
492                }
493                
494                SharedSwarmWingData shared = getShared();
495                
496                ShipAPI target = null;
497                float targetDist = Float.MAX_VALUE;
498                
499                for (ShipAPI curr : engine.getShips()) {
500                        if (curr == ship) continue;
501                        if (curr.isFighter() && (!curr.isWingLeader() || curr.getOwner() == owner || !attackSwarm)) continue;
502                        
503                        // just avoid everything - looking for a clear area
504                        if (constructionSwarm) {
505                                float currRadius = curr.getCollisionRadius() * 2f;
506                                FlockingData data = new FlockingData();
507                                data.facing = curr.getFacing();
508                                data.loc = curr.getLocation();
509                                data.vel = curr.getVelocity();
510                                data.attractWeight = 0f;
511                                data.repelWeight = getShipWeight(curr) * 1f;
512                                data.minA = 0f;
513                                data.maxA = 0f;
514                                data.minR = radius + currRadius;
515                                data.maxR = radius + currRadius + Math.min(100f, currRadius * 1f);
516                                data.repelAtAngleDist = (data.maxR - data.minR) * 0.5f;
517                                flockingData.add(data);
518                                continue;
519                        }
520                        
521                        if (curr.isHulk() || curr.getOwner() == 100) continue;
522                        
523                        // return to Fabricator Units, ignore other ships
524                        if (reclamationSwarm) {
525                                if (!ThreatCombatStrategyAI.isFabricator(curr) || curr.getOwner() != owner) continue;
526                                float currRadius = curr.getCollisionRadius() * 0.5f;
527                                FlockingData data = new FlockingData();
528                                data.facing = curr.getFacing();
529                                data.loc = curr.getLocation();
530                                data.vel = curr.getVelocity();
531                                data.attractWeight = getShipWeight(curr) * (1f - ship.getCurrentCR());
532                                data.repelWeight = data.attractWeight * 10f;
533                                data.minA = radius + currRadius;
534                                data.maxA = 1000000f;
535                                data.minR = radius + currRadius;
536                                data.maxR = radius + currRadius + 100f;
537                                data.repelAtAngleDist = (data.maxR - data.minR) * 0.5f;
538                                flockingData.add(data);
539                                continue;
540                        }
541                        
542                        
543                        float currRadius = curr.getCollisionRadius() * 0.5f;
544                        
545                        if (curr.getOwner() != owner && engine.isAwareOf(owner, curr)) {
546                                FlockingData data = new FlockingData();
547                                data.facing = curr.getFacing();
548                                data.loc = curr.getLocation();
549                                data.vel = curr.getVelocity();
550                                data.attractWeight = getShipWeight(curr);
551                                data.repelWeight = data.attractWeight * 10f;
552                                data.minA = attackRange + radius + currRadius;
553                                data.maxA = 1000000f;
554                                data.repelAtAngleDist = Math.min(attackRange * 0.5f, 400f);
555                                data.minR = radius + currRadius;
556                                data.maxR = attackRange + radius + currRadius;
557                                if (curr == shared.target) {
558                                        //boolean inFront = Misc.isInArc(curr.getFacing(), 90f, curr.getLocation(), ship.getLocation());
559                                        float angleDiffFromFront = Misc.getAngleDiff(curr.getFacing(), Misc.getAngleInDegrees(curr.getLocation(), ship.getLocation()));
560                                        float maxDiff = 45f;
561                                        if (angleDiffFromFront < maxDiff) {
562//                                              data.minR *= 2f - angleDiffFromFront / maxDiff;
563//                                              data.maxR *= 2f - angleDiffFromFront / maxDiff;
564                                                data.minR = data.minR + (data.maxR - data.minR) * 0.5f;
565                                                data.minR += 500f * (1f - angleDiffFromFront / maxDiff);
566                                                data.maxR += 500f * (1f - angleDiffFromFront / maxDiff);
567                                                data.repelAtAngleDist += 500f * (1f - angleDiffFromFront / maxDiff);
568                                        }
569                                        data.attractWeight += 200f;
570                                        data.repelWeight += 600f;
571                                }
572                                flockingData.add(data);
573                                
574                                float dist = Misc.getDistance(loc, curr.getLocation());
575                                if (dist < targetDist && dist < MAX_TARGET_RANGE) {
576                                        target = curr;
577                                        targetDist = dist;
578                                }
579                                
580                                // add extra attractor behind the enemy ship to encourage going around and not hanging out in one spot
581                                if ((curr.isDestroyer() || curr.isCruiser() || curr.isCapital()) && curr == shared.target) {
582                                        data = new FlockingData();
583                                        //Vector2f dir = Misc.getUnitVector(ship.getLocation(), curr.getLocation());
584                                        Vector2f dir = Misc.getUnitVectorAtDegreeAngle(curr.getFacing() + 180f);
585                                        dir.scale(curr.getCollisionRadius() * 0.5f + attackRange * attackRangeMult);
586                                        data.facing = curr.getFacing();
587                                        data.loc = Vector2f.add(curr.getLocation(), dir, new Vector2f());
588                                        data.vel = curr.getVelocity();
589                                        data.attractWeight = getShipWeight(curr) * 1f;
590                                        data.minA = attackRange + radius + currRadius;
591                                        data.maxA = 1000000f;
592                                        if (curr == shared.target) {
593                                                data.attractWeight += 200f;
594                                        }
595                                        flockingData.add(data);
596                                }
597                                
598                                
599                        } else if (curr.getOwner() == owner) {
600                                FlockingData data = new FlockingData();
601                                data.facing = curr.getFacing();
602                                data.loc = curr.getLocation();
603                                data.vel = curr.getVelocity();
604                                data.attractWeight = getShipWeight(curr) * 0.1f;
605                                data.repelWeight = data.attractWeight * 50f;
606                                data.minA = attackRange + radius + currRadius;
607                                data.maxA = 1000000f;
608                                data.minR = radius + currRadius;
609                                data.maxR = attackRange * 0.75f + radius + currRadius;
610                                data.repelAtAngleDist = Math.min(attackRange * 0.5f, 400f);
611                                flockingData.add(data);
612                        }
613                }
614                
615                if (target != null) {
616                        flags.setFlag(AIFlags.MANEUVER_TARGET, 3f, target);
617                } else {
618                        flags.unsetFlag(AIFlags.MANEUVER_TARGET);
619                }
620                
621                if (flockingData.isEmpty() && !constructionSwarm) {
622                        FlockingData data = new FlockingData();
623                        data.facing = 0f;
624                        data.loc = new Vector2f();
625                        data.vel = new Vector2f();
626                        data.attractWeight = 5f;
627                        data.repelWeight = data.attractWeight * 10f;
628                        data.minA = 1000f;
629                        data.maxA = 1000000f;
630                        data.minR = 1000f;
631                        data.maxR = 3000f;
632                        data.repelAtAngleDist = 1000f;
633                        flockingData.add(data);
634                }
635                
636//              if (true) {
637//                      FlockingData data = new FlockingData();
638//                      data.facing = 0f;
639//                      data.loc = new Vector2f(8000f, -18000f);
640//                      data.vel = new Vector2f();
641//                      data.attractWeight = 1000f;
642//                      data.minA = 1000f;
643//                      data.maxA = 1000000f;
644//                      flockingData.add(data);
645//              }
646                
647                //RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship);
648                if (swarm != null && swarm.params.flockingClass != null && swarm.attachedTo != null) {
649                        for (RoilingSwarmEffect curr : RoilingSwarmEffect.getFlockingMap().getList(swarm.params.flockingClass)) {
650                                if (curr == swarm) continue;
651                                if (curr.attachedTo == ship || curr.attachedTo == null ||
652                                                curr.attachedTo.getOwner() != owner) {
653                                        continue;
654                                }
655                                
656                                if (swarm.params.flockingClass.equals(curr.params.flockingClass)) {
657                                        // avoid other construction swarms - looking for a clear area
658                                        if (constructionSwarm) {
659                                                float currRadius = curr.params.maxOffset;
660                                                FlockingData data = new FlockingData();
661                                                data.facing = curr.attachedTo.getFacing();
662                                                data.loc = curr.attachedTo.getLocation();
663                                                data.vel = curr.attachedTo.getVelocity();
664                                                data.attractWeight = 0f;
665                                                data.repelWeight = 8f;
666                                                data.minA = 0f;
667                                                data.maxA = 0f;
668                                                data.minR = radius + currRadius;
669                                                data.maxR = radius + currRadius + Math.min(100f, currRadius * 1f);
670                                                data.repelAtAngleDist = (data.maxR - data.minR) * 0.5f;
671                                                flockingData.add(data);
672                                                continue;
673                                        }
674                                        
675                                        
676                                        boolean sameWing = wing == ((ShipAPI)curr.attachedTo).getWing();
677                                        boolean otherWingLeader = ((ShipAPI)curr.attachedTo).isWingLeader();
678                                        
679                                        // actually - make the leader wait a bit, otherwise they never catch up
680                                        // or not
681                                        if (wingLeader && sameWing) continue; // others catch up/line up on leader
682                                        
683                                        if (!sameWing) {
684                                                float dist = Misc.getDistance(loc, curr.attachedTo.getLocation());
685                                                if (dist > ATTRACTOR_RANGE_MAX + 500f) continue;
686                                        }
687                                        
688                                        
689                                        float currRadius = curr.attachedTo.getCollisionRadius() * 0.5f;
690                                        FlockingData data = new FlockingData();
691                                        data.facing = curr.attachedTo.getFacing();
692                                        data.loc = curr.attachedTo.getLocation();
693                                        data.vel = curr.attachedTo.getVelocity();
694                                        data.attractWeight = 1f;
695                                        data.repelWeight = 10f;
696                                        data.cohesionWeight = 1f;
697                                        if (sameWing) {
698                                                if (wingLeader) {
699                                                        data.attractWeight = 0.1f;
700                                                } else {
701                                                        data.attractWeight = 3f;
702                                                }
703                                                data.minA = 0f + radius + currRadius;
704                                                data.maxA = ATTRACTOR_RANGE_MAX_SAME_WING + radius + currRadius;
705                                        } else {
706                                                data.minA = 0f + radius + currRadius;
707                                                data.maxA = ATTRACTOR_RANGE_MAX + radius + currRadius;
708                                        }
709                                        data.minR = REPEL_RANGE_MIN + radius + currRadius;
710                                        data.maxR = REPEL_RANGE_MAX + radius + currRadius;
711                                        if (wingLeader && otherWingLeader) {
712                                                data.maxR = ATTRACTOR_RANGE_MAX + radius + currRadius;
713                                        }
714                                        data.minC = COHESION_RANGE_MIN + radius + currRadius;
715                                        data.maxC = COHESION_RANGE_MAX + radius + currRadius;
716                                        if (reclamationSwarm) {
717                                                data.minR *= 0.33f;
718                                                data.maxR *= 0.33f;
719                                        }
720                                        flockingData.add(data);
721                                }
722                        }
723                }
724        }
725        
726        
727        
728        
729        
730        
731        
732        @Override
733        public ShipwideAIFlags getAIFlags() {
734                return flags;
735        }
736        
737        public static float getShipWeight(ShipAPI ship) {
738                return getShipWeight(ship, true);
739        }
740        public static float getShipWeight(ShipAPI ship, boolean adjustForNonCombat) {
741                boolean nonCombat = ship.isNonCombat(false);
742                float weight = 0;
743                switch (ship.getHullSize()) {
744                case CAPITAL_SHIP: weight += 8; break;
745                case CRUISER: weight += 4; break;
746                case DESTROYER: weight += 2; break;
747                case FRIGATE: weight += 1; break;
748                case FIGHTER: weight += 1; break;
749                }
750                if (nonCombat && adjustForNonCombat) weight *= 0.25f;
751                if (ship.isDrone()) weight *= 0.1f;
752                return weight;
753        }
754
755        public void setDoNotFireDelay(float amount) {}
756        public void forceCircumstanceEvaluation() {}
757        public boolean needsRefit() { return false; }
758        public void cancelCurrentManeuver() {}
759        public ShipAIConfig getConfig() { return null; }
760}
761
762
763
764
765
766
767
768
769
770
771
772
773