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