001package com.fs.starfarer.api.impl.combat.threat; 002 003import java.util.ArrayList; 004import java.util.List; 005 006import org.lwjgl.util.vector.Vector2f; 007 008import com.fs.starfarer.api.Global; 009import com.fs.starfarer.api.combat.CombatEngineAPI; 010import com.fs.starfarer.api.combat.DamageType; 011import com.fs.starfarer.api.combat.FighterWingAPI; 012import com.fs.starfarer.api.combat.MutableShipStatsAPI; 013import com.fs.starfarer.api.combat.ShipAIConfig; 014import com.fs.starfarer.api.combat.ShipAIPlugin; 015import com.fs.starfarer.api.combat.ShipAPI; 016import com.fs.starfarer.api.combat.ShipCommand; 017import com.fs.starfarer.api.combat.ShipwideAIFlags; 018import com.fs.starfarer.api.combat.ShipwideAIFlags.AIFlags; 019import com.fs.starfarer.api.combat.WeaponGroupAPI; 020import com.fs.starfarer.api.impl.combat.threat.ConstructionSwarmSystemScript.SwarmConstructionData; 021import com.fs.starfarer.api.util.IntervalUtil; 022import com.fs.starfarer.api.util.Misc; 023import com.fs.starfarer.api.util.WeightedRandomPicker; 024 025public class ThreatSwarmAI implements ShipAIPlugin { 026 027 public static float ATTRACTOR_RANGE_MAX_SAME_WING = 1000000f; 028 public static float ATTRACTOR_RANGE_MAX = 500f; 029 public static float COHESION_RANGE_MIN = 150f; 030 public static float COHESION_RANGE_MAX = 300f; 031 public static float REPEL_RANGE_MIN = 0f; 032 public static float REPEL_RANGE_MAX = 150f; 033 034 public static float MAX_TARGET_RANGE = 3000f; 035 036 public static boolean isAttackSwarm(ShipAPI ship) { 037 return ship != null && ship.getVariant().getHullVariantId().equals(SwarmLauncherEffect.ATTACK_SWARM_VARIANT); 038 } 039 public static boolean isConstructionSwarm(ShipAPI ship) { 040 return ship != null && ship.getVariant().getHullVariantId().equals(SwarmLauncherEffect.CONSTRUCTION_SWARM_VARIANT); 041 } 042 public static boolean isReclamationSwarm(ShipAPI ship) { 043 return ship != null && ship.getVariant().getHullVariantId().equals(SwarmLauncherEffect.RECLAMATION_SWARM_VARIANT); 044 } 045 046 public static class SharedSwarmWingData { 047 public ShipAPI target = null; 048 } 049 050 public static class FlockingData { 051 public Vector2f loc; 052 public Vector2f vel; 053 public float minA; 054 public float maxA; 055 public float minR; 056 public float maxR; 057 public float repelAtAngleDist; 058 public float minC; 059 public float maxC; 060 public float attractWeight; 061 public float repelWeight; 062 public float cohesionWeight; 063 public float facing; 064 } 065 066 public static float PROB_ENABLE_OTHER_GROUP = 0.5f; 067 068 protected ShipwideAIFlags flags = new ShipwideAIFlags(); 069 protected ShipAPI ship; 070 071 protected IntervalUtil updateInterval = new IntervalUtil(0.5f, 1.5f); 072 protected IntervalUtil headingInterval = new IntervalUtil(0.5f, 1.5f); 073 protected IntervalUtil attackRangeMultInterval = new IntervalUtil(0.2f, 1.8f); 074 protected IntervalUtil reclamationReturnInterval = new IntervalUtil(0.2f, 1.8f); 075 076 protected float sinceTurnedOffFlash = 0f; 077 protected ShipAPI fabricator = null; 078 079 protected List<FlockingData> flockingData = new ArrayList<>(); 080 protected float desiredHeading = 0f; 081 protected float headingChangeRate = 0f; 082 protected float elapsedSincePrevHeadingUpdate = 0f; 083 protected float attackRangeMult = 1f; 084 085 protected IntervalUtil enableOtherWeaponInterval = new IntervalUtil(5f, 15f); 086 protected IntervalUtil priorityTargetPickerInterval = new IntervalUtil(1f, 3f); 087 protected float enableOtherWeaponDuration = 0f; 088 protected float elapsed = 0f; 089 090 protected boolean startedConstruction = false; 091 private SwarmConstructionData constructionData; 092 private ThreatShipConstructionScript constructionScript; 093 094 protected boolean attackSwarm = false; 095 protected boolean constructionSwarm = false; 096 protected boolean reclamationSwarm = false; 097 098 public ThreatSwarmAI(ShipAPI ship) { 099 this.ship = ship; 100 101 attackSwarm = isAttackSwarm(ship); 102 constructionSwarm = isConstructionSwarm(ship); 103 reclamationSwarm = isReclamationSwarm(ship); 104 105 doInitialSetup(); 106 107 updateInterval.forceIntervalElapsed(); 108 headingInterval.forceIntervalElapsed(); 109 attackRangeMultInterval.forceIntervalElapsed(); 110 priorityTargetPickerInterval.forceIntervalElapsed(); 111 } 112 113 public SharedSwarmWingData getShared() { 114 if (ship.getWing() == null) return new SharedSwarmWingData(); 115 116 String key = "SharedSwarmWingData"; 117 SharedSwarmWingData data = (SharedSwarmWingData) ship.getWing().getCustomData().get(key); 118 if (data == null) { 119 data = new SharedSwarmWingData(); 120 ship.getWing().getCustomData().put(key, data); 121 } 122 return data; 123 } 124 125 protected void doInitialSetup() { 126 if (attackSwarm) { 127 // 0: voltaic 128 // 1: unstable 129 // 2: kinetic 130 // 3: seeker 131 // 4: defabrication 132 133 toggleOn(0); 134 toggleOn(1); 135 toggleOff(2); 136 toggleOff(3); 137 toggleOff(4); 138 139 ship.giveCommand(ShipCommand.SELECT_GROUP, null, 6); 140 } 141 } 142 143 protected void toggleOn(int groupNum) { 144 List<WeaponGroupAPI> groups = ship.getWeaponGroupsCopy(); 145 if (groups.size() <= groupNum) return; 146 groups.get(groupNum).toggleOn(); 147 } 148 protected void toggleOff(int groupNum) { 149 List<WeaponGroupAPI> groups = ship.getWeaponGroupsCopy(); 150 if (groups.size() <= groupNum) return; 151 groups.get(groupNum).toggleOff(); 152 } 153 154 155 protected void advanceForSpecificSwarmType(float amount) { 156 if (attackSwarm) { 157 if (ship.isWingLeader()) { 158 priorityTargetPickerInterval.advance(amount); 159 if (priorityTargetPickerInterval.intervalElapsed()) { 160 pickPriorityTarget(); 161 } 162 } 163 164 attackRangeMultInterval.advance(amount * 0.1f); 165 if (attackRangeMultInterval.intervalElapsed()) { 166 updateAttackRangeMult(); 167 } 168 169 // 0: voltaic, always on 170 // 1: unstable, always on 171 // 2: kinetic 172 // 3: seeker 173 // 4: unused (was defab at some point) 174 if (enableOtherWeaponDuration > 0) { 175 enableOtherWeaponDuration -= amount; 176 if (enableOtherWeaponDuration <= 0) { 177 toggleOff(2); 178 toggleOff(3); 179 toggleOff(4); 180 } 181 } else { 182 //amount *= 10f; 183 boolean phaseMode = VoltaicDischargeOnFireEffect.isSwarmPhaseMode(ship); 184 185 186 enableOtherWeaponInterval.advance(amount * 5f); 187 if (enableOtherWeaponInterval.intervalElapsed()) { 188 if ((float) Math.random() < PROB_ENABLE_OTHER_GROUP) { 189 toggleOff(2); 190 toggleOff(3); 191 toggleOff(4); 192 193 ShipAPI target = (ShipAPI) flags.getCustom(AIFlags.MANEUVER_TARGET); 194 195// boolean targetShieldsFacingUs = false; 196// if (target != null) { 197// ShieldAPI targetShield = target.getShield(); 198// targetShieldsFacingUs = targetShield != null && 199// targetShield.isOn() && 200// Misc.isInArc(targetShield.getFacing(), Math.max(30f, targetShield.getActiveArc()), 201// target.getLocation(), ship.getLocation()); 202// } 203// if (targetShieldsFacingUs) { 204// toggleOn(2); 205// } else { 206// toggleOn(3); 207// } 208 209 210 // use Seeker only when it will be destroyed by using seeker, as a "final attack" 211 boolean useSeeker = ship.getHullLevel() < 0.22f; 212 boolean useKinetic = true; 213 if (target == null || target.isFighter()) { 214 useSeeker = false; 215 useKinetic = false; 216 } 217 218 if (useSeeker) { 219 toggleOn(3); 220 } else if (useKinetic) { 221 toggleOn(2); 222 } 223 224 enableOtherWeaponDuration = 0.5f + 0.5f * (float) Math.random(); 225 } 226 } 227 } 228 } 229 230 if (constructionSwarm) { 231 if (constructionData == null) { 232 RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship); 233 if (swarm != null) { 234 constructionData = (SwarmConstructionData) swarm.custom1; 235 } 236 } 237 if (constructionData != null) { 238 if (elapsed > constructionData.preConstructionTravelTime && !startedConstruction) { 239 startedConstruction = true; 240 RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship); 241 if (swarm != null) { 242 constructionScript = new ThreatShipConstructionScript( 243 constructionData.variantId, ship, 0f, constructionData.constructionTime); 244 Global.getCombatEngine().addPlugin(constructionScript); 245 } 246 } 247 } 248 } 249 250 if (reclamationSwarm) { 251 if (fabricator == null) { 252 reclamationReturnInterval.advance(amount); 253 if (reclamationReturnInterval.intervalElapsed()) { 254 int owner = ship.getOriginalOwner(); 255 CombatEngineAPI engine = Global.getCombatEngine(); 256 for (ShipAPI curr : engine.getShips()) { 257 if (curr == ship || curr.getOwner() != owner) continue; 258 if (curr.isHulk() || curr.getOwner() == 100) continue; 259 if (!ThreatCombatStrategyAI.isFabricator(curr)) continue; 260 261 float dist = Misc.getDistance(curr.getLocation(), ship.getLocation()); 262 if (dist < curr.getCollisionRadius() + 200f) { 263 // turn off flash and return to fabricator 264 RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship); 265 swarm.params.flashFrequency = 0f; 266 swarm.params.flashProbability = 0f; 267 268 fabricator = curr; 269 break; 270 } 271 } 272 } 273 } else { 274 sinceTurnedOffFlash += amount; 275 if (sinceTurnedOffFlash > 3f) { 276 CombatEngineAPI engine = Global.getCombatEngine(); 277 if (fabricator.isAlive()) { 278 fabricator.setCurrentCR(Math.min(1f, fabricator.getCurrentCR() + 0.01f * ship.getHullLevel())); 279 280 RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship); 281 RoilingSwarmEffect swarmFabricator = RoilingSwarmEffect.getSwarmFor(fabricator); 282 if (swarm != null && swarmFabricator != null) { 283 swarm.transferMembersTo(swarmFabricator, swarm.getNumActiveMembers()); 284 } 285 } 286 ship.setHitpoints(0f); 287 ship.setSpawnDebris(false); 288 engine.applyDamage(ship, ship.getLocation(), 100f, DamageType.ENERGY, 0f, true, false, ship, false); 289 } 290 } 291 } 292 } 293 294 protected void pickPriorityTarget() { 295 SharedSwarmWingData data = getShared(); 296 if (data.target != null && data.target.isAlive()) { 297 return; 298 } 299 300 WeightedRandomPicker<ShipAPI> picker = new WeightedRandomPicker<>(); 301 CombatEngineAPI engine = Global.getCombatEngine(); 302 int owner = ship.getOriginalOwner(); 303 304 for (ShipAPI curr : engine.getShips()) { 305 if (curr == ship) continue; 306 if (curr.isFighter()) continue; 307 if (curr.isHulk() || curr.getOwner() == 100) continue; 308 309 if (curr.getOwner() != owner && engine.isAwareOf(owner, curr)) { 310 float weight = getShipWeight(curr); 311 if (curr.isFrigate()) { 312 weight *= 0.0001f; 313 } 314 picker.add(curr, weight); 315 } 316 } 317 318 data.target = picker.pick(); 319 } 320 protected void updateAttackRangeMult() { 321 //attackRangeMult = 0.75f + 0.5f * (float) Math.random(); 322 attackRangeMult = 0.5f + 1f * (float) Math.random(); 323 } 324 325 @Override 326 public void advance(float amount) { 327 //if (true) return; 328 329 elapsed += amount; 330 advanceForSpecificSwarmType(amount); 331 332 updateInterval.advance(amount); 333 if (updateInterval.intervalElapsed()) { 334 updateFlockingData(); 335 } 336 337 headingInterval.advance(amount * 5f); 338 if (headingInterval.intervalElapsed()) { 339 computeDesiredHeading(); 340 elapsedSincePrevHeadingUpdate = 0f; 341 } 342 343 giveMovementCommands(); 344 345 elapsedSincePrevHeadingUpdate += amount; 346 } 347 348 protected void giveMovementCommands() { 349 if (constructionScript != null && constructionScript.getShip() != null) { 350 ship.giveCommand(ShipCommand.DECELERATE, null, 0); 351 return; 352 } 353 354 String source = "swarm_wingman_catch_up_speed_bonus"; 355 MutableShipStatsAPI stats = ship.getMutableStats(); 356 if (ship.isWingLeader() || ship.getWingLeader() == null) { 357 stats.getMaxSpeed().unmodifyMult(source); 358 stats.getAcceleration().unmodifyMult(source); 359 stats.getDeceleration().unmodifyMult(source); 360 } else { 361 ShipAPI leader = ship.getWingLeader(); 362 float dist = Misc.getDistance(ship.getLocation(), leader.getLocation()); 363 float mult = (dist - COHESION_RANGE_MAX * 0.5f - 364 ship.getCollisionRadius() * 0.5f - leader.getCollisionRadius() * 0.5f) / COHESION_RANGE_MAX; 365 if (mult < 0f) mult = 0f; 366 if (mult > 1f) mult = 1f; 367 stats.getMaxSpeed().modifyMult(source, 1f + .25f * mult); 368 stats.getAcceleration().modifyMult(source, 1f + 0.5f * mult); 369 stats.getDeceleration().modifyMult(source, 1f + 0.5f * mult); 370 } 371 372 float useHeading = desiredHeading; 373 //useHeading += headingChangeRate * elapsedSincePrevHeadingUpdate; 374 375 CombatEngineAPI engine = Global.getCombatEngine(); 376 engine.headInDirectionWithoutTurning(ship, useHeading, 10000); 377 Misc.turnTowardsFacingV2(ship, useHeading, 0f); 378 } 379 380 protected void computeDesiredHeading() { 381 382 Vector2f loc = ship.getLocation(); 383 Vector2f vel = ship.getVelocity(); 384 float facing = ship.getFacing(); 385 386 Vector2f total = new Vector2f(); 387 388 for (FlockingData curr : flockingData) { 389 float dist = Misc.getDistance(curr.loc, loc); 390 if (curr.maxR > 0 && dist < curr.maxR) { 391 float repelWeight = curr.repelWeight; 392 if (dist > curr.minR && curr.maxR > curr.minR) { 393 repelWeight = (dist - curr.minR) / (curr.maxR - curr.minR); 394 if (repelWeight > 1f) repelWeight = 1f; 395 repelWeight = 1f - repelWeight; 396 repelWeight *= curr.repelWeight; 397 } 398 399 Vector2f dir = Misc.getUnitVector(curr.loc, loc); 400 401 float distIntoRepel = curr.maxR - dist; 402 float repelAdjustmentAngle = 0f; 403 if (distIntoRepel < curr.repelAtAngleDist && curr.repelAtAngleDist > 0) { 404 float repelMult = (1f - distIntoRepel / curr.repelAtAngleDist); 405 repelAdjustmentAngle = 90f * repelMult; 406 repelWeight *= (1f - repelMult); 407 408 float repelAngle = Misc.getAngleInDegrees(dir); 409 float turnDir = Misc.getClosestTurnDirection(dir, vel); 410 repelAdjustmentAngle *= turnDir; 411 dir = Misc.getUnitVectorAtDegreeAngle(repelAngle + repelAdjustmentAngle); 412 } 413 414 dir.scale(repelWeight); 415 Vector2f.add(total, dir, total); 416 } 417 418 if (curr.maxA > 0 && dist < curr.maxA) { 419 float attractWeight = curr.attractWeight; 420 if (dist > curr.minA && curr.maxA > curr.minA) { 421 attractWeight = (dist - curr.minA) / (curr.maxA - curr.minA); 422 if (attractWeight > 1f) attractWeight = 1f; 423 attractWeight = 1f - attractWeight; 424 attractWeight *= curr.attractWeight; 425 } 426 427 Vector2f dir = Misc.getUnitVector(loc, curr.loc); 428 dir.scale(attractWeight); 429 Vector2f.add(total, dir, total); 430 } 431 432 if (curr.maxC > 0 && dist < curr.maxC) { 433 float cohesionWeight = curr.cohesionWeight; 434 if (dist > curr.minC && curr.maxC > curr.minC) { 435 cohesionWeight = (dist - curr.minC) / (curr.maxC - curr.minC); 436 if (cohesionWeight > 1f) cohesionWeight = 1f; 437 cohesionWeight = 1f - cohesionWeight; 438 cohesionWeight *= curr.cohesionWeight; 439 } 440 441 Vector2f dir = new Vector2f(curr.vel); 442 Misc.normalise(dir); 443 dir.scale(cohesionWeight); 444 Vector2f.add(total, dir, total); 445 } 446 } 447 448 if (total.length() <= 0) { 449 desiredHeading = ship.getFacing(); 450 headingChangeRate = ship.getAngularVelocity() * 0.5f; 451 } else { 452// Vector2f currDir = new Vector2f(vel); 453// Misc.normalise(currDir); 454// currDir.scale(total.length() * 0.25f); 455// Vector2f.add(total, currDir, total); 456 457 float prev = desiredHeading; 458 desiredHeading = Misc.getAngleInDegrees(total); 459 if (elapsedSincePrevHeadingUpdate > 0) { 460 headingChangeRate = Misc.getAngleDiff(prev, desiredHeading) / elapsedSincePrevHeadingUpdate; 461 } else { 462 headingChangeRate = ship.getAngularVelocity() * 0.5f; 463 } 464 } 465 } 466 467 468 protected void updateFlockingData() { 469 flockingData.clear(); 470 471 CombatEngineAPI engine = Global.getCombatEngine(); 472 473 if (constructionScript != null && constructionScript.getShip() != null) { 474 return; 475 } 476 477 478 int owner = ship.getOriginalOwner(); 479 FighterWingAPI wing = ship.getWing(); 480 if (wing == null) return; 481 482 Vector2f loc = ship.getLocation(); 483 boolean wingLeader = ship.isWingLeader(); 484 485 float attackRange = wing.getSpec().getAttackRunRange(); 486 attackRange *= attackRangeMult; 487 float radius = ship.getCollisionRadius() * 0.5f; 488 489 RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship); 490 if (swarm != null) { // && (constructionSwarm) { 491 radius = swarm.params.maxOffset; 492 } 493 494 SharedSwarmWingData shared = getShared(); 495 496 ShipAPI target = null; 497 float targetDist = Float.MAX_VALUE; 498 499 for (ShipAPI curr : engine.getShips()) { 500 if (curr == ship) continue; 501 if (curr.isFighter() && (!curr.isWingLeader() || curr.getOwner() == owner || !attackSwarm)) continue; 502 503 // just avoid everything - looking for a clear area 504 if (constructionSwarm) { 505 float currRadius = curr.getCollisionRadius() * 2f; 506 FlockingData data = new FlockingData(); 507 data.facing = curr.getFacing(); 508 data.loc = curr.getLocation(); 509 data.vel = curr.getVelocity(); 510 data.attractWeight = 0f; 511 data.repelWeight = getShipWeight(curr) * 1f; 512 data.minA = 0f; 513 data.maxA = 0f; 514 data.minR = radius + currRadius; 515 data.maxR = radius + currRadius + Math.min(100f, currRadius * 1f); 516 data.repelAtAngleDist = (data.maxR - data.minR) * 0.5f; 517 flockingData.add(data); 518 continue; 519 } 520 521 if (curr.isHulk() || curr.getOwner() == 100) continue; 522 523 // return to Fabricator Units, ignore other ships 524 if (reclamationSwarm) { 525 if (!ThreatCombatStrategyAI.isFabricator(curr) || curr.getOwner() != owner) continue; 526 float currRadius = curr.getCollisionRadius() * 0.5f; 527 FlockingData data = new FlockingData(); 528 data.facing = curr.getFacing(); 529 data.loc = curr.getLocation(); 530 data.vel = curr.getVelocity(); 531 data.attractWeight = getShipWeight(curr) * (1f - ship.getCurrentCR()); 532 data.repelWeight = data.attractWeight * 10f; 533 data.minA = radius + currRadius; 534 data.maxA = 1000000f; 535 data.minR = radius + currRadius; 536 data.maxR = radius + currRadius + 100f; 537 data.repelAtAngleDist = (data.maxR - data.minR) * 0.5f; 538 flockingData.add(data); 539 continue; 540 } 541 542 543 float currRadius = curr.getCollisionRadius() * 0.5f; 544 545 if (curr.getOwner() != owner && engine.isAwareOf(owner, curr)) { 546 FlockingData data = new FlockingData(); 547 data.facing = curr.getFacing(); 548 data.loc = curr.getLocation(); 549 data.vel = curr.getVelocity(); 550 data.attractWeight = getShipWeight(curr); 551 data.repelWeight = data.attractWeight * 10f; 552 data.minA = attackRange + radius + currRadius; 553 data.maxA = 1000000f; 554 data.repelAtAngleDist = Math.min(attackRange * 0.5f, 400f); 555 data.minR = radius + currRadius; 556 data.maxR = attackRange + radius + currRadius; 557 if (curr == shared.target) { 558 //boolean inFront = Misc.isInArc(curr.getFacing(), 90f, curr.getLocation(), ship.getLocation()); 559 float angleDiffFromFront = Misc.getAngleDiff(curr.getFacing(), Misc.getAngleInDegrees(curr.getLocation(), ship.getLocation())); 560 float maxDiff = 45f; 561 if (angleDiffFromFront < maxDiff) { 562// data.minR *= 2f - angleDiffFromFront / maxDiff; 563// data.maxR *= 2f - angleDiffFromFront / maxDiff; 564 data.minR = data.minR + (data.maxR - data.minR) * 0.5f; 565 data.minR += 500f * (1f - angleDiffFromFront / maxDiff); 566 data.maxR += 500f * (1f - angleDiffFromFront / maxDiff); 567 data.repelAtAngleDist += 500f * (1f - angleDiffFromFront / maxDiff); 568 } 569 data.attractWeight += 200f; 570 data.repelWeight += 600f; 571 } 572 flockingData.add(data); 573 574 float dist = Misc.getDistance(loc, curr.getLocation()); 575 if (dist < targetDist && dist < MAX_TARGET_RANGE) { 576 target = curr; 577 targetDist = dist; 578 } 579 580 // add extra attractor behind the enemy ship to encourage going around and not hanging out in one spot 581 if ((curr.isDestroyer() || curr.isCruiser() || curr.isCapital()) && curr == shared.target) { 582 data = new FlockingData(); 583 //Vector2f dir = Misc.getUnitVector(ship.getLocation(), curr.getLocation()); 584 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(curr.getFacing() + 180f); 585 dir.scale(curr.getCollisionRadius() * 0.5f + attackRange * attackRangeMult); 586 data.facing = curr.getFacing(); 587 data.loc = Vector2f.add(curr.getLocation(), dir, new Vector2f()); 588 data.vel = curr.getVelocity(); 589 data.attractWeight = getShipWeight(curr) * 1f; 590 data.minA = attackRange + radius + currRadius; 591 data.maxA = 1000000f; 592 if (curr == shared.target) { 593 data.attractWeight += 200f; 594 } 595 flockingData.add(data); 596 } 597 598 599 } else if (curr.getOwner() == owner) { 600 FlockingData data = new FlockingData(); 601 data.facing = curr.getFacing(); 602 data.loc = curr.getLocation(); 603 data.vel = curr.getVelocity(); 604 data.attractWeight = getShipWeight(curr) * 0.1f; 605 data.repelWeight = data.attractWeight * 50f; 606 data.minA = attackRange + radius + currRadius; 607 data.maxA = 1000000f; 608 data.minR = radius + currRadius; 609 data.maxR = attackRange * 0.75f + radius + currRadius; 610 data.repelAtAngleDist = Math.min(attackRange * 0.5f, 400f); 611 flockingData.add(data); 612 } 613 } 614 615 if (target != null) { 616 flags.setFlag(AIFlags.MANEUVER_TARGET, 3f, target); 617 } else { 618 flags.unsetFlag(AIFlags.MANEUVER_TARGET); 619 } 620 621 if (flockingData.isEmpty() && !constructionSwarm) { 622 FlockingData data = new FlockingData(); 623 data.facing = 0f; 624 data.loc = new Vector2f(); 625 data.vel = new Vector2f(); 626 data.attractWeight = 5f; 627 data.repelWeight = data.attractWeight * 10f; 628 data.minA = 1000f; 629 data.maxA = 1000000f; 630 data.minR = 1000f; 631 data.maxR = 3000f; 632 data.repelAtAngleDist = 1000f; 633 flockingData.add(data); 634 } 635 636// if (true) { 637// FlockingData data = new FlockingData(); 638// data.facing = 0f; 639// data.loc = new Vector2f(8000f, -18000f); 640// data.vel = new Vector2f(); 641// data.attractWeight = 1000f; 642// data.minA = 1000f; 643// data.maxA = 1000000f; 644// flockingData.add(data); 645// } 646 647 //RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship); 648 if (swarm != null && swarm.params.flockingClass != null && swarm.attachedTo != null) { 649 for (RoilingSwarmEffect curr : RoilingSwarmEffect.getFlockingMap().getList(swarm.params.flockingClass)) { 650 if (curr == swarm) continue; 651 if (curr.attachedTo == ship || curr.attachedTo == null || 652 curr.attachedTo.getOwner() != owner) { 653 continue; 654 } 655 656 if (swarm.params.flockingClass.equals(curr.params.flockingClass)) { 657 // avoid other construction swarms - looking for a clear area 658 if (constructionSwarm) { 659 float currRadius = curr.params.maxOffset; 660 FlockingData data = new FlockingData(); 661 data.facing = curr.attachedTo.getFacing(); 662 data.loc = curr.attachedTo.getLocation(); 663 data.vel = curr.attachedTo.getVelocity(); 664 data.attractWeight = 0f; 665 data.repelWeight = 8f; 666 data.minA = 0f; 667 data.maxA = 0f; 668 data.minR = radius + currRadius; 669 data.maxR = radius + currRadius + Math.min(100f, currRadius * 1f); 670 data.repelAtAngleDist = (data.maxR - data.minR) * 0.5f; 671 flockingData.add(data); 672 continue; 673 } 674 675 676 boolean sameWing = wing == ((ShipAPI)curr.attachedTo).getWing(); 677 boolean otherWingLeader = ((ShipAPI)curr.attachedTo).isWingLeader(); 678 679 // actually - make the leader wait a bit, otherwise they never catch up 680 // or not 681 if (wingLeader && sameWing) continue; // others catch up/line up on leader 682 683 if (!sameWing) { 684 float dist = Misc.getDistance(loc, curr.attachedTo.getLocation()); 685 if (dist > ATTRACTOR_RANGE_MAX + 500f) continue; 686 } 687 688 689 float currRadius = curr.attachedTo.getCollisionRadius() * 0.5f; 690 FlockingData data = new FlockingData(); 691 data.facing = curr.attachedTo.getFacing(); 692 data.loc = curr.attachedTo.getLocation(); 693 data.vel = curr.attachedTo.getVelocity(); 694 data.attractWeight = 1f; 695 data.repelWeight = 10f; 696 data.cohesionWeight = 1f; 697 if (sameWing) { 698 if (wingLeader) { 699 data.attractWeight = 0.1f; 700 } else { 701 data.attractWeight = 3f; 702 } 703 data.minA = 0f + radius + currRadius; 704 data.maxA = ATTRACTOR_RANGE_MAX_SAME_WING + radius + currRadius; 705 } else { 706 data.minA = 0f + radius + currRadius; 707 data.maxA = ATTRACTOR_RANGE_MAX + radius + currRadius; 708 } 709 data.minR = REPEL_RANGE_MIN + radius + currRadius; 710 data.maxR = REPEL_RANGE_MAX + radius + currRadius; 711 if (wingLeader && otherWingLeader) { 712 data.maxR = ATTRACTOR_RANGE_MAX + radius + currRadius; 713 } 714 data.minC = COHESION_RANGE_MIN + radius + currRadius; 715 data.maxC = COHESION_RANGE_MAX + radius + currRadius; 716 if (reclamationSwarm) { 717 data.minR *= 0.33f; 718 data.maxR *= 0.33f; 719 } 720 flockingData.add(data); 721 } 722 } 723 } 724 } 725 726 727 728 729 730 731 732 @Override 733 public ShipwideAIFlags getAIFlags() { 734 return flags; 735 } 736 737 public static float getShipWeight(ShipAPI ship) { 738 return getShipWeight(ship, true); 739 } 740 public static float getShipWeight(ShipAPI ship, boolean adjustForNonCombat) { 741 boolean nonCombat = ship.isNonCombat(false); 742 float weight = 0; 743 switch (ship.getHullSize()) { 744 case CAPITAL_SHIP: weight += 8; break; 745 case CRUISER: weight += 4; break; 746 case DESTROYER: weight += 2; break; 747 case FRIGATE: weight += 1; break; 748 case FIGHTER: weight += 1; break; 749 } 750 if (nonCombat && adjustForNonCombat) weight *= 0.25f; 751 if (ship.isDrone()) weight *= 0.1f; 752 return weight; 753 } 754 755 public void setDoNotFireDelay(float amount) {} 756 public void forceCircumstanceEvaluation() {} 757 public boolean needsRefit() { return false; } 758 public void cancelCurrentManeuver() {} 759 public ShipAIConfig getConfig() { return null; } 760} 761 762 763 764 765 766 767 768 769 770 771 772 773