001package com.fs.starfarer.api.impl.combat; 002 003import java.util.ArrayList; 004import java.util.Iterator; 005import java.util.List; 006 007import org.lwjgl.util.vector.Vector2f; 008 009import com.fs.starfarer.api.Global; 010import com.fs.starfarer.api.combat.CollisionGridAPI; 011import com.fs.starfarer.api.combat.CombatEngineAPI; 012import com.fs.starfarer.api.combat.CombatEntityAPI; 013import com.fs.starfarer.api.combat.MissileAIPlugin; 014import com.fs.starfarer.api.combat.MissileAPI; 015import com.fs.starfarer.api.combat.ShipAPI; 016import com.fs.starfarer.api.combat.ShipCommand; 017import com.fs.starfarer.api.impl.combat.MoteControlScript.SharedMoteAIData; 018import com.fs.starfarer.api.util.FaderUtil; 019import com.fs.starfarer.api.util.IntervalUtil; 020import com.fs.starfarer.api.util.Misc; 021 022public class MoteAIScript implements MissileAIPlugin { 023 024 public static float MAX_FLOCK_RANGE = 500; 025 public static float MAX_HARD_AVOID_RANGE = 200; 026 public static float AVOID_RANGE = 50; 027 public static float COHESION_RANGE = 100; 028 029 public static float ATTRACTOR_LOCK_STOP_FLOCKING_ADD = 300f; 030 031 protected MissileAPI missile; 032 033 protected IntervalUtil tracker = new IntervalUtil(0.05f, 0.1f); 034 035 protected IntervalUtil updateListTracker = new IntervalUtil(0.05f, 0.1f); 036 protected List<MissileAPI> missileList = new ArrayList<MissileAPI>(); 037 protected List<CombatEntityAPI> hardAvoidList = new ArrayList<CombatEntityAPI>(); 038 039 protected float r; 040 041 protected CombatEntityAPI target; 042 protected SharedMoteAIData data; 043 044 public MoteAIScript(MissileAPI missile) { 045 this.missile = missile; 046 r = (float) Math.random(); 047 elapsed = -(float) Math.random() * 0.5f; 048 049 data = MoteControlScript.getSharedData(missile.getSource()); 050 051 updateHardAvoidList(); 052 } 053 054 public void updateHardAvoidList() { 055 hardAvoidList.clear(); 056 057 CollisionGridAPI grid = Global.getCombatEngine().getAiGridShips(); 058 Iterator<Object> iter = grid.getCheckIterator(missile.getLocation(), MAX_HARD_AVOID_RANGE * 2f, MAX_HARD_AVOID_RANGE * 2f); 059 while (iter.hasNext()) { 060 Object o = iter.next(); 061 if (!(o instanceof ShipAPI)) continue; 062 063 ShipAPI ship = (ShipAPI) o; 064 065 if (ship.isFighter()) continue; 066 hardAvoidList.add(ship); 067 } 068 069 grid = Global.getCombatEngine().getAiGridAsteroids(); 070 iter = grid.getCheckIterator(missile.getLocation(), MAX_HARD_AVOID_RANGE * 2f, MAX_HARD_AVOID_RANGE * 2f); 071 while (iter.hasNext()) { 072 Object o = iter.next(); 073 if (!(o instanceof CombatEntityAPI)) continue; 074 075 CombatEntityAPI asteroid = (CombatEntityAPI) o; 076 hardAvoidList.add(asteroid); 077 } 078 } 079 080 public void doFlocking() { 081 if (missile.getSource() == null) return; 082 083 ShipAPI source = missile.getSource(); 084 CombatEngineAPI engine = Global.getCombatEngine(); 085 086 float avoidRange = AVOID_RANGE; 087 float cohesionRange = COHESION_RANGE; 088 089 float sourceRejoin = source.getCollisionRadius() + 200f; 090 091 float sourceRepel = source.getCollisionRadius() + 50f; 092 float sourceCohesion = source.getCollisionRadius() + 600f; 093 094 float sin = (float) Math.sin(data.elapsed * 1f); 095 float mult = 1f + sin * 0.25f; 096 avoidRange *= mult; 097 098 Vector2f total = new Vector2f(); 099 Vector2f attractor = getAttractorLoc(); 100 101 if (attractor != null) { 102 float dist = Misc.getDistance(missile.getLocation(), attractor); 103 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(missile.getLocation(), attractor)); 104 float f = dist / 200f; 105 if (f > 1f) f = 1f; 106 dir.scale(f * 3f); 107 Vector2f.add(total, dir, total); 108 109 avoidRange *= 3f; 110 } 111 112 boolean hardAvoiding = false; 113 for (CombatEntityAPI other : hardAvoidList) { 114 float dist = Misc.getDistance(missile.getLocation(), other.getLocation()); 115 float hardAvoidRange = other.getCollisionRadius() + avoidRange + 50f; 116 if (dist < hardAvoidRange) { 117 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(other.getLocation(), missile.getLocation())); 118 float f = 1f - dist / (hardAvoidRange); 119 dir.scale(f * 5f); 120 Vector2f.add(total, dir, total); 121 hardAvoiding = f > 0.5f; 122 } 123 } 124 125 126 //for (MissileAPI otherMissile : missileList) { 127 for (MissileAPI otherMissile : data.motes) { 128 if (otherMissile == missile) continue; 129 130 float dist = Misc.getDistance(missile.getLocation(), otherMissile.getLocation()); 131 132 133 float w = otherMissile.getMaxHitpoints(); 134 w = 1f; 135 136 float currCohesionRange = cohesionRange; 137 138 if (dist < avoidRange && otherMissile != missile && !hardAvoiding) { 139 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(otherMissile.getLocation(), missile.getLocation())); 140 float f = 1f - dist / avoidRange; 141 dir.scale(f * w); 142 Vector2f.add(total, dir, total); 143 } 144 145 if (dist < currCohesionRange) { 146 Vector2f dir = new Vector2f(otherMissile.getVelocity()); 147 Misc.normalise(dir); 148 float f = 1f - dist / currCohesionRange; 149 dir.scale(f * w); 150 Vector2f.add(total, dir, total); 151 } 152 153// if (dist < cohesionRange && dist > avoidRange) { 154// //Vector2f dir = Utils.getUnitVectorAtDegreeAngle(Utils.getAngleInDegrees(missile.getLocation(), mote.getLocation())); 155// Vector2f dir = Utils.getUnitVectorAtDegreeAngle(Utils.getAngleInDegrees(mote.getLocation(), missile.getLocation())); 156// float f = dist / cohesionRange - 1f; 157// dir.scale(f * 0.5f); 158// Vector2f.add(total, dir, total); 159// } 160 } 161 162 if (missile.getSource() != null) { 163 float dist = Misc.getDistance(missile.getLocation(), source.getLocation()); 164 if (dist > sourceRejoin) { 165 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(missile.getLocation(), source.getLocation())); 166 float f = dist / (sourceRejoin + 400f) - 1f; 167 dir.scale(f * 0.5f); 168 169 Vector2f.add(total, dir, total); 170 } 171 172 if (dist < sourceRepel) { 173 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(source.getLocation(), missile.getLocation())); 174 float f = 1f - dist / sourceRepel; 175 dir.scale(f * 5f); 176 Vector2f.add(total, dir, total); 177 } 178 179 if (dist < sourceCohesion && source.getVelocity().length() > 20f) { 180 Vector2f dir = new Vector2f(source.getVelocity()); 181 Misc.normalise(dir); 182 float f = 1f - dist / sourceCohesion; 183 dir.scale(f * 1f); 184 Vector2f.add(total, dir, total); 185 } 186 187 // if not strongly going anywhere, circle the source ship; only kicks in for lone motes 188 if (total.length() <= 0.05f) { 189 float offset = r > 0.5f ? 90f : -90f; 190 Vector2f dir = Misc.getUnitVectorAtDegreeAngle( 191 Misc.getAngleInDegrees(missile.getLocation(), source.getLocation()) + offset); 192 float f = 1f; 193 dir.scale(f * 1f); 194 Vector2f.add(total, dir, total); 195 } 196 } 197 198 if (total.length() > 0) { 199 float dir = Misc.getAngleInDegrees(total); 200 engine.headInDirectionWithoutTurning(missile, dir, 10000); 201 202 if (r > 0.5f) { 203 missile.giveCommand(ShipCommand.TURN_LEFT); 204 } else { 205 missile.giveCommand(ShipCommand.TURN_RIGHT); 206 } 207 missile.getEngineController().forceShowAccelerating(); 208 } 209 } 210 211 //public void accumulate(FlockingData data, Vector2f ) 212 213 214 protected IntervalUtil flutterCheck = new IntervalUtil(2f, 4f); 215 protected FaderUtil currFlutter = null; 216 protected float flutterRemaining = 0f; 217 218 protected float elapsed = 0f; 219 public void advance(float amount) { 220 if (missile.isFizzling()) return; 221 if (missile.getSource() == null) return; 222 223 elapsed += amount; 224 225 updateListTracker.advance(amount); 226 if (updateListTracker.intervalElapsed()) { 227 updateHardAvoidList(); 228 } 229 230 //missile.getEngineController().getShipEngines().get(0). 231 232 if (flutterRemaining <= 0) { 233 flutterCheck.advance(amount); 234 if (flutterCheck.intervalElapsed() && 235 ((float) Math.random() > 0.9f || 236 (data.attractorLock != null && (float) Math.random() > 0.5f))) { 237 flutterRemaining = 2f + (float) Math.random() * 2f; 238 } 239 } 240 241// if (flutterRemaining > 0) { 242// flutterRemaining -= amount; 243// if (currFlutter == null) { 244// float min = 1/15f; 245// float max = 1/4f; 246// float dur = min + (max - min) * (float) Math.random(); 247// //dur *= 0.5f; 248// currFlutter = new FaderUtil(0f, dur/2f, dur/2f, false, true); 249// currFlutter.fadeIn(); 250// } 251// currFlutter.advance(amount); 252// if (currFlutter.isFadedOut()) { 253// currFlutter = null; 254// } 255// } else { 256// currFlutter = null; 257// } 258// 259// if (currFlutter != null) { 260// missile.setGlowRadius(currFlutter.getBrightness() * 30f); 261// } else { 262// missile.setGlowRadius(0f); 263// } 264// if (true) { 265// doFlocking(); 266// return; 267// } 268 269 270 if (elapsed >= 0.5f) { 271 272 boolean wantToFlock = !isTargetValid(); 273 if (data.attractorLock != null) { 274 float dist = Misc.getDistance(missile.getLocation(), data.attractorLock.getLocation()); 275 if (dist > data.attractorLock.getCollisionRadius() + ATTRACTOR_LOCK_STOP_FLOCKING_ADD) { 276 wantToFlock = true; 277 } 278 } 279 280 if (wantToFlock) { 281 doFlocking(); 282 } else { 283 CombatEngineAPI engine = Global.getCombatEngine(); 284 Vector2f targetLoc = engine.getAimPointWithLeadForAutofire(missile, 1.5f, target, 50); 285 engine.headInDirectionWithoutTurning(missile, 286 Misc.getAngleInDegrees(missile.getLocation(), targetLoc), 287 10000); 288 //AIUtils.turnTowardsPointV2(missile, targetLoc); 289 if (r > 0.5f) { 290 missile.giveCommand(ShipCommand.TURN_LEFT); 291 } else { 292 missile.giveCommand(ShipCommand.TURN_RIGHT); 293 } 294 missile.getEngineController().forceShowAccelerating(); 295 } 296 } 297 298 tracker.advance(amount); 299 if (tracker.intervalElapsed()) { 300 if (elapsed >= 0.5f) { 301 acquireNewTargetIfNeeded(); 302 } 303 //causeEnemyMissilesToTargetThis(); 304 } 305 } 306 307 308 @SuppressWarnings("unchecked") 309 protected boolean isTargetValid() { 310 if (target == null || (target instanceof ShipAPI && ((ShipAPI)target).isPhased())) { 311 return false; 312 } 313 CombatEngineAPI engine = Global.getCombatEngine(); 314 315 if (target != null && target instanceof ShipAPI && ((ShipAPI)target).isHulk()) return false; 316 317 List list = null; 318 if (target instanceof ShipAPI) { 319 list = engine.getShips(); 320 } else { 321 list = engine.getMissiles(); 322 } 323 return target != null && list.contains(target) && target.getOwner() != missile.getOwner(); 324 } 325 326 protected void acquireNewTargetIfNeeded() { 327 if (data.attractorLock != null) { 328 target = data.attractorLock; 329 return; 330 } 331 332 CombatEngineAPI engine = Global.getCombatEngine(); 333 334 // want to: target nearest missile that is not targeted by another two motes already 335 int owner = missile.getOwner(); 336 337 int maxMotesPerMissile = 2; 338 float maxDistFromSourceShip = MoteControlScript.MAX_DIST_FROM_SOURCE_TO_ENGAGE_AS_PD; 339 float maxDistFromAttractor = MoteControlScript.MAX_DIST_FROM_ATTRACTOR_TO_ENGAGE_AS_PD; 340 341 float minDist = Float.MAX_VALUE; 342 CombatEntityAPI closest = null; 343 for (MissileAPI other : engine.getMissiles()) { 344 if (other.getOwner() == owner) continue; 345 if (other.getOwner() == 100) continue; 346 float distToTarget = Misc.getDistance(missile.getLocation(), other.getLocation()); 347 348 if (distToTarget > minDist) continue; 349 if (distToTarget > 3000 && !engine.isAwareOf(owner, other)) continue; 350 351 float distFromAttractor = Float.MAX_VALUE; 352 if (data.attractorTarget != null) { 353 distFromAttractor = Misc.getDistance(other.getLocation(), data.attractorTarget); 354 } 355 float distFromSource = Misc.getDistance(other.getLocation(), missile.getSource().getLocation()); 356 if (distFromSource > maxDistFromSourceShip && 357 distFromAttractor > maxDistFromAttractor) continue; 358 359 if (getNumMotesTargeting(other) >= maxMotesPerMissile) continue; 360 if (distToTarget < minDist) { 361 closest = other; 362 minDist = distToTarget; 363 } 364 } 365 366 for (ShipAPI other : engine.getShips()) { 367 if (other.getOwner() == owner) continue; 368 if (other.getOwner() == 100) continue; 369 if (!other.isFighter()) continue; 370 float distToTarget = Misc.getDistance(missile.getLocation(), other.getLocation()); 371 if (distToTarget > minDist) continue; 372 if (distToTarget > 3000 && !engine.isAwareOf(owner, other)) continue; 373 374 float distFromAttractor = Float.MAX_VALUE; 375 if (data.attractorTarget != null) { 376 distFromAttractor = Misc.getDistance(other.getLocation(), data.attractorTarget); 377 } 378 float distFromSource = Misc.getDistance(other.getLocation(), missile.getSource().getLocation()); 379 if (distFromSource > maxDistFromSourceShip && 380 distFromAttractor > maxDistFromAttractor) continue; 381 382 if (getNumMotesTargeting(other) >= maxMotesPerMissile) continue; 383 if (distToTarget < minDist) { 384 closest = other; 385 minDist = distToTarget; 386 } 387 } 388 389 target = closest; 390 } 391 392 protected int getNumMotesTargeting(CombatEntityAPI other) { 393 int count = 0; 394 for (MissileAPI mote : data.motes) { 395 if (mote == missile) continue; 396 if (mote.getUnwrappedMissileAI() instanceof MoteAIScript) { 397 MoteAIScript ai = (MoteAIScript) mote.getUnwrappedMissileAI(); 398 if (ai.getTarget() == other) { 399 count++; 400 } 401 } 402 } 403 return count; 404 } 405 406 public Vector2f getAttractorLoc() { 407 Vector2f attractor = null; 408 if (data.attractorTarget != null) { 409 attractor = data.attractorTarget; 410 if (data.attractorLock != null) { 411 attractor = data.attractorLock.getLocation(); 412 } 413 } 414 return attractor; 415 } 416 417 public CombatEntityAPI getTarget() { 418 return target; 419 } 420 421 public void setTarget(CombatEntityAPI target) { 422 this.target = target; 423 } 424 public void render() { 425 426 } 427}