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.SectorEntityToken.VisibilityLevel; 006import com.fs.starfarer.api.campaign.ai.FleetAIFlags; 007import com.fs.starfarer.api.campaign.ai.ModularFleetAIAPI; 008import com.fs.starfarer.api.campaign.rules.MemoryAPI; 009import com.fs.starfarer.api.characters.AbilityPlugin; 010import com.fs.starfarer.api.impl.campaign.abilities.InterdictionPulseAbility; 011import com.fs.starfarer.api.impl.campaign.ids.Abilities; 012import com.fs.starfarer.api.util.IntervalUtil; 013import com.fs.starfarer.api.util.Misc; 014public class InterdictionPulseAbilityAI extends BaseAbilityAI { 015 016 public static final float AI_FREQUENCY_MULT = 1f; 017 018 private IntervalUtil interval = new IntervalUtil(0.05f, 0.15f); 019 020 021 public void advance(float days) { 022 interval.advance(days * InterdictionPulseAbilityAI.AI_FREQUENCY_MULT); 023 if (!interval.intervalElapsed()) return; 024 025 if (fleet.getAI() instanceof ModularFleetAIAPI) { 026 ModularFleetAIAPI ai = (ModularFleetAIAPI) fleet.getAI(); 027 if (ai.getTacticalModule().isMaintainingContact()) { 028 return; 029 } 030 } 031 032 MemoryAPI mem = fleet.getMemoryWithoutUpdate(); 033 if (ability.isActiveOrInProgress()) { 034 mem.set(FleetAIFlags.HAS_SPEED_PENALTY, true, 0.2f); 035 mem.set(FleetAIFlags.USED_INTERDICTION_PULSE, true, 0.5f); 036 return; 037 } 038 039 if (!ability.isUsable()) return; 040 041 CampaignFleetAPI pursueTarget = mem.getFleet(FleetAIFlags.PURSUIT_TARGET); 042 CampaignFleetAPI fleeingFrom = mem.getFleet(FleetAIFlags.NEAREST_FLEEING_FROM); 043 044 045 float activationTime = ability.getSpec().getActivationDays() * Global.getSector().getClock().getSecondsPerDay(); 046 if (fleeingFrom != null) { 047 048 float range = InterdictionPulseAbility.getRange(fleet); 049 float dist = Misc.getDistance(fleet.getLocation(), fleeingFrom.getLocation()); 050 if (dist > range + 200) return; 051 052 VisibilityLevel level = fleeingFrom.getVisibilityLevelTo(fleet); 053 if (level == VisibilityLevel.NONE) return; 054 055 if (fleet.getAI() != null) { 056 if (!fleet.getAI().isHostileTo(fleeingFrom)) return; 057 } 058 059 //float speed = Math.max(1f, fleeingFrom.getTravelSpeed()); 060 float speed = Math.max(1f, fleeingFrom.getVelocity().length()); 061 float time = dist / speed; 062 063 boolean usingHasBenefit = false; 064 065 float interdictDur = InterdictionPulseAbility.getInterdictSeconds(fleet, fleeingFrom); 066 067 if (interdictDur > 0 && fleeingFrom.getVelocity().length() > fleet.getVelocity().length()) { 068 for (AbilityPlugin ability : fleeingFrom.getAbilities().values()) { 069 if (!ability.getSpec().hasTag(Abilities.TAG_BURN + "+")) continue; 070 if (ability.isActiveOrInProgress()) { 071 usingHasBenefit = true; 072 break; 073 } 074 } 075 076 AbilityPlugin eb = fleet.getAbility(Abilities.EMERGENCY_BURN); 077 if (eb != null && eb.getCooldownLeft() < activationTime + 1f) usingHasBenefit = true; 078 } 079 080 if (time > activationTime + 2f && time < activationTime + 7f && usingHasBenefit) { 081 if (shouldSkipUsing()) return; 082 ability.activate(); 083 } 084 return; 085 } 086 087 if (pursueTarget != null) { 088 float range = InterdictionPulseAbility.getRange(fleet); 089 float dist = Misc.getDistance(fleet.getLocation(), pursueTarget.getLocation()); 090 if (dist > range + 200) return; 091 092 VisibilityLevel level = pursueTarget.getVisibilityLevelTo(fleet); 093 if (level == VisibilityLevel.NONE) return; 094 095 if (fleet.getAI() != null) { 096 if (!fleet.getAI().isHostileTo(pursueTarget)) return; 097 } 098 099 100 //float speed = Math.max(1f, pursueTarget.getTravelSpeed()); 101 float speed = Math.max(1f, pursueTarget.getVelocity().length()); 102 float closingSpeed = Misc.getClosingSpeed(fleet.getLocation(), pursueTarget.getLocation(), 103 fleet.getVelocity(), pursueTarget.getVelocity()); 104 speed = Math.max(1f, (speed - closingSpeed) / 2f); 105 float time = Math.max(200, (range - dist)) / speed; 106 float timeToReach = dist / fleet.getVelocity().length(); 107 108 boolean usingHasBenefit = false; 109 float interdictDur = InterdictionPulseAbility.getInterdictSeconds(fleet, pursueTarget); 110 111 if (interdictDur > 0 && pursueTarget.getVelocity().length() > fleet.getVelocity().length()) { 112 for (AbilityPlugin ability : pursueTarget.getAbilities().values()) { 113 if (!ability.getSpec().hasTag(Abilities.TAG_BURN + "+")) continue; 114 if (ability.isActiveOrInProgress()) { 115 usingHasBenefit = true; 116 break; 117 } 118 } 119 120// eb = fleet.getAbility(Abilities.EMERGENCY_BURN); 121// if (eb != null && eb.getCooldownLeft() < activationTime + 1f) usingHasBenefit = true; 122 } 123 124 AbilityPlugin tj = pursueTarget.getAbility(Abilities.TRANSVERSE_JUMP); 125 if (tj != null && tj.isActiveOrInProgress() && timeToReach > activationTime && 126 dist < range) { 127 usingHasBenefit = true; 128 } 129 130 AbilityPlugin sb = pursueTarget.getAbility(Abilities.SUSTAINED_BURN); 131 if (sb != null && sb.isActiveOrInProgress() && 132 sb.getProgressFraction() > 0.25f && 133 sb.getProgressFraction() <= 0.5f) { 134 usingHasBenefit = true; 135 } 136 137 if (usingHasBenefit && time > activationTime + 0.5f) { 138 if (shouldSkipUsing()) return; 139 ability.activate(); 140 } 141 142 return; 143 } 144 145 } 146 147 public boolean shouldSkipUsing() { 148 for (CampaignFleetAPI other : fleet.getContainingLocation().getFleets()) { 149 if (fleet == other) continue; 150 if (other.getMemoryWithoutUpdate().contains(FleetAIFlags.USED_INTERDICTION_PULSE)) { 151 return true; 152 } 153 AbilityPlugin ip = other.getAbility(Abilities.INTERDICTION_PULSE); 154 if (ip != null && ip.isActiveOrInProgress()) return true; 155 } 156 return false; 157 } 158} 159 160 161 162 163 164