001package com.fs.starfarer.api.impl.combat.dweller;
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.CollisionClass;
011import com.fs.starfarer.api.combat.CombatEngineAPI;
012import com.fs.starfarer.api.combat.DamageType;
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.WeaponGroupAPI;
019import com.fs.starfarer.api.util.IntervalUtil;
020import com.fs.starfarer.api.util.Misc;
021import com.fs.starfarer.api.util.WeightedRandomPicker;
022
023public class ShroudedVortexAI implements ShipAIPlugin {
024
025        public static class FlockingData {
026                public Vector2f loc;
027                public Vector2f vel;
028                public float minA;
029                public float maxA;
030                public float minR;
031                public float maxR;
032                public float repelAtAngleDist;
033                public float minC;
034                public float maxC;
035                public float attractWeight;
036                public float repelWeight;
037                public float cohesionWeight;
038                public float facing;
039        }       
040        
041        
042        /**
043         * Loses hitpoints over time, when near zero, blows up.
044         */
045        public static float HULL_FRACTION_LOST_PER_SECOND = 0.05f;
046        
047        public static String VORTEX_FLOCKING = "vortex_flocking";
048        public static float ATTRACTOR_RANGE_MAX = 2000f;
049        public static float COHESION_RANGE_MIN = 150f;
050        public static float COHESION_RANGE_MAX = 300f;
051        public static float REPEL_RANGE_MIN = 0f;
052        public static float REPEL_RANGE_MAX = 400f;
053        
054        protected ShipwideAIFlags flags = new ShipwideAIFlags();
055        protected ShipAPI ship;
056        protected boolean exploded = false;
057        protected Vector2f prevVel = null;
058        
059        protected IntervalUtil updateInterval = new IntervalUtil(0.5f, 1.5f);
060        protected IntervalUtil headingInterval = new IntervalUtil(0.5f, 1.5f);
061        
062        protected ShipAPI target = null;
063        protected float timeOnTarget = 0f;
064        protected float numCollisions = 0f;
065        
066        protected List<FlockingData> flockingData = new ArrayList<>();
067        protected float desiredHeading = 0f;
068        
069        public ShroudedVortexAI(ShipAPI ship) {
070                this.ship = ship;
071                
072                doInitialSetup();
073                
074                updateInterval.forceIntervalElapsed();
075        }
076        
077        protected void doInitialSetup() {
078                ship.addTag(VORTEX_FLOCKING);
079        }
080        
081        protected void toggleOn(int groupNum) {
082                List<WeaponGroupAPI> groups = ship.getWeaponGroupsCopy();
083                if (groups.size() <= groupNum) return;
084                groups.get(groupNum).toggleOn();
085        }
086        protected void toggleOff(int groupNum) {
087                List<WeaponGroupAPI> groups = ship.getWeaponGroupsCopy();
088                if (groups.size() <= groupNum) return;
089                groups.get(groupNum).toggleOff();
090        }
091        
092        
093        @Override
094        public void advance(float amount) {
095                //if (true) return;
096                
097                if (target != null) {
098                        timeOnTarget += amount;
099                }
100                
101                updateInterval.advance(amount);
102                if (updateInterval.intervalElapsed()) {
103                        ShipAPI prev = target;
104                        target = findTarget();
105                        if (prev != target) {
106                                timeOnTarget = 0f;
107                        }
108                        
109                        updateFlockingData();
110                }
111                
112                headingInterval.advance(amount * 5f);
113                if (headingInterval.intervalElapsed()) {
114                        computeDesiredHeading();
115                }
116                
117//              String id = getClass().getSimpleName();
118//              if (timeOnTarget > 3f) {
119//                      ship.getMutableStats().getAcceleration().modifyMult(id, 0.24f);
120//                      ship.getMutableStats().getDeceleration().modifyMult(id, 0.24f);
121//              } else {
122//                      ship.getMutableStats().getAcceleration().unmodifyMult(id);
123//                      ship.getMutableStats().getDeceleration().unmodifyMult(id);
124//              }
125                
126                if (prevVel != null) {
127                        float delta = Vector2f.sub(prevVel, ship.getVelocity(), new Vector2f()).length();
128                        // likely collision, stop the ship to make it feel heavier
129                        if (delta > ship.getMaxSpeedWithoutBoost() * 0.25f) {
130                                ship.getVelocity().scale(0.1f);
131                                numCollisions++;
132                        }
133                }
134                prevVel = new Vector2f(ship.getVelocity()); 
135
136                
137                CombatEngineAPI engine = Global.getCombatEngine();
138                
139                DwellerShroud shroud = DwellerShroud.getShroudFor(ship);
140                if (shroud != null) {
141                        ShipAPI sourceShip = (ShipAPI) shroud.custom1;
142                        if (sourceShip != null) {
143                                float dist = Misc.getDistance(ship.getLocation(), sourceShip.getLocation());
144                                if (dist > (ship.getCollisionRadius() + sourceShip.getCollisionRadius() * 0.75f)) {
145                                        ship.setCollisionClass(CollisionClass.SHIP);
146                                } else {
147                                        ship.setCollisionClass(CollisionClass.FIGHTER);
148                                }
149                        }
150                }
151                
152                
153                float damage = ship.getMaxHitpoints() * HULL_FRACTION_LOST_PER_SECOND * (1f + numCollisions) * amount;
154                ship.setHitpoints(ship.getHitpoints() - damage);
155                if (ship.getHitpoints() <= 0f) {
156                        // like other damage, this will trigger the explosion visuals/damage in ShroudedVortexShipCreator
157                        engine.applyDamage(ship, ship.getLocation(), 10000f, DamageType.ENERGY, 0f, true, false, ship, false);
158                }
159                
160                giveMovementCommands();
161        }
162        
163        protected void giveMovementCommands() {
164                CombatEngineAPI engine = Global.getCombatEngine();
165                
166                //ship.giveCommand(ShipCommand.DECELERATE, null, 0);
167                if (ship.hasTag(ShroudedVortexShipCreator.TAG_MIRRORED_VORTEX)) {
168                        ship.giveCommand(ShipCommand.TURN_RIGHT, null, 0);
169                } else {
170                        ship.giveCommand(ShipCommand.TURN_LEFT, null, 0);
171                }
172
173                float heading = Misc.getAngleInDegrees(ship.getVelocity());
174                if (target != null) {
175                        float speed = (ship.getVelocity().length() + ship.getMaxSpeed()) * 0.5f;
176                        Vector2f point = engine.getAimPointWithLeadForAutofire(ship, 1f, target, speed);
177                        heading = Misc.getAngleInDegrees(ship.getLocation(), point);
178                }
179                
180                //engine.headInDirectionWithoutTurning(ship, heading, 10000);
181                
182                engine.headInDirectionWithoutTurning(ship, desiredHeading, 10000);
183        }
184        
185        
186        @Override
187        public ShipwideAIFlags getAIFlags() {
188                return flags;
189        }
190        
191        
192        public ShipAPI findTarget() {
193                float range = 5000f;
194                float goodRange = ship.getHullLevel() / HULL_FRACTION_LOST_PER_SECOND * ship.getMaxSpeed() * 0.75f;
195                Vector2f from = ship.getLocation();
196                
197                CombatEngineAPI engine = Global.getCombatEngine();
198                Iterator<Object> iter = engine.getShipGrid().getCheckIterator(from,
199                                                                                                                                                        range * 2f, range * 2f);
200                int owner = ship.getOwner();
201//              ShipAPI best = null;
202//              float maxScore = -100000f;
203                
204                float currAngle = Misc.getAngleInDegrees(ship.getVelocity());
205                
206                WeightedRandomPicker<ShipAPI> good = new WeightedRandomPicker<>();
207                WeightedRandomPicker<ShipAPI> lessGood = new WeightedRandomPicker<>();
208                
209                while (iter.hasNext()) {
210                        Object o = iter.next();
211                        if (!(o instanceof ShipAPI)) continue;
212                        
213                        ShipAPI other = (ShipAPI) o;
214                        if (other == ship) continue;
215                        if (other.getOwner() == owner) continue;
216                        if (other.isHulk()) continue;
217                        if (other.isPhased()) continue;
218                        if (!engine.isAwareOf(owner, other))
219                        
220                        if (other.getCollisionClass() == CollisionClass.NONE) continue;
221                        
222
223                        float dist = Misc.getDistance(from, other.getLocation());
224                        if (dist > range) continue;
225                        
226                        float score = 1f;
227                        if (other.isFrigate()) {
228                                score = 11f;
229                        } else if (other.isDestroyer()) {
230                                score = 15f;
231                        } else if (other.isCruiser() || other.isCapital()) {
232                                score = 25f;
233                        }
234                        
235                        
236                        float angleToOther = Misc.getAngleInDegrees(ship.getLocation(), other.getLocation());
237                        float angleDiff = Misc.getAngleDiff(currAngle, angleToOther);
238                        
239                        float f = angleDiff / 90f;
240                        if (f > 1f) f = 1f; 
241                        
242                        float minus = 5f * dist / 5000f;
243                        if (minus > 5f) minus = 3f;
244                        
245                        score -= minus;
246                        
247                        score -= f * 5f;
248                        
249                        if (dist > goodRange) {
250                                lessGood.add(other, score);
251                        } else {
252                                good.add(other, score);
253                        }
254//                      if (dist > goodRange) {
255//                              score -= 100f;
256//                      }
257//                      
258//                      if (score > maxScore) {
259//                              maxScore = score;
260//                              best = other;
261//                      }
262                }
263                
264                if (target != null) {
265                        if (good.getItems().contains(target)) return target;
266                        if (lessGood.getItems().contains(target) && good.isEmpty()) return target;
267                }
268                
269                if (!good.isEmpty()) {
270                        return good.pick();
271                }
272                
273                return lessGood.pick();
274        }
275        
276        
277        
278        
279        protected void updateFlockingData() {
280                flockingData.clear();
281                
282                CombatEngineAPI engine = Global.getCombatEngine();
283                
284                int owner = ship.getOriginalOwner();
285                Vector2f loc = ship.getLocation();
286                float radius = ship.getCollisionRadius() * 1f;
287                
288                if (target != null) {
289                        float dist = Misc.getDistance(ship.getLocation(), target.getLocation());
290                        FlockingData data = new FlockingData();
291                        data.facing = target.getFacing();
292                        data.loc = target.getLocation();
293                        data.vel = target.getVelocity();
294                        data.attractWeight = 100f;
295                        if (dist - ship.getCollisionRadius() - target.getCollisionRadius() < 500f) {
296                                data.attractWeight *= 10f;
297                        }
298                        data.repelWeight = 0f;
299                        data.minA = 0f;
300                        data.maxA = 1000000f;
301                        data.minR = 0f;
302                        data.maxR = 0f;
303                        data.repelAtAngleDist = 0f;
304                        flockingData.add(data);
305                }
306                
307                for (ShipAPI curr : engine.getShips()) {
308                        if (curr == ship) continue;
309                        if (curr.getOwner() != owner) continue;
310                        if (curr.isHulk() || curr.getOwner() == 100) continue;
311                        
312                        float currRadius = curr.getCollisionRadius();
313                
314                        if (curr.hasTag(VORTEX_FLOCKING)) {
315                                FlockingData data = new FlockingData();
316                                data.facing = Misc.getAngleInDegrees(curr.getVelocity());
317                                data.loc = curr.getLocation();
318                                data.vel = curr.getVelocity();
319                                data.repelWeight = 100f;
320                                data.cohesionWeight = 1f;
321                                data.attractWeight = 3f;
322                                
323                                data.minA = 0f + radius + currRadius;
324                                data.maxA = ATTRACTOR_RANGE_MAX + radius + currRadius;
325                                
326                                data.minR = REPEL_RANGE_MIN + radius + currRadius;
327                                data.maxR = REPEL_RANGE_MAX + radius + currRadius;
328                                
329                                data.minC = COHESION_RANGE_MIN + radius + currRadius;
330                                data.maxC = COHESION_RANGE_MAX + radius + currRadius;
331        
332                                flockingData.add(data);
333                        } else if (!curr.isFighter()) {
334                                FlockingData data = new FlockingData();
335                                data.facing = Misc.getAngleInDegrees(curr.getVelocity());
336                                data.loc = curr.getLocation();
337                                data.vel = curr.getVelocity();
338                                data.attractWeight = 0f;
339                                data.cohesionWeight = 0f;
340                                data.repelWeight = 100f;
341                                
342                                data.minA = 0f;
343                                data.maxA = 0f;
344                                
345                                data.minR = REPEL_RANGE_MIN * 0.5f + radius + currRadius;
346                                data.maxR = REPEL_RANGE_MAX * 0.5f + radius + currRadius;
347                                
348                                data.minC = 0f;
349                                data.maxC = 0f;
350        
351                                flockingData.add(data);
352                        }
353                }
354        }       
355        
356        protected void computeDesiredHeading() {
357                
358                Vector2f loc = ship.getLocation();
359                Vector2f vel = ship.getVelocity();
360                float facing = ship.getFacing();
361                
362                Vector2f total = new Vector2f();
363                
364                for (FlockingData curr : flockingData) {
365                        float dist = Misc.getDistance(curr.loc, loc);
366                        if (curr.maxR > 0 && dist < curr.maxR) {
367                                float repelWeight = curr.repelWeight;
368                                if (dist > curr.minR && curr.maxR > curr.minR) {
369                                        repelWeight = (dist - curr.minR)  / (curr.maxR - curr.minR);
370                                        if (repelWeight > 1f) repelWeight = 1f;
371                                        repelWeight = 1f - repelWeight;
372                                        repelWeight *= curr.repelWeight;
373                                }
374                                
375                                Vector2f dir = Misc.getUnitVector(curr.loc, loc);
376                                
377                                float distIntoRepel = curr.maxR - dist;
378                                float repelAdjustmentAngle = 0f;
379                                if (distIntoRepel < curr.repelAtAngleDist && curr.repelAtAngleDist > 0) {
380                                        float repelMult = (1f - distIntoRepel / curr.repelAtAngleDist);
381                                        repelAdjustmentAngle = 90f * repelMult;
382                                        repelWeight *= (1f - repelMult);
383
384                                        float repelAngle = Misc.getAngleInDegrees(dir);
385                                        float turnDir = Misc.getClosestTurnDirection(dir, vel);
386                                        repelAdjustmentAngle *= turnDir;
387                                        dir = Misc.getUnitVectorAtDegreeAngle(repelAngle + repelAdjustmentAngle);
388                                }
389                                
390                                dir.scale(repelWeight);
391                                Vector2f.add(total, dir, total);
392                        }
393                        
394                        if (curr.maxA > 0 && dist < curr.maxA) {
395                                float attractWeight = curr.attractWeight;
396                                if (dist > curr.minA && curr.maxA > curr.minA) {
397                                        attractWeight = (dist - curr.minA)  / (curr.maxA - curr.minA);
398                                        if (attractWeight > 1f) attractWeight = 1f;
399                                        attractWeight = 1f - attractWeight;
400                                        attractWeight *= curr.attractWeight;
401                                }
402                                
403                                Vector2f dir = Misc.getUnitVector(loc, curr.loc);
404                                dir.scale(attractWeight);
405                                Vector2f.add(total, dir, total);
406                        }
407                        
408                        if (curr.maxC > 0 && dist < curr.maxC) {
409                                float cohesionWeight = curr.cohesionWeight;
410                                if (dist > curr.minC && curr.maxC > curr.minC) {
411                                        cohesionWeight = (dist - curr.minC)  / (curr.maxC - curr.minC);
412                                        if (cohesionWeight > 1f) cohesionWeight = 1f;
413                                        cohesionWeight = 1f - cohesionWeight;
414                                        cohesionWeight *= curr.cohesionWeight;
415                                }
416                                
417                                Vector2f dir = new Vector2f(curr.vel);
418                                Misc.normalise(dir);
419                                dir.scale(cohesionWeight);
420                                Vector2f.add(total, dir, total);
421                        }
422                }
423                
424                if (total.length() <= 0) {
425                        desiredHeading = ship.getFacing();
426                } else {
427                        desiredHeading = Misc.getAngleInDegrees(total);
428                }
429        }
430        
431        
432        public void setDoNotFireDelay(float amount) {}
433        public void forceCircumstanceEvaluation() {}
434        public boolean needsRefit() { return false; }
435        public void cancelCurrentManeuver() {}
436        public ShipAIConfig getConfig() { return null; }
437}
438
439
440
441
442
443
444
445
446
447
448
449
450