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