001package com.fs.starfarer.api.impl.campaign.abilities.ai;
002
003import com.fs.starfarer.api.Global;
004import com.fs.starfarer.api.campaign.CampaignFleetAPI;
005import com.fs.starfarer.api.campaign.FleetAssignment;
006import com.fs.starfarer.api.campaign.SectorEntityToken.VisibilityLevel;
007import com.fs.starfarer.api.campaign.ai.FleetAIFlags;
008import com.fs.starfarer.api.campaign.ai.ModularFleetAIAPI;
009import com.fs.starfarer.api.campaign.rules.MemoryAPI;
010import com.fs.starfarer.api.fleet.FleetMemberAPI;
011import com.fs.starfarer.api.impl.campaign.abilities.EmergencyBurnAbility;
012import com.fs.starfarer.api.impl.campaign.terrain.HyperspaceTerrainPlugin;
013import com.fs.starfarer.api.util.IntervalUtil;
014import com.fs.starfarer.api.util.Misc;
015
016public class EmergencyBurnAbilityAI extends BaseAbilityAI {
017
018        public static String AI_USE_TIMEOUT_KEY = "$ebai_timeout";
019        public static float AI_USE_TIMEOUT_DAYS_MIN = 3f;
020        public static float AI_USE_TIMEOUT_DAYS_MAX = 5f;
021        
022        public static float AI_FREQUENCY_MULT = 1f;
023        
024        protected IntervalUtil interval = new IntervalUtil(0.05f, 0.15f);
025
026//      public EmergencyBurnAbilityAI(AbilityPlugin ability, ModularFleetAIAPI ai) {
027//              super(ability, ai);
028//      }
029
030        protected void activate() {
031//              if (fleet.getContainingLocation() != null && fleet.getContainingLocation().isCurrentLocation()) {
032//                      float dist = Misc.getDistance(fleet.getLocation(), Global.getSector().getPlayerFleet().getLocation());
033//                      if (dist < 1000f) {
034//                              System.out.println("fwefewfew");
035//                      }
036//              }
037                ability.activate();
038                MemoryAPI mem = fleet.getMemoryWithoutUpdate();
039                mem.set(AI_USE_TIMEOUT_KEY, true,
040                                AI_USE_TIMEOUT_DAYS_MIN + (AI_USE_TIMEOUT_DAYS_MAX - AI_USE_TIMEOUT_DAYS_MIN) * (float) Math.random());
041        }
042        
043        public void advance(float days) {
044                interval.advance(days * EmergencyBurnAbilityAI.AI_FREQUENCY_MULT * 0.25f);
045                if (!interval.intervalElapsed()) return;
046                
047//              if (fleet.getName().contains("[5]")) {
048//                      System.out.println("ewfwefwe");
049//              }
050                if (ability.isActiveOrInProgress()) {
051                        MemoryAPI mem = fleet.getMemoryWithoutUpdate();
052                        mem.set(FleetAIFlags.HAS_SPEED_BONUS, true, 0.2f);
053                        mem.set(FleetAIFlags.HAS_VISION_PENALTY, true, 0.2f);
054                        return;
055                }
056                
057                // max burn bonus wouldn't mean much due to a low multiplier, don't use it
058                // DO NOT want to check HAS_SPEED_PENALTY here, as using this ability will cancel "Go Dark".
059                // since EB now removes terrain penalties
060                // but a *very* low mult can also be indicative of an interdict...
061                //if (fleet.getStats().getFleetwideMaxBurnMod().getBonusMult() <= 0.3f) {
062                if (fleet.getStats().getFleetwideMaxBurnMod().getBonusMult() <= 0.15f) {
063                        return;
064                }
065                
066                if (fleet.getAI() != null && fleet.getAI().getCurrentAssignmentType() == FleetAssignment.STANDING_DOWN) {
067                        return;
068                }
069                
070                MemoryAPI mem = fleet.getMemoryWithoutUpdate();
071//              if (fleet.isInCurrentLocation()) {
072//                      System.out.println("23r23r23r3");
073//              }
074                if (mem.getBoolean(AI_USE_TIMEOUT_KEY)) {
075                        return;
076                }
077                
078                if (fleet.isInHyperspace() && Misc.isInsideSlipstream(fleet)) {
079                        activate();
080                        return;
081                }
082                
083                if (fleet.getMemoryWithoutUpdate().getBoolean(HyperspaceTerrainPlugin.STORM_STRIKE_TIMEOUT_KEY) &&
084                                !Misc.isSlowMoving(fleet)) {
085                        activate();
086                        return;
087                }
088                
089                if (Misc.isInsideBlackHole(fleet, false)) {
090                        activate();
091                        return;
092                }
093                
094                
095                
096                CampaignFleetAPI pursueTarget = mem.getFleet(FleetAIFlags.PURSUIT_TARGET);
097                CampaignFleetAPI fleeingFrom = mem.getFleet(FleetAIFlags.NEAREST_FLEEING_FROM);
098                //Vector2f travelDest = mem.getVector2f(FleetAIFlags.TRAVEL_DEST);
099                
100                // need to evaluate whether ability is worth using: how desperate the situation is vs the CR hit
101                
102                // being pursued by a faster enemy that's relatively close: turn on
103                if (fleeingFrom != null) {
104                        if (fleeingFrom.isStationMode()) return;
105                        
106                        
107                        VisibilityLevel level = fleet.getVisibilityLevelTo(fleeingFrom);
108                        if (level == VisibilityLevel.NONE) return; // they can't see us, don't make it easier
109                        
110                        if (!ability.isUsable()) return;
111                        
112                        if (fleeingFrom.isPlayerFleet()) {
113                                boolean avoidingPlayer = Misc.isAvoidingPlayerHalfheartedly(fleet);
114                                if (avoidingPlayer) return;
115                        }
116                        
117                        UseCost cost = getUseCost();
118                        boolean hopelessFight = isGreatlyOutmatchedBy(fleeingFrom);
119                        float dist = Misc.getDistance(fleet.getLocation(), fleeingFrom.getLocation()) - fleet.getRadius() + fleeingFrom.getRadius();
120                        float detRange = fleeingFrom.getMaxSensorRangeToDetect(fleet);
121                        float ourSpeed = fleet.getFleetData().getBurnLevel();
122                        float theirSpeed = fleeingFrom.getFleetData().getBurnLevel();
123                        float closingSpeed = Misc.getClosingSpeed(fleet.getLocation(), fleeingFrom.getLocation(), 
124                                                                                                          fleet.getVelocity(), fleeingFrom.getVelocity());
125                        if ((theirSpeed > ourSpeed && closingSpeed > 1) || (closingSpeed > 1 && dist < 100)) {
126                                if (hopelessFight && dist < 200) { // very close and really don't want to fight
127                                        activate();
128                                } else if ((cost == UseCost.LOW || cost == UseCost.MEDIUM) && dist < 500) { // low cost, getting decently close
129                                        activate();
130                                } else if ((cost == UseCost.LOW || cost == UseCost.MEDIUM) && dist < 100) { // medium cost, very close
131                                        activate();
132                                } else if ((cost == UseCost.LOW || cost == UseCost.MEDIUM) && dist > detRange - 100f) { // low cost, close to being able to get out of sight
133                                        activate();
134                                }
135                        }
136                        return;
137                }
138                
139                // pursuing a faster enemy, and would be faster then them with EB on: turn on
140                if (pursueTarget != null) {
141                        if (pursueTarget.isStationMode()) return;
142                        
143                        if (fleet.getAI() instanceof ModularFleetAIAPI) {
144                                ModularFleetAIAPI ai = (ModularFleetAIAPI) fleet.getAI();
145                                if (ai.getTacticalModule().isMaintainingContact()) {
146                                        return;
147                                }
148                        }
149                        
150                        VisibilityLevel level = pursueTarget.getVisibilityLevelTo(fleet);
151                        if (level == VisibilityLevel.NONE) return;
152                        
153                        if (pursueTarget.isPlayerFleet()) {
154                                level = fleet.getVisibilityLevelTo(pursueTarget);
155                                if (level == VisibilityLevel.NONE) {
156                                        float closingSpeed = Misc.getClosingSpeed(pursueTarget.getLocation(), fleet.getLocation(), 
157                                                                                                                          pursueTarget.getVelocity(), fleet.getVelocity());
158                                        if (closingSpeed > 0) {
159                                                return;
160                                        }
161                                }
162                        }
163                        
164                        
165                        if (!ability.isUsable()) return;
166                        
167                        boolean targetInsignificant = otherInsignificant(pursueTarget);// && !pursueTarget.isPlayerFleet();
168//                      if (pursueTarget.isPlayerFleet()) {
169//                              System.out.println("test player fleet EB");
170//                      }
171
172                        UseCost cost = getUseCost();
173                        float dist = Misc.getDistance(fleet.getLocation(), pursueTarget.getLocation()) - fleet.getRadius() - pursueTarget.getRadius();
174                        if (dist < 0) return;
175                        
176                        float detRange = pursueTarget.getMaxSensorRangeToDetect(fleet);
177                        float ourSpeed = fleet.getFleetData().getBurnLevel();
178                        float theirSpeed = pursueTarget.getFleetData().getBurnLevel();
179                        
180                        float closingSpeed = Misc.getClosingSpeed(fleet.getLocation(), pursueTarget.getLocation(), 
181                                                                                                          fleet.getVelocity(), pursueTarget.getVelocity());
182                        
183                        if (cost == UseCost.LOW && closingSpeed <= -1 && dist > detRange - 100f) { // about to lose sensor contact
184                                activate();
185                        } else if (cost == UseCost.LOW && dist < 200 && closingSpeed < 50 && !targetInsignificant) { // close, pounce
186                                activate();
187                        } else if (cost == UseCost.LOW && theirSpeed > ourSpeed && dist > 300 && !targetInsignificant) {
188                                activate();
189                        }
190                        return;
191                }
192                
193                
194                boolean useEB = mem.getBoolean(FleetAIFlags.USE_EB_FOR_TRAVEL);
195                if (useEB) {
196                        if (!ability.isUsable()) return;
197                        activate();
198                        return;
199                }
200                
201        }
202        
203        public static enum UseCost {
204                LOW,
205                MEDIUM,
206                HIGH
207        }
208        private UseCost getUseCost() {
209                float count = 0;
210                float numCritAlready = 0;
211                float numCrit = 0;
212                float numLow = 0;
213                float numOk = 0;
214                
215                float crCrit = Global.getSettings().getCRPlugin().getCriticalMalfunctionThreshold(null);
216                float crLow = Global.getSettings().getCRPlugin().getMalfunctionThreshold(null) + 0.01f;
217                
218                boolean allCRMaxed = true;
219                for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
220                        count++;
221                        
222                        if (member.isCivilian()) {
223                                numOk++;
224                                continue;
225                        }
226                        
227                        float useCost = member.getDeployCost() * EmergencyBurnAbility.CR_COST_MULT;
228                        float cr = member.getRepairTracker().getCR();
229                        float maxCR = member.getRepairTracker().getMaxCR();
230                        
231                        float crAfter = cr - useCost;
232                        
233                        if (cr < maxCR) {
234                                allCRMaxed = false;
235                        }
236                        
237                        if (cr <= crCrit * 0.5f) {
238                                numCritAlready++;
239                        }
240                        if (crAfter <= crCrit) {
241                                numCrit++;
242                        } else if (crAfter <= crLow) {
243                                numLow++;
244                        } else {
245                                numOk++;
246                        }
247                }
248                
249                if (numCritAlready >= count) return UseCost.LOW;
250                
251                if (allCRMaxed) return UseCost.LOW;
252                if (numOk + numLow >= count) return UseCost.MEDIUM;
253                //if (numOk + numLow >= count && numOk * 0.5f >= numLow) return UseCost.LOW;
254                //if (numLow > numCrit * 0.5f) return UseCost.MEDIUM;
255                return UseCost.HIGH;
256        }
257        
258        
259        
260        
261        protected boolean isGreatlyOutmatchedBy(CampaignFleetAPI other) {
262                float us = getStrength(fleet);
263                float them = getStrength(other);
264                
265                if (us < 0.1f) us = 0.1f;
266                if (them < 0.1f) them = 0.1f;
267                return them > us * 3f;
268        }
269        
270        protected boolean otherInsignificant(CampaignFleetAPI other) {
271                float us = getStrength(fleet);
272                float them = getStrength(other);
273                
274                if (us < 0.1f) us = 0.1f;
275                if (them < 0.1f) them = 0.1f;
276                return us > them * 5f;
277        }
278        
279        public static float getStrength(CampaignFleetAPI fleet) {
280                float str = 0f;
281                for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
282                        if (member.canBeDeployedForCombat()) {
283                                float strength = member.getMemberStrength();
284                                str += strength;
285                        }
286                }
287                return str;
288        }
289}
290
291
292
293
294
295