001package com.fs.starfarer.api.impl.combat.threat; 002 003import java.util.ArrayList; 004import java.util.LinkedHashSet; 005import java.util.List; 006import java.util.Set; 007 008import org.lwjgl.util.vector.Vector2f; 009 010import com.fs.starfarer.api.Global; 011import com.fs.starfarer.api.combat.AssignmentTargetAPI; 012import com.fs.starfarer.api.combat.BattleObjectiveAPI; 013import com.fs.starfarer.api.combat.CombatAssignmentType; 014import com.fs.starfarer.api.combat.CombatEngineAPI; 015import com.fs.starfarer.api.combat.CombatFleetManagerAPI; 016import com.fs.starfarer.api.combat.CombatFleetManagerAPI.AssignmentInfo; 017import com.fs.starfarer.api.combat.CombatTaskManagerAPI; 018import com.fs.starfarer.api.combat.DeployedFleetMemberAPI; 019import com.fs.starfarer.api.combat.ShipAIPlugin; 020import com.fs.starfarer.api.combat.ShipAPI; 021import com.fs.starfarer.api.combat.ShipwideAIFlags.AIFlags; 022import com.fs.starfarer.api.fleet.FleetGoal; 023import com.fs.starfarer.api.impl.campaign.ids.HullMods; 024import com.fs.starfarer.api.impl.campaign.ids.Tags; 025import com.fs.starfarer.api.util.IntervalUtil; 026import com.fs.starfarer.api.util.Misc; 027 028/** 029 * Doesn't care about command points etc, just functions in a different way. May use command points/tasks/etc 030 * to accomplish its goals, just as an implementation detail, but conceptually it's fundamentally different from how 031 * human-type fleets work. 032 * 033 * @author Alex 034 * 035 */ 036public class ThreatCombatStrategyAI { 037 038 public static float SND_BASE = 60f; 039 public static float SND_TIMER = 60f; 040 public static float SND_FRACTION = 0.5f; 041 042 protected boolean playerSide; 043 protected CombatTaskManagerAPI taskManager; 044 protected CombatFleetManagerAPI fleetManager; 045 protected CombatFleetManagerAPI enemyFleetManager; 046 protected int owner; 047 protected boolean allyMode = false; 048 049 protected IntervalUtil everySecond = new IntervalUtil(0.8f, 1.2f); 050 protected CombatEngineAPI engine; 051 protected float mw, mh; 052 053 protected boolean abort = false; 054 055 protected AssignmentInfo mainDefend1; 056 protected AssignmentInfo mainDefend2; 057 058 protected float captureAllTimeRemaining; 059 protected boolean gaveInitialOrders = false; 060 061 protected float untilSNDOnSkirmishUnits; 062 063 064 public ThreatCombatStrategyAI(int owner) { 065 engine = Global.getCombatEngine(); 066 this.owner = owner; 067 playerSide = owner == 0; 068 allyMode = playerSide; 069 //allyMode = false; 070 fleetManager = engine.getFleetManager(owner); 071 taskManager = fleetManager.getTaskManager(allyMode); 072 taskManager.getCommandPointsStat().modifyFlat("ThreatCombatStrategyAI", 1000000000); 073 074 enemyFleetManager = engine.getFleetManager(owner == 0 ? 1 : 0); 075 076 if (fleetManager.getGoal() == FleetGoal.ESCAPE || enemyFleetManager.getGoal() == FleetGoal.ESCAPE) { 077 abort = true; 078 } else { 079 if (fleetManager.getAdmiralAI() != null) { 080 taskManager.clearTasks(); 081 fleetManager.getAdmiralAI().setNoOrders(true); 082 } 083 } 084 085 mw = engine.getMapWidth(); 086 mh = engine.getMapHeight(); 087 088 resetSNDTimer(); 089 } 090 091 protected void resetSNDTimer() { 092 untilSNDOnSkirmishUnits = SND_TIMER * (0.75f + (float) Math.random() * 0.5f); 093 untilSNDOnSkirmishUnits += SND_BASE; 094 } 095 096 protected void manageSND(float amount) { 097 untilSNDOnSkirmishUnits -= amount; 098 if (captureAllTimeRemaining > 0) return; 099 if (untilSNDOnSkirmishUnits <= 0) { 100 for (DeployedFleetMemberAPI member : fleetManager.getDeployedCopyDFM()) { 101 ShipAPI ship = member.getShip(); 102 if (ship == null || ship.getAI() == null) continue; 103 if (ship.hasTag(ThreatShipConstructionScript.SHIP_UNDER_CONSTRUCTION)) continue; 104 105 if (!ship.getHullSpec().hasTag(Tags.THREAT_SKIRMISH)) continue; 106 if ((float) Math.random() > SND_FRACTION) continue; 107 108 cancelOrders(member, false); 109 ship.getAIFlags().setFlag(AIFlags.IGNORES_ORDERS, SND_BASE * (0.75f + (float) Math.random() * 0.5f)); 110 } 111 resetSNDTimer(); 112 } 113 } 114 115 protected void giveInitialOrders() { 116 captureAllTimeRemaining = 80f; 117 for (BattleObjectiveAPI curr : engine.getObjectives()) { 118 taskManager.createAssignment(CombatAssignmentType.CAPTURE, curr, false); 119 } 120 } 121 122 123 public void advance(float amount) { 124 //if (true) return; 125 if (abort) return; 126 if (engine.isPaused()) return; 127 128 captureAllTimeRemaining -= amount; 129 130 manageSND(amount); 131 132 everySecond.advance(amount); 133 if (everySecond.intervalElapsed()) { 134 // if non-threat ships are deployed from this fleetManager, don't want to be doing any Threat things 135 List<DeployedFleetMemberAPI> deployed = fleetManager.getDeployedCopyDFM(); 136 if (deployed.isEmpty()) return; 137 138 boolean someMatching = false; 139 for (DeployedFleetMemberAPI member : deployed) { 140 if (!member.isFighterWing() && member.getShip() != null && 141 member.isAlly() == allyMode && 142 !member.getShip().getVariant().hasHullMod(HullMods.THREAT_HULLMOD)) { 143 abort = true; 144 return; 145 } else if (!member.isFighterWing() && member.getShip() != null && 146 member.isAlly() == allyMode) { 147 someMatching = true; 148 } 149 } 150 151 if (!someMatching) return; 152 153 if (!gaveInitialOrders) { 154 giveInitialOrders(); 155 gaveInitialOrders = true; 156 } 157 158 float sign = 1f; 159 if (owner == 0) sign = -1; 160 Vector2f enemyCom = getEnemyCenterOfMass(); 161 Vector2f fabricatorLoc = new Vector2f(0, 0 + mh * 0.33f * sign); 162 Vector2f axis = Misc.getUnitVector(fabricatorLoc, enemyCom); 163 Vector2f perp = new Vector2f(axis.y, -axis.x); 164 float distToEnemyCom = Misc.getDistance(fabricatorLoc, enemyCom); 165 166 //float hiveOffset = 2000f; 167 float hiveOffset = distToEnemyCom - 6000f; 168 if (Math.abs(hiveOffset) < 2000f) { 169 hiveOffset = Math.signum(hiveOffset) * 2000f; 170 } 171 if (hiveOffset > 2000) hiveOffset= 2000f; 172 Vector2f hiveLoc = new Vector2f(axis); 173 hiveLoc.scale(hiveOffset); 174 Vector2f.add(hiveLoc, fabricatorLoc, hiveLoc); 175 176 float enemyWeightNearFabricatorLoc = Misc.countEnemyWeightInArcAroundLocation( 177 owner, fabricatorLoc, 0f, 360f, 3000f, null, true, true); 178 179 int fabricators = 0; 180 int hives = 0; 181 for (DeployedFleetMemberAPI member : fleetManager.getDeployedCopyDFM()) { 182 ShipAPI ship = member.getShip(); 183 if (ship == null || ship.getAI() == null) continue; 184 if (isFabricator(ship)) { 185 fabricators++; 186 } 187 if (isHive(ship)) { 188 hives++; 189 } 190 } 191 192 float defDist = distToEnemyCom - 2000f; 193 if (enemyWeightNearFabricatorLoc >= 3f) { 194 defDist = Math.min(defDist, 3000f); 195 } 196 if (fabricators == 0 && hives == 0) { 197 if (mainDefend1 != null) { 198 taskManager.removeAssignment(mainDefend1); 199 mainDefend1 = null; 200 } 201 if (mainDefend2 != null) { 202 taskManager.removeAssignment(mainDefend2); 203 mainDefend2 = null; 204 } 205 } else if (defDist < 2000f) { 206 if (mainDefend1 != null) { 207 float dist = Misc.getDistance(mainDefend1.getTarget().getLocation(), fabricatorLoc); 208 if (dist > 1000f) { 209 taskManager.removeAssignment(mainDefend1); 210 mainDefend1 = null; 211 } 212 } 213 214 if (mainDefend2 != null) { 215 taskManager.removeAssignment(mainDefend2); 216 mainDefend2 = null; 217 } 218 219 if (mainDefend1 == null) { 220 AssignmentTargetAPI wp = taskManager.createWaypoint2(fabricatorLoc, allyMode); 221 mainDefend1 = taskManager.createAssignment(CombatAssignmentType.DEFEND, wp, false); 222 } 223 224 //Global.getCombatEngine().getCombatUI().addMessage(0, "Threat aggro mode engaged!"); 225 // enemies close to fabricators - attack! 226 for (DeployedFleetMemberAPI member : fleetManager.getDeployedCopyDFM()) { 227 ShipAPI ship = member.getShip(); 228 if (ship == null || ship.getAI() == null) continue; 229 if (isCombatUnit(ship) && ship.getAI() instanceof ShipAIPlugin) { 230// ShipAIPlugin ai = (ShipAIPlugin) ship.getAI(); 231// ShipAIConfig config = ai.getConfig(); 232// config.personalityOverride = Personalities.RECKLESS; 233// config.alwaysStrafeOffensively = true; 234// config.backingOffWhileNotVentingAllowed = false; 235// config.turnToFaceWithUndamagedArmor = false; 236// config.burnDriveIgnoreEnemies = true; 237 ship.getAIFlags().setFlag(AIFlags.DO_NOT_BACK_OFF, 2f); 238 ship.getAIFlags().setFlag(AIFlags.DO_NOT_VENT, 2f); 239 ship.getAIFlags().setFlag(AIFlags.IGNORES_ORDERS, 2f); 240 } 241 } 242 } else { 243 Vector2f defLoc = new Vector2f(axis); 244 defLoc.scale(defDist); 245 Vector2f.add(defLoc, fabricatorLoc, defLoc); 246 247 Vector2f defLoc1 = new Vector2f(perp); 248 defLoc1.scale(1000f); 249 Vector2f.add(defLoc1, defLoc, defLoc1); 250 251 Vector2f defLoc2 = new Vector2f(perp); 252 defLoc2.scale(-1000f); 253 Vector2f.add(defLoc2, defLoc, defLoc2); 254 255 256 if (mainDefend1 != null) { 257 float dist = Misc.getDistance(mainDefend1.getTarget().getLocation(), defLoc1); 258 if (dist > 1000f) { 259 taskManager.removeAssignment(mainDefend1); 260 mainDefend1 = null; 261 } 262 } 263 if (mainDefend2 != null) { 264 float dist = Misc.getDistance(mainDefend2.getTarget().getLocation(), defLoc2); 265 if (dist > 1000f) { 266 taskManager.removeAssignment(mainDefend2); 267 mainDefend2 = null; 268 } 269 } 270 271 if (mainDefend1 == null) { 272 AssignmentTargetAPI wp = taskManager.createWaypoint2(defLoc1, allyMode); 273 mainDefend1 = taskManager.createAssignment(CombatAssignmentType.DEFEND, wp, false); 274 } 275 276 if (mainDefend2 == null) { 277 AssignmentTargetAPI wp = taskManager.createWaypoint2(defLoc2, allyMode); 278 mainDefend2 = taskManager.createAssignment(CombatAssignmentType.DEFEND, wp, false); 279 } 280 } 281 282 if (captureAllTimeRemaining <= 0f) { 283 float axisAngle = Misc.getAngleInDegrees(axis); 284 List<AssignmentTargetAPI> withCaptures = new ArrayList<>(); 285 for (AssignmentInfo info : taskManager.getAllAssignments()) { 286 if (info.getTarget() == null) continue; 287 if (info.getType() == CombatAssignmentType.CAPTURE || info.getType() == CombatAssignmentType.CONTROL) { 288 if ((fabricators == 0 && hives == 0) || 289 !wantsToControl(fabricatorLoc, axisAngle, distToEnemyCom, info.getTarget().getLocation())) { 290 taskManager.removeAssignment(info); 291 } else { 292 withCaptures.add(info.getTarget()); 293 } 294 } 295 } 296 297 if (fabricators > 0 || hives > 0) { 298 for (BattleObjectiveAPI curr : engine.getObjectives()) { 299 if (withCaptures.contains(curr)) continue; 300 if (wantsToControl(fabricatorLoc, axisAngle, distToEnemyCom, curr.getLocation())) { 301 taskManager.createAssignment(CombatAssignmentType.CAPTURE, curr, false); 302 } 303 } 304 } 305 } 306 307 Set<DeployedFleetMemberAPI> escorted = new LinkedHashSet<>(); 308 309 for (DeployedFleetMemberAPI member : fleetManager.getDeployedCopyDFM()) { 310 ShipAPI ship = member.getShip(); 311 if (ship == null || ship.getAI() == null) continue; 312 if (ship.hasTag(ThreatShipConstructionScript.SHIP_UNDER_CONSTRUCTION)) continue; 313 314 float enemyCheckRange = 3000f; 315 if (isHive(ship)) { 316 enemyCheckRange = 1000f; 317 } 318 319 float enemyWeight = Misc.countEnemyWeightInArcAroundLocation(owner, ship.getLocation(), 0f, 360f, enemyCheckRange, null, true, true); 320 float shipWeight = Misc.getShipWeight(ship, true); 321 322 boolean enemiesNear = enemyWeight >= shipWeight * 0.5f; 323 324 if (isFabricator(ship)) { 325 float min = 0f; 326 float max = 1000f; 327 if (enemiesNear) { 328 min = 1000f; 329 max = 2000f; 330 } 331 giveMovementOrder(member, fabricatorLoc, min, max); 332 } else if (isHive(ship)) { 333 if (enemiesNear) { 334 cancelOrders(member, true); 335 } else { 336 giveMovementOrder(member, hiveLoc, 1000f, 1500f); 337 } 338 } else if (isOverseer(ship)) { 339 DeployedFleetMemberAPI closest = null; 340 float minDist = Float.MAX_VALUE; 341 for (DeployedFleetMemberAPI other : fleetManager.getDeployedCopyDFM()) { 342 if (other == member || other.getShip() == null || escorted.contains(other)) continue; 343 344 float extraDistScore = 0f; 345 if (other.getShip().isCruiser() && isCombatUnit(other.getShip())) { 346 extraDistScore = 0f; 347 } else if (other.getShip().isDestroyer() && isCombatUnit(other.getShip())) { 348 extraDistScore = 100000f; 349 } else if (other.getShip().isFrigate() && isCombatUnit(other.getShip())) { 350 if (enemiesNear) { 351 extraDistScore = 500000f; 352 } else { 353 extraDistScore = 1000000f; 354 } 355 } else if (isHive(other.getShip())) { 356 if (enemiesNear) { 357 extraDistScore = 100000f; 358 } else { 359 extraDistScore = 10000000f; 360 } 361 } else { 362 continue; 363 } 364 float dist = Misc.getDistance(member.getLocation(), other.getLocation()) + extraDistScore; 365 if (dist < minDist) { 366 closest = other; 367 minDist = dist; 368 } 369 } 370 if (closest != null) { 371 member.getShip().getAIFlags().setFlag(AIFlags.TIMID_ESCORT, 2f); 372 member.getShip().getAIFlags().setFlag(AIFlags.ESCORT_RANGE_MODIFIER, 2f, 300f); 373 escort(member, closest); 374 } 375 } 376 377 } 378 379 cleanUpEmptyAssignments(); 380 } 381 } 382 383 protected boolean wantsToControl(Vector2f fabricatorLoc, float axisAngle, float distToEnemyCom, Vector2f objectiveLoc) { 384 float dist = Misc.getDistance(fabricatorLoc, objectiveLoc); 385 float angle = Misc.getAngleInDegrees(fabricatorLoc, objectiveLoc); 386 float angleDiff = Misc.getAngleDiff(axisAngle, angle); 387 388 float enemyWeight = Misc.countEnemyWeightInArcAroundLocation(owner, objectiveLoc, 0f, 360f, 3000f, null, true, true); 389 if (enemyWeight <= 0f && angleDiff > 30f) return true; 390 //return !(dist > 2000 && (dist > distToEnemyCom || angleDiff > 45f)); 391 return dist < 5000 || (dist < distToEnemyCom && angleDiff < 45f); 392 } 393 394 395 protected void cancelOrders(DeployedFleetMemberAPI member, boolean withSearchAndDestroy) { 396 AssignmentInfo curr = taskManager.getAssignmentFor(member.getShip()); 397 if (curr != null) { 398 taskManager.removeAssignment(curr); 399 } 400 if (withSearchAndDestroy) { 401 taskManager.orderSearchAndDestroy(member, false); 402 } 403 } 404 405 protected void escort(DeployedFleetMemberAPI member, DeployedFleetMemberAPI target) { 406 if (member.getShip() == null) return; 407 AssignmentInfo curr = taskManager.getAssignmentFor(member.getShip()); 408 if (curr != null && curr.getType() == CombatAssignmentType.LIGHT_ESCORT && 409 taskManager.getAssignmentTargetFor(member.getShip()) == target) { 410 return; 411 } 412 413 AssignmentInfo info = taskManager.createAssignment(CombatAssignmentType.LIGHT_ESCORT, target, false); 414 taskManager.setAssignmentWeight(info, 0f); 415 taskManager.giveAssignment(member, info, false); 416 } 417 418 protected void giveMovementOrder(DeployedFleetMemberAPI member, Vector2f loc, float minDist, float maxDist) { 419 AssignmentInfo curr = taskManager.getAssignmentFor(member.getShip()); 420 boolean needToMakeAssignment = curr == null || curr.getTarget() == null || Misc.getDistance(curr.getTarget().getLocation(), loc) > 100f; 421 422 float dist = Misc.getDistance(member.getLocation(), loc); 423 if (dist < minDist) { 424 if (curr != null && (curr.getType() == CombatAssignmentType.RALLY_CIVILIAN)) { 425 taskManager.removeAssignment(curr); 426 } 427 } 428 429 boolean needToLeash = false; 430 needToLeash |= curr == null && dist > minDist; 431 needToLeash |= curr != null && dist > maxDist; 432 433 if (needToMakeAssignment && needToLeash) { 434 AssignmentTargetAPI wp = taskManager.createWaypoint2(loc, allyMode); 435 AssignmentInfo task = taskManager.createAssignment(CombatAssignmentType.RALLY_CIVILIAN, wp, false); 436 taskManager.giveAssignment(member, task, false); 437 } 438 } 439 440 public void cleanUpEmptyAssignments() { 441 taskManager.reassign(); // so assigned members isn't empty 442 List<AssignmentInfo> remove = new ArrayList<>(); 443 for (AssignmentInfo curr : taskManager.getAllAssignments()) { 444 if (curr.getType() == CombatAssignmentType.CONTROL) continue; 445 if (curr.getType() == CombatAssignmentType.CAPTURE) continue; 446 if (curr.getType() == CombatAssignmentType.DEFEND) continue; 447 if (curr.getAssignedMembers().isEmpty()) { 448 remove.add(curr); 449 } 450 } 451 for (AssignmentInfo curr : remove) { 452 taskManager.removeAssignment(curr); 453 } 454 455 taskManager.clearEmptyWaypoints(); 456 } 457 458 protected Vector2f getEnemyCenterOfMass() { 459 Vector2f com = new Vector2f(); 460 float weight = 0; 461 for (DeployedFleetMemberAPI member : enemyFleetManager.getDeployedCopyDFM()) { 462 if (member.isFighterWing()) continue; 463 if (member.getShip() == null) continue; 464 if (!engine.isAwareOf(owner, member.getShip())) continue; 465 466 Vector2f.add(member.getLocation(), com, com); 467 weight++; // maybe feels better than scaling weight by ship size etc 468 } 469 if (weight > 0) { 470 com.scale(1f / weight); 471 } 472 return com; 473 } 474 475 476 public static boolean isCombatUnit(ShipAPI ship) { 477 return ship.getHullSpec().hasTag(Tags.THREAT_COMBAT); 478 } 479 public static boolean isOverseer(ShipAPI ship) { 480 return ship.getHullSpec().hasTag(Tags.THREAT_OVERSEER); 481 } 482 public static boolean isHive(ShipAPI ship) { 483 return ship.getHullSpec().hasTag(Tags.THREAT_HIVE); 484 } 485 public static boolean isFabricator(ShipAPI ship) { 486 return ship.getHullSpec().hasTag(Tags.THREAT_FABRICATOR); 487 //return ship.getSystem() != null && ship.getSystem().getId().equals(ShipSystems.CONSTRUCTION_SWARM); 488 } 489} 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512