001package com.fs.starfarer.api.impl.combat.dweller;
002
003import org.lwjgl.util.vector.Vector2f;
004
005import com.fs.starfarer.api.Global;
006import com.fs.starfarer.api.combat.CombatEngineAPI;
007import com.fs.starfarer.api.combat.ShipAPI;
008import com.fs.starfarer.api.combat.ShipCommand;
009import com.fs.starfarer.api.combat.ShipSystemAIScript;
010import com.fs.starfarer.api.combat.ShipSystemAPI;
011import com.fs.starfarer.api.combat.ShipwideAIFlags;
012import com.fs.starfarer.api.combat.ShipwideAIFlags.AIFlags;
013import com.fs.starfarer.api.util.IntervalUtil;
014import com.fs.starfarer.api.util.Misc;
015
016
017/**
018 * The goal of this AI is *not* to make optimal decisions for when to use the system, and the system stats are not
019 * balanced for that. Rather, the goal is to use the system to create predictable/interesting movement behaviors.
020 * 
021 * @author Alex
022 *
023 */
024public class ConvulsiveLungeSystemAI implements ShipSystemAIScript {
025
026        public static float HULL_LOSS_FOR_PULLBACK = 0.25f;
027        public static float MAW_LUNGE_ARC = 30f;
028        
029        public static class SharedLungeAIData {
030                public float usedByMawToAttack;
031                public float usedByMawToPullBack;
032        }
033        
034        public static SharedLungeAIData getSharedData() {
035                String key = "lunge_AI_shared";
036                SharedLungeAIData data = (SharedLungeAIData)Global.getCombatEngine().getCustomData().get(key);
037                if (data == null) {
038                        data = new SharedLungeAIData();
039                        Global.getCombatEngine().getCustomData().put(key, data);
040                }
041                return data;
042        }
043        
044        public static float MIN_AGGRO_USE_INTERVAL = 5f;
045        
046        public static float USE_SCORE_PER_USE = 20f;
047        public static float USE_SCORE_THRESHOLD = 45f;
048        
049        
050        protected ShipAPI ship;
051        protected CombatEngineAPI engine;
052        protected ShipwideAIFlags flags;
053        protected ShipSystemAPI system;
054        protected ConvulsiveLungeSystemScript script;
055        
056        protected float sinceUsedForAttackOrMove = 100f;
057        protected float recentAggroUseScore = 0f;
058        protected boolean allowAggroUse = true;
059        
060        protected IntervalUtil tracker = new IntervalUtil(0.75f, 1.25f);
061        
062        protected float hullLevelAtPrevSystemUse = 1f;
063        protected float prevHardFluxLevel = 0f;
064        
065        public void init(ShipAPI ship, ShipSystemAPI system, ShipwideAIFlags flags, CombatEngineAPI engine) {
066                this.ship = ship;
067                this.flags = flags;
068                this.engine = engine;
069                this.system = system;
070                
071                script = (ConvulsiveLungeSystemScript)system.getScript();
072        }
073        
074        public void advance(float amount, Vector2f missileDangerDir, Vector2f collisionDangerDir, ShipAPI target) {
075                if (ship == null) return;
076                
077                tracker.advance(amount);
078                
079                sinceUsedForAttackOrMove += amount;
080                
081                boolean isMaw = DwellerCombatStrategyAI.isMaw(ship);
082                
083                if (isMaw) {
084                        recentAggroUseScore -= amount;
085                        if (recentAggroUseScore < 0f) recentAggroUseScore = 0f;
086                        if (!allowAggroUse && recentAggroUseScore <= 0) {
087                                allowAggroUse = true;
088                        } else if (allowAggroUse && recentAggroUseScore >= USE_SCORE_THRESHOLD) {
089                                allowAggroUse = false;
090                        }
091                }
092                
093                SharedLungeAIData data = getSharedData();
094                float now = Global.getCombatEngine().getTotalElapsedTime(false);
095                
096                boolean forceUseForPullback = !isMaw && data.usedByMawToPullBack + 0.1f > now;
097                boolean forceUseForAttack = !isMaw && data.usedByMawToAttack + 0.1f > now;
098                
099                if (ship.getFluxLevel() > 0.95f && ship.getHullLevel() > 0.25f && 
100                                ship.getShield() != null && ship.getShield().isOn()) {
101                        ship.getShield().toggleOff();
102                        ship.getAIFlags().setFlag(AIFlags.DO_NOT_USE_SHIELDS, 3f);
103                        forceUseForPullback = true;
104                }
105                
106                if (tracker.intervalElapsed() || forceUseForPullback || forceUseForAttack) {
107                        if (!isSystemUsable()) return;
108                        if (ship.getFluxTracker().isOverloadedOrVenting()) return;
109                        
110                        float hullLevel = ship.getHullLevel();
111                        
112                        hullLevelAtPrevSystemUse = Math.max(hullLevelAtPrevSystemUse, hullLevel);
113                        
114                        float hardFluxLevel = ship.getHardFluxLevel();
115                        float fluxLevel = ship.getFluxLevel();
116                        
117                        boolean useSystemForPullback = hullLevel <= hullLevelAtPrevSystemUse - HULL_LOSS_FOR_PULLBACK;
118                        
119                        if (((hardFluxLevel >= prevHardFluxLevel && hardFluxLevel >= 0.33f) || fluxLevel > 0.65f) &&
120                                        ship.getAIFlags().hasFlag(AIFlags.BACKING_OFF)) {// && (float) Math.random() > 0.75f) {
121                                if (target != null) {
122                                        float dist = Misc.getDistance(ship.getLocation(), target.getLocation());
123                                        dist -= ship.getCollisionRadius() + target.getCollisionRadius();
124                                        if (dist < 1000f || hardFluxLevel > prevHardFluxLevel + 0.02f) {
125                                                useSystemForPullback = true;
126                                        }
127                                }
128                        }
129                        
130                        prevHardFluxLevel = hardFluxLevel;
131                        
132                        useSystemForPullback |= forceUseForPullback;
133                        
134                        if (useSystemForPullback) {
135                                float angle = ship.getFacing() + 180f;
136                                if (target != null) {
137                                        angle = Misc.getAngleInDegrees(target.getLocation(), ship.getLocation());
138                                }
139                                if (missileDangerDir != null) {
140                                        angle = Misc.getAngleInDegrees(missileDangerDir) + 180f;
141                                }
142                                
143                                if (isMaw && Misc.getAngleDiff(ship.getFacing() + 180f, angle) > MAW_LUNGE_ARC * 0.5f) {
144                                        return;
145                                }
146                                
147                                
148                                Vector2f point = Misc.getUnitVectorAtDegreeAngle(angle);
149                                point.scale(2000f);
150                                Vector2f.add(point, ship.getLocation(), point);
151                                
152                                giveCommand(point);
153                                hullLevelAtPrevSystemUse = hullLevel;
154                                
155                                if (isMaw) {
156                                        data.usedByMawToPullBack = now;
157                                }
158                                return;
159                        }
160                        
161                        
162                        boolean useSystemForAttackOrMovement = false;
163                        
164                        float arc = 30f;
165                        float checkDist = 700f;
166                        if (!isMaw && forceUseForAttack) checkDist = 300f;
167                        boolean blocked = false;
168                        float angle = ship.getFacing();
169                        Vector2f dir = Misc.getUnitVectorAtDegreeAngle(angle);
170                        if (target != null) {
171                                angle = Misc.getAngleInDegrees(ship.getLocation(), target.getLocation());
172                        }
173                        
174                        
175                        
176                        for (ShipAPI other : Global.getCombatEngine().getShips()) {
177                                if (other.isFighter()) continue;
178                                
179                                if (other.getOwner() != ship.getOwner() && 
180                                                (!other.isHulk() || (isMaw && other.getMassWithModules() < ship.getMass() * 0.25f)) &&
181                                                (ship.getHullSize().ordinal() > other.getHullSize().ordinal() || (!isMaw && forceUseForAttack)) &&
182                                                ship.getHullLevel() > 0.5f &&
183                                                other != target) {
184                                        continue;
185                                }
186                                
187                                
188                                float dist = Misc.getDistance(ship.getLocation(), other.getLocation());
189                                dist -= (ship.getCollisionRadius() + other.getCollisionRadius()) * 0.6f;
190                                if (dist > checkDist) continue;
191                                
192                                if (Misc.isInArc(angle, arc, ship.getLocation(), other.getLocation())) {
193                                        blocked = true;
194                                        break;
195                                }
196                        }
197
198                        float speed = ship.getVelocity().length();
199                        float speedInDir = Vector2f.dot(dir, ship.getVelocity());
200                        boolean aligned = speedInDir > speed * 0.65f && speed >= ship.getMaxSpeed() * 0.9f;
201                        
202                        useSystemForAttackOrMovement = !blocked && aligned;
203                        if (sinceUsedForAttackOrMove < MIN_AGGRO_USE_INTERVAL || !allowAggroUse) {
204                                useSystemForAttackOrMovement = false;
205                        }
206                        
207                        //if (!isMaw && (float) Math.random() > 0.1f) useSystemForAttackOrMovement = false;
208                        if (!isMaw) useSystemForAttackOrMovement = false;
209                        useSystemForAttackOrMovement |= (forceUseForAttack && !blocked);
210                        
211                        
212                        if (useSystemForAttackOrMovement) {
213                                if (isMaw && Misc.getAngleDiff(ship.getFacing(), angle) > MAW_LUNGE_ARC * 0.5f) {
214                                        return;
215                                }
216                                
217                                Vector2f point = Misc.getUnitVectorAtDegreeAngle(angle);
218                                point.scale(2000f);
219                                Vector2f.add(point, ship.getLocation(), point);
220                                
221                                giveCommand(point);
222                                
223                                if (isMaw) {
224                                        data.usedByMawToAttack = now;
225                                }
226                                sinceUsedForAttackOrMove = 0f;
227                                recentAggroUseScore += USE_SCORE_PER_USE;
228                                return;
229                        }
230                        
231                        
232                }
233        }
234        
235        public boolean isSystemUsable() {
236                if (system.getCooldownRemaining() > 0) return false;
237                if (system.isOutOfAmmo()) return false;
238                if (system.isActive()) return false;
239                return true;
240        }
241        
242        public void giveCommand(Vector2f target) {
243                if (ship.getAIFlags() != null) {
244                        ship.getAIFlags().setFlag(AIFlags.SYSTEM_TARGET_COORDS, 1f, target);
245                }
246                ship.giveCommand(ShipCommand.USE_SYSTEM, null, 0);
247        }
248
249}
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271