001package com.fs.starfarer.api.impl.combat.threat;
002
003import java.util.ArrayList;
004import java.util.LinkedHashSet;
005import java.util.List;
006import java.util.Set;
007
008import org.lwjgl.util.vector.Vector2f;
009
010import com.fs.starfarer.api.Global;
011import com.fs.starfarer.api.combat.AssignmentTargetAPI;
012import com.fs.starfarer.api.combat.BattleObjectiveAPI;
013import com.fs.starfarer.api.combat.CombatAssignmentType;
014import com.fs.starfarer.api.combat.CombatEngineAPI;
015import com.fs.starfarer.api.combat.CombatFleetManagerAPI;
016import com.fs.starfarer.api.combat.CombatFleetManagerAPI.AssignmentInfo;
017import com.fs.starfarer.api.combat.CombatTaskManagerAPI;
018import com.fs.starfarer.api.combat.DeployedFleetMemberAPI;
019import com.fs.starfarer.api.combat.ShipAIPlugin;
020import com.fs.starfarer.api.combat.ShipAPI;
021import com.fs.starfarer.api.combat.ShipwideAIFlags.AIFlags;
022import com.fs.starfarer.api.fleet.FleetGoal;
023import com.fs.starfarer.api.impl.campaign.ids.HullMods;
024import com.fs.starfarer.api.impl.campaign.ids.Tags;
025import com.fs.starfarer.api.util.IntervalUtil;
026import com.fs.starfarer.api.util.Misc;
027
028/**
029 * Doesn't care about command points etc, just functions in a different way. May use command points/tasks/etc
030 * to accomplish its goals, just as an implementation detail, but conceptually it's fundamentally different from how
031 * human-type fleets work.
032 * 
033 * @author Alex
034 *
035 */
036public class ThreatCombatStrategyAI {
037        
038        public static float SND_BASE = 60f;
039        public static float SND_TIMER = 60f;
040        public static float SND_FRACTION = 0.5f;
041        
042        protected boolean playerSide;
043        protected CombatTaskManagerAPI taskManager;
044        protected CombatFleetManagerAPI fleetManager;
045        protected CombatFleetManagerAPI enemyFleetManager;
046        protected int owner;
047        protected boolean allyMode = false;
048        
049        protected IntervalUtil everySecond = new IntervalUtil(0.8f, 1.2f);
050        protected CombatEngineAPI engine;
051        protected float mw, mh;
052        
053        protected boolean abort = false;
054        
055        protected AssignmentInfo mainDefend1;
056        protected AssignmentInfo mainDefend2;
057        
058        protected float captureAllTimeRemaining; 
059        protected boolean gaveInitialOrders = false;
060        
061        protected float untilSNDOnSkirmishUnits; 
062        
063        
064        public ThreatCombatStrategyAI(int owner) {
065                engine = Global.getCombatEngine();
066                this.owner = owner;
067                playerSide = owner == 0;
068                allyMode = playerSide;
069                //allyMode = false;
070                fleetManager = engine.getFleetManager(owner);
071                taskManager = fleetManager.getTaskManager(allyMode);
072                taskManager.getCommandPointsStat().modifyFlat("ThreatCombatStrategyAI", 1000000000);
073                
074                enemyFleetManager = engine.getFleetManager(owner == 0 ? 1 : 0);
075                
076                if (fleetManager.getGoal() == FleetGoal.ESCAPE || enemyFleetManager.getGoal() == FleetGoal.ESCAPE) {
077                        abort = true;
078                } else {
079                        if (fleetManager.getAdmiralAI() != null) {
080                                taskManager.clearTasks();
081                                fleetManager.getAdmiralAI().setNoOrders(true);
082                        }
083                }
084                
085                mw = engine.getMapWidth();
086                mh = engine.getMapHeight();
087                
088                resetSNDTimer();
089        }
090        
091        protected void resetSNDTimer() {
092                untilSNDOnSkirmishUnits = SND_TIMER * (0.75f + (float) Math.random() * 0.5f); 
093                untilSNDOnSkirmishUnits += SND_BASE; 
094        }
095        
096        protected void manageSND(float amount) {
097                untilSNDOnSkirmishUnits -= amount;
098                if (captureAllTimeRemaining > 0) return;
099                if (untilSNDOnSkirmishUnits <= 0) {
100                        for (DeployedFleetMemberAPI member : fleetManager.getDeployedCopyDFM()) {
101                                ShipAPI ship = member.getShip();
102                                if (ship == null || ship.getAI() == null) continue;
103                                if (ship.hasTag(ThreatShipConstructionScript.SHIP_UNDER_CONSTRUCTION)) continue;
104                                
105                                if (!ship.getHullSpec().hasTag(Tags.THREAT_SKIRMISH)) continue;
106                                if ((float) Math.random() > SND_FRACTION) continue;
107                                
108                                cancelOrders(member, false);
109                                ship.getAIFlags().setFlag(AIFlags.IGNORES_ORDERS, SND_BASE * (0.75f + (float) Math.random() * 0.5f));
110                        }
111                        resetSNDTimer();
112                }
113        }
114        
115        protected void giveInitialOrders() {
116                captureAllTimeRemaining = 80f;
117                for (BattleObjectiveAPI curr : engine.getObjectives()) {
118                        taskManager.createAssignment(CombatAssignmentType.CAPTURE, curr, false);
119                }               
120        }
121        
122        
123        public void advance(float amount) {
124                //if (true) return;
125                if (abort) return;
126                if (engine.isPaused()) return;
127                
128                captureAllTimeRemaining -= amount;
129                
130                manageSND(amount);
131                
132                everySecond.advance(amount);
133                if (everySecond.intervalElapsed()) {
134                        // if non-threat ships are deployed from this fleetManager, don't want to be doing any Threat things
135                        List<DeployedFleetMemberAPI> deployed = fleetManager.getDeployedCopyDFM();
136                        if (deployed.isEmpty()) return;
137                        
138                        boolean someMatching = false;
139                        for (DeployedFleetMemberAPI member : deployed) {
140                                if (!member.isFighterWing() && member.getShip() != null &&
141                                                member.isAlly() == allyMode &&
142                                                !member.getShip().getVariant().hasHullMod(HullMods.THREAT_HULLMOD)) {
143                                        abort = true;
144                                        return;
145                                } else if (!member.isFighterWing() && member.getShip() != null &&
146                                                member.isAlly() == allyMode) {
147                                        someMatching = true;
148                                }
149                        }
150                        
151                        if (!someMatching) return;
152                        
153                        if (!gaveInitialOrders) {
154                                giveInitialOrders();
155                                gaveInitialOrders = true;
156                        }
157                        
158                        float sign = 1f;
159                        if (owner == 0) sign = -1;
160                        Vector2f enemyCom = getEnemyCenterOfMass();
161                        Vector2f fabricatorLoc = new Vector2f(0, 0 + mh * 0.33f * sign);
162                        Vector2f axis = Misc.getUnitVector(fabricatorLoc, enemyCom);
163                        Vector2f perp = new Vector2f(axis.y, -axis.x);
164                        float distToEnemyCom = Misc.getDistance(fabricatorLoc, enemyCom);
165                        
166                        //float hiveOffset = 2000f;
167                        float hiveOffset = distToEnemyCom - 6000f;
168                        if (Math.abs(hiveOffset) < 2000f) {
169                                hiveOffset = Math.signum(hiveOffset) * 2000f;
170                        }
171                        if (hiveOffset > 2000) hiveOffset= 2000f;
172                        Vector2f hiveLoc = new Vector2f(axis);
173                        hiveLoc.scale(hiveOffset);
174                        Vector2f.add(hiveLoc, fabricatorLoc, hiveLoc);
175
176                        float enemyWeightNearFabricatorLoc = Misc.countEnemyWeightInArcAroundLocation(
177                                        owner, fabricatorLoc, 0f, 360f, 3000f, null, true, true);
178                        
179                        int fabricators = 0;
180                        int hives = 0;
181                        for (DeployedFleetMemberAPI member : fleetManager.getDeployedCopyDFM()) {
182                                ShipAPI ship = member.getShip();
183                                if (ship == null || ship.getAI() == null) continue;
184                                if (isFabricator(ship)) {
185                                        fabricators++;
186                                }
187                                if (isHive(ship)) {
188                                        hives++;
189                                }
190                        }
191                        
192                        float defDist = distToEnemyCom - 2000f;
193                        if (enemyWeightNearFabricatorLoc >= 3f) {
194                                defDist = Math.min(defDist, 3000f);
195                        }
196                        if (fabricators == 0 && hives == 0) {
197                                if (mainDefend1 != null) {
198                                        taskManager.removeAssignment(mainDefend1);
199                                        mainDefend1 = null;
200                                }
201                                if (mainDefend2 != null) {
202                                        taskManager.removeAssignment(mainDefend2);
203                                        mainDefend2 = null;
204                                }
205                        } else if (defDist < 2000f) {
206                                if (mainDefend1 != null) {
207                                        float dist = Misc.getDistance(mainDefend1.getTarget().getLocation(), fabricatorLoc);
208                                        if (dist > 1000f) {
209                                                taskManager.removeAssignment(mainDefend1);
210                                                mainDefend1 = null;
211                                        }
212                                }
213
214                                if (mainDefend2 != null) {
215                                        taskManager.removeAssignment(mainDefend2);
216                                        mainDefend2 = null;
217                                }
218                                
219                                if (mainDefend1 == null) {
220                                        AssignmentTargetAPI wp = taskManager.createWaypoint2(fabricatorLoc, allyMode);
221                                        mainDefend1 = taskManager.createAssignment(CombatAssignmentType.DEFEND, wp, false);
222                                }
223                                
224                                //Global.getCombatEngine().getCombatUI().addMessage(0, "Threat aggro mode engaged!");
225                                // enemies close to fabricators - attack! 
226                                for (DeployedFleetMemberAPI member : fleetManager.getDeployedCopyDFM()) {
227                                        ShipAPI ship = member.getShip();
228                                        if (ship == null || ship.getAI() == null) continue;
229                                        if (isCombatUnit(ship) && ship.getAI() instanceof ShipAIPlugin) {
230//                                              ShipAIPlugin ai = (ShipAIPlugin) ship.getAI();
231//                                              ShipAIConfig config = ai.getConfig();
232//                                              config.personalityOverride = Personalities.RECKLESS;
233//                                              config.alwaysStrafeOffensively = true;
234//                                              config.backingOffWhileNotVentingAllowed = false;
235//                                              config.turnToFaceWithUndamagedArmor = false;
236//                                              config.burnDriveIgnoreEnemies = true;
237                                                ship.getAIFlags().setFlag(AIFlags.DO_NOT_BACK_OFF, 2f);
238                                                ship.getAIFlags().setFlag(AIFlags.DO_NOT_VENT, 2f);
239                                                ship.getAIFlags().setFlag(AIFlags.IGNORES_ORDERS, 2f);
240                                        }
241                                }
242                        } else {
243                                Vector2f defLoc = new Vector2f(axis);
244                                defLoc.scale(defDist);
245                                Vector2f.add(defLoc, fabricatorLoc, defLoc);
246                                
247                                Vector2f defLoc1 = new Vector2f(perp);
248                                defLoc1.scale(1000f);
249                                Vector2f.add(defLoc1, defLoc, defLoc1);
250                                
251                                Vector2f defLoc2 = new Vector2f(perp);
252                                defLoc2.scale(-1000f);
253                                Vector2f.add(defLoc2, defLoc, defLoc2);
254                                
255                                
256                                if (mainDefend1 != null) {
257                                        float dist = Misc.getDistance(mainDefend1.getTarget().getLocation(), defLoc1);
258                                        if (dist > 1000f) {
259                                                taskManager.removeAssignment(mainDefend1);
260                                                mainDefend1 = null;
261                                        }
262                                }
263                                if (mainDefend2 != null) {
264                                        float dist = Misc.getDistance(mainDefend2.getTarget().getLocation(), defLoc2);
265                                        if (dist > 1000f) {
266                                                taskManager.removeAssignment(mainDefend2);
267                                                mainDefend2 = null;
268                                        }
269                                }
270                                
271                                if (mainDefend1 == null) {
272                                        AssignmentTargetAPI wp = taskManager.createWaypoint2(defLoc1, allyMode);
273                                        mainDefend1 = taskManager.createAssignment(CombatAssignmentType.DEFEND, wp, false);
274                                }
275                                
276                                if (mainDefend2 == null) {
277                                        AssignmentTargetAPI wp = taskManager.createWaypoint2(defLoc2, allyMode);
278                                        mainDefend2 = taskManager.createAssignment(CombatAssignmentType.DEFEND, wp, false);
279                                }
280                        }
281                        
282                        if (captureAllTimeRemaining <= 0f) {
283                                float axisAngle = Misc.getAngleInDegrees(axis);
284                                List<AssignmentTargetAPI> withCaptures = new ArrayList<>();
285                                for (AssignmentInfo info : taskManager.getAllAssignments()) {
286                                        if (info.getTarget() == null) continue;
287                                        if (info.getType() == CombatAssignmentType.CAPTURE || info.getType() == CombatAssignmentType.CONTROL) {
288                                                if ((fabricators == 0 && hives == 0) ||
289                                                                !wantsToControl(fabricatorLoc, axisAngle, distToEnemyCom, info.getTarget().getLocation())) {
290                                                        taskManager.removeAssignment(info);
291                                                } else {
292                                                        withCaptures.add(info.getTarget());
293                                                }
294                                        }
295                                }
296                                
297                                if (fabricators > 0 || hives > 0) {
298                                        for (BattleObjectiveAPI curr : engine.getObjectives()) {
299                                                if (withCaptures.contains(curr)) continue;
300                                                if (wantsToControl(fabricatorLoc, axisAngle, distToEnemyCom, curr.getLocation())) {
301                                                        taskManager.createAssignment(CombatAssignmentType.CAPTURE, curr, false);
302                                                }
303                                        }
304                                }
305                        }
306                        
307                        Set<DeployedFleetMemberAPI> escorted = new LinkedHashSet<>();
308                        
309                        for (DeployedFleetMemberAPI member : fleetManager.getDeployedCopyDFM()) {
310                                ShipAPI ship = member.getShip();
311                                if (ship == null || ship.getAI() == null) continue;
312                                if (ship.hasTag(ThreatShipConstructionScript.SHIP_UNDER_CONSTRUCTION)) continue;
313                                
314                                float enemyCheckRange = 3000f;
315                                if (isHive(ship)) {
316                                        enemyCheckRange = 1000f;
317                                }
318                                
319                                float enemyWeight = Misc.countEnemyWeightInArcAroundLocation(owner, ship.getLocation(), 0f, 360f, enemyCheckRange, null, true, true);
320                                float shipWeight = Misc.getShipWeight(ship, true);
321                                
322                                boolean enemiesNear = enemyWeight >= shipWeight * 0.5f;
323                                
324                                if (isFabricator(ship)) {
325                                        float min = 0f;
326                                        float max = 1000f;
327                                        if (enemiesNear) {
328                                                min = 1000f;
329                                                max = 2000f;
330                                        }
331                                        giveMovementOrder(member, fabricatorLoc, min, max);
332                                } else if (isHive(ship)) {
333                                        if (enemiesNear) {
334                                                cancelOrders(member, true);
335                                        } else {
336                                                giveMovementOrder(member, hiveLoc, 1000f, 1500f);
337                                        }
338                                } else if (isOverseer(ship)) {
339                                        DeployedFleetMemberAPI closest = null;
340                                        float minDist = Float.MAX_VALUE;
341                                        for (DeployedFleetMemberAPI other : fleetManager.getDeployedCopyDFM()) {
342                                                if (other == member || other.getShip() == null || escorted.contains(other)) continue;
343                                                
344                                                float extraDistScore = 0f;
345                                                if (other.getShip().isCruiser() && isCombatUnit(other.getShip())) {
346                                                        extraDistScore = 0f;
347                                                } else if (other.getShip().isDestroyer() && isCombatUnit(other.getShip())) {
348                                                        extraDistScore = 100000f;
349                                                } else if (other.getShip().isFrigate() && isCombatUnit(other.getShip())) {
350                                                        if (enemiesNear) {
351                                                                extraDistScore = 500000f;
352                                                        } else {
353                                                                extraDistScore = 1000000f;
354                                                        }
355                                                } else if (isHive(other.getShip())) {
356                                                        if (enemiesNear) {
357                                                                extraDistScore = 100000f;
358                                                        } else {
359                                                                extraDistScore = 10000000f;
360                                                        }
361                                                } else {
362                                                        continue;
363                                                }
364                                                float dist = Misc.getDistance(member.getLocation(), other.getLocation()) + extraDistScore;
365                                                if (dist < minDist) {
366                                                        closest = other;
367                                                        minDist = dist;
368                                                }
369                                        }
370                                        if (closest != null) {
371                                                member.getShip().getAIFlags().setFlag(AIFlags.TIMID_ESCORT, 2f);
372                                                member.getShip().getAIFlags().setFlag(AIFlags.ESCORT_RANGE_MODIFIER, 2f, 300f);
373                                                escort(member, closest);
374                                        }
375                                }
376                                
377                        }
378
379                        cleanUpEmptyAssignments();
380                }
381        }
382        
383        protected boolean wantsToControl(Vector2f fabricatorLoc, float axisAngle, float distToEnemyCom, Vector2f objectiveLoc) {
384                float dist = Misc.getDistance(fabricatorLoc, objectiveLoc);
385                float angle = Misc.getAngleInDegrees(fabricatorLoc, objectiveLoc);
386                float angleDiff = Misc.getAngleDiff(axisAngle, angle);
387                
388                float enemyWeight = Misc.countEnemyWeightInArcAroundLocation(owner, objectiveLoc, 0f, 360f, 3000f, null, true, true);
389                if (enemyWeight <= 0f && angleDiff > 30f) return true;
390                //return !(dist > 2000 && (dist > distToEnemyCom || angleDiff > 45f));
391                return dist < 5000 || (dist < distToEnemyCom && angleDiff < 45f);
392        }
393        
394        
395        protected void cancelOrders(DeployedFleetMemberAPI member, boolean withSearchAndDestroy) {
396                AssignmentInfo curr = taskManager.getAssignmentFor(member.getShip());
397                if (curr != null) {
398                        taskManager.removeAssignment(curr);
399                }
400                if (withSearchAndDestroy) {
401                        taskManager.orderSearchAndDestroy(member, false);
402                }
403        }
404        
405        protected void escort(DeployedFleetMemberAPI member, DeployedFleetMemberAPI target) {
406                if (member.getShip() == null) return;
407                AssignmentInfo curr = taskManager.getAssignmentFor(member.getShip());
408                if (curr != null && curr.getType() == CombatAssignmentType.LIGHT_ESCORT && 
409                                taskManager.getAssignmentTargetFor(member.getShip()) == target) {
410                        return;
411                }
412                
413                AssignmentInfo info = taskManager.createAssignment(CombatAssignmentType.LIGHT_ESCORT, target, false);
414                taskManager.setAssignmentWeight(info, 0f);
415                taskManager.giveAssignment(member, info, false);
416        }
417        
418        protected void giveMovementOrder(DeployedFleetMemberAPI member, Vector2f loc, float minDist, float maxDist) {
419                AssignmentInfo curr = taskManager.getAssignmentFor(member.getShip());
420                boolean needToMakeAssignment = curr == null || curr.getTarget() == null || Misc.getDistance(curr.getTarget().getLocation(), loc) > 100f;
421                
422                float dist = Misc.getDistance(member.getLocation(), loc);
423                if (dist < minDist) {
424                        if (curr != null && (curr.getType() == CombatAssignmentType.RALLY_CIVILIAN)) {
425                                taskManager.removeAssignment(curr);
426                        }
427                }
428                
429                boolean needToLeash = false;
430                needToLeash |= curr == null && dist > minDist;
431                needToLeash |= curr != null && dist > maxDist;
432                
433                if (needToMakeAssignment && needToLeash) {
434                        AssignmentTargetAPI wp = taskManager.createWaypoint2(loc, allyMode);
435                        AssignmentInfo task = taskManager.createAssignment(CombatAssignmentType.RALLY_CIVILIAN, wp, false);
436                        taskManager.giveAssignment(member, task, false);
437                }
438        }
439        
440        public void cleanUpEmptyAssignments() {
441                taskManager.reassign(); // so assigned members isn't empty
442                List<AssignmentInfo> remove = new ArrayList<>();
443                for (AssignmentInfo curr : taskManager.getAllAssignments()) {
444                        if (curr.getType() == CombatAssignmentType.CONTROL) continue;
445                        if (curr.getType() == CombatAssignmentType.CAPTURE) continue;
446                        if (curr.getType() == CombatAssignmentType.DEFEND) continue;
447                        if (curr.getAssignedMembers().isEmpty()) {
448                                remove.add(curr);
449                        }
450                }
451                for (AssignmentInfo curr : remove) {
452                        taskManager.removeAssignment(curr);
453                }
454                
455                taskManager.clearEmptyWaypoints();
456        }
457        
458        protected Vector2f getEnemyCenterOfMass() {
459                Vector2f com = new Vector2f();
460                float weight = 0;
461                for (DeployedFleetMemberAPI member : enemyFleetManager.getDeployedCopyDFM()) {
462                        if (member.isFighterWing()) continue;
463                        if (member.getShip() == null) continue;
464                        if (!engine.isAwareOf(owner, member.getShip())) continue;
465                        
466                        Vector2f.add(member.getLocation(), com, com);
467                        weight++; // maybe feels better than scaling weight by ship size etc
468                }
469                if (weight > 0) {
470                        com.scale(1f / weight);
471                }
472                return com;
473        }
474        
475        
476        public static boolean isCombatUnit(ShipAPI ship) {
477                return ship.getHullSpec().hasTag(Tags.THREAT_COMBAT);
478        }
479        public static boolean isOverseer(ShipAPI ship) {
480                return ship.getHullSpec().hasTag(Tags.THREAT_OVERSEER);
481        }
482        public static boolean isHive(ShipAPI ship) {
483                return ship.getHullSpec().hasTag(Tags.THREAT_HIVE);
484        }
485        public static boolean isFabricator(ShipAPI ship) {
486                return ship.getHullSpec().hasTag(Tags.THREAT_FABRICATOR);
487                //return ship.getSystem() != null && ship.getSystem().getId().equals(ShipSystems.CONSTRUCTION_SWARM);
488        }
489}
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512