001package com.fs.starfarer.api.impl.campaign.procgen.themes; 002 003import org.lwjgl.util.vector.Vector2f; 004 005import com.fs.starfarer.api.EveryFrameScript; 006import com.fs.starfarer.api.Global; 007import com.fs.starfarer.api.Script; 008import com.fs.starfarer.api.campaign.CampaignFleetAPI; 009import com.fs.starfarer.api.campaign.CustomCampaignEntityAPI; 010import com.fs.starfarer.api.campaign.CustomEntitySpecAPI; 011import com.fs.starfarer.api.campaign.FleetAssignment; 012import com.fs.starfarer.api.campaign.SectorEntityToken; 013import com.fs.starfarer.api.campaign.SectorEntityToken.VisibilityLevel; 014import com.fs.starfarer.api.campaign.ai.FleetAIFlags; 015import com.fs.starfarer.api.campaign.ai.FleetAssignmentDataAPI; 016import com.fs.starfarer.api.campaign.ai.ModularFleetAIAPI; 017import com.fs.starfarer.api.campaign.econ.MarketAPI; 018import com.fs.starfarer.api.campaign.rules.MemoryAPI; 019import com.fs.starfarer.api.impl.campaign.fleets.RouteManager; 020import com.fs.starfarer.api.impl.campaign.ids.MemFlags; 021import com.fs.starfarer.api.impl.campaign.ids.Tags; 022import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.MarketCMD; 023import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.Objectives; 024import com.fs.starfarer.api.plugins.BuildObjectiveTypePicker; 025import com.fs.starfarer.api.plugins.BuildObjectiveTypePicker.BuildObjectiveParams; 026import com.fs.starfarer.api.util.IntervalUtil; 027import com.fs.starfarer.api.util.Misc; 028 029public abstract class BaseAssignmentAI implements EveryFrameScript { 030 031 public interface FleetActionDelegate { 032 boolean canRaid(CampaignFleetAPI fleet, MarketAPI market); 033 String getRaidApproachText(CampaignFleetAPI fleet, MarketAPI market); 034 String getRaidActionText(CampaignFleetAPI fleet, MarketAPI market); 035 void performRaid(CampaignFleetAPI fleet, MarketAPI market); 036 037 String getRaidPrepText(CampaignFleetAPI fleet, SectorEntityToken from); 038 String getRaidInSystemText(CampaignFleetAPI fleet); 039 String getRaidDefaultText(CampaignFleetAPI fleet); 040 } 041 042 protected CampaignFleetAPI fleet; 043 protected Boolean done = null; 044 protected Boolean giveInitial = true; 045 protected FleetActionDelegate delegate = null; 046 047 public BaseAssignmentAI() { 048 } 049 050 public BaseAssignmentAI(CampaignFleetAPI fleet) { 051 this.fleet = fleet; 052 giveInitialAssignments(); 053 } 054 055 public FleetActionDelegate getDelegate() { 056 return delegate; 057 } 058 059 public void setDelegate(FleetActionDelegate delegate) { 060 this.delegate = delegate; 061 } 062 063 protected abstract void giveInitialAssignments(); 064 protected abstract void pickNext(); 065 066 public void advance(float amount) { 067 if (fleet.getCurrentAssignment() == null) { 068 pickNext(); 069 } 070 } 071 072 073 public boolean isDone() { 074 return done != null; 075 } 076 077 public void setDone() { 078 done = true; 079 } 080 081 public boolean runWhilePaused() { 082 return false; 083 } 084 085 086 087 protected IntervalUtil raidTracker; 088 protected IntervalUtil capTracker; 089 protected IntervalUtil buildTracker; 090 protected void checkRaid(float amount) { 091 if (fleet.isInHyperspace()) return; 092 093 if (raidTracker == null) { 094 raidTracker = new IntervalUtil(0.3f, 0.7f); 095 } 096 raidTracker.advance(Misc.getDays(amount)); 097 if (!raidTracker.intervalElapsed()) return; 098 099 checkColonyAction(); 100 } 101 102 protected void checkCapture(float amount) { 103 if (fleet.isInHyperspace()) return; 104 105 if (capTracker == null) { 106 capTracker = new IntervalUtil(0.3f, 0.7f); 107 } 108 capTracker.advance(Misc.getDays(amount)); 109 if (!capTracker.intervalElapsed()) return; 110 111 checkObjectiveAction(false); 112 } 113 114 protected void checkBuild(float amount) { 115 if (fleet.isInHyperspace()) return; 116 117 if (buildTracker == null) { 118 buildTracker = new IntervalUtil(0.3f, 0.7f); 119 } 120 buildTracker.advance(Misc.getDays(amount)); 121 if (!buildTracker.intervalElapsed()) return; 122 123 checkObjectiveAction(true); 124 } 125 126 protected void checkObjectiveAction(boolean build) { 127// if (!fleet.isInCurrentLocation() && fleet.getName().contains("Tactistar")) { 128// System.out.println("efwfe2534324"); 129// } 130 131 if (!canTakeAction()) return; 132 133 134// if (fleet.isInCurrentLocation()) { 135// System.out.println("fwewefe"); 136// } 137 SectorEntityToken closest = null; 138 float minDist = Float.MAX_VALUE; 139 140 if (build) { 141 for (SectorEntityToken objective : fleet.getContainingLocation().getEntitiesWithTag(Tags.STABLE_LOCATION)) { 142 if (objective.hasTag(Tags.NON_CLICKABLE)) continue; 143 if (objective.hasTag(Tags.FADING_OUT_AND_EXPIRING)) continue; 144 145 // so that it's not continuously rebuilt if it keeps being salvaged by the player 146 if (objective.getMemoryWithoutUpdate().getBoolean(MemFlags.RECENTLY_SALVAGED)) continue; 147 148 float dist = Misc.getDistance(fleet, objective); 149 if (dist < minDist) { 150 closest = objective; 151 minDist = dist; 152 } 153 } 154 } else { 155 for (SectorEntityToken objective : fleet.getContainingLocation().getEntitiesWithTag(Tags.OBJECTIVE)) { 156 if (objective.getFaction() == fleet.getFaction()) continue; 157 if (!objective.getFaction().isHostileTo(fleet.getFaction()) && 158 !Misc.isFleetMadeHostileToFaction(fleet, objective.getFaction())) { 159 boolean ownerHasColonyInSystem = false; 160 for (MarketAPI curr : Misc.getMarketsInLocation(objective.getContainingLocation())) { 161 if (curr.getFaction() == objective.getFaction() && 162 !curr.getFaction().isNeutralFaction()) { 163 ownerHasColonyInSystem = true; 164 break; 165 } 166 } 167 if (ownerHasColonyInSystem) continue; 168 } 169 170 float dist = Misc.getDistance(fleet, objective); 171 if (dist < minDist) { 172 closest = objective; 173 minDist = dist; 174 } 175 } 176 } 177 178 if (closest == null || minDist > 2000f) return; 179 180 for (CampaignFleetAPI other : Misc.getNearbyFleets(closest, 2000f)) { 181 if (other == fleet) continue; 182 183 if (other.isHostileTo(fleet)) { 184 if (other.getFleetPoints() > fleet.getFleetPoints() * 0.25f) return; 185 } 186 187 if (other.getFaction() == fleet.getFaction()) { 188 float dist = Misc.getDistance(other, closest); 189 if (dist < minDist) return; 190 } 191 } 192 193 if (build) { 194 BuildObjectiveParams params = new BuildObjectiveParams(); 195 params.faction = fleet.getFaction(); 196 params.fleet = fleet; 197 params.stableLoc = closest; 198 BuildObjectiveTypePicker pick = Global.getSector().getGenericPlugins().pickPlugin(BuildObjectiveTypePicker.class, params); 199 String type = null; 200 if (pick != null) { 201 type = pick.pickObjectiveToBuild(params); 202 } 203 if (type != null) { 204 giveBuildOrder(closest, type); 205 } 206 } else { 207 giveCaptureOrder(closest); 208 } 209 } 210 211 212 protected void giveCaptureOrder(final SectorEntityToken target) { 213 clearTempAssignments(fleet); 214 215// if (!fleet.isInCurrentLocation() && fleet.getName().contains("Tactistar")) { 216// System.out.println("efwfe2534324"); 217// } 218 219 Misc.setFlagWithReason(fleet.getMemoryWithoutUpdate(), 220 MemFlags.FLEET_BUSY, TEMP_BUSY_REASON, true, (1.5f + (float) Math.random())); 221 222 String name = ((CustomCampaignEntityAPI)target).getCustomEntitySpec().getNameInText(); 223 String capText = "taking control of " + name; 224 String moveText = "moving to take control of " + name; 225 226 Vector2f loc = Misc.getUnitVectorAtDegreeAngle( 227 Misc.getAngleInDegrees(target.getLocation(), fleet.getLocation())); 228 float holdRadius = fleet.getRadius() + target.getRadius() - 10; 229 loc.scale(holdRadius); 230 Vector2f.add(loc, target.getLocation(), loc); 231 SectorEntityToken holdLoc = target.getContainingLocation().createToken(loc); 232 233 holdLoc.setCircularOrbit(target, 234 Misc.getAngleInDegrees(target.getLocation(), fleet.getLocation()), 235 holdRadius, 1000000f); 236 fleet.getContainingLocation().addEntity(holdLoc); 237 Misc.fadeAndExpire(holdLoc, 5f); 238 239 fleet.addAssignmentAtStart(FleetAssignment.HOLD, holdLoc, 0.5f, capText, new Script() { 240 public void run() { 241 if (target.isAlive()) { 242 Objectives o = new Objectives(target); 243 o.control(fleet.getFaction().getId()); 244 } 245 clearTempAssignments(fleet); 246 } 247 }); 248 FleetAssignmentDataAPI curr = fleet.getCurrentAssignment(); 249 if (curr != null) { 250 curr.setCustom(TEMP_ASSIGNMENT); 251 } 252 253 float dist = Misc.getDistance(target, fleet); 254 if (dist > fleet.getRadius() + target.getRadius() + 300f) { 255 256 fleet.addAssignmentAtStart(FleetAssignment.DELIVER_CREW, target, 3f, moveText, null); 257 curr = fleet.getCurrentAssignment(); 258 if (curr != null) { 259 curr.setCustom(TEMP_ASSIGNMENT); 260 } 261 } 262 } 263 264 265 protected void giveBuildOrder(final SectorEntityToken target, String type) { 266 clearTempAssignments(fleet); 267 268 CustomEntitySpecAPI spec = Global.getSettings().getCustomEntitySpec(type); 269 270 Misc.setFlagWithReason(fleet.getMemoryWithoutUpdate(), 271 MemFlags.FLEET_BUSY, TEMP_BUSY_REASON, true, (1.5f + (float) Math.random())); 272 273 String name = spec.getNameInText(); 274 String capText = "constructing " + name; 275 String moveText = "moving to construct " + name; 276 277 Vector2f loc = Misc.getUnitVectorAtDegreeAngle( 278 Misc.getAngleInDegrees(target.getLocation(), fleet.getLocation())); 279 float holdRadius = fleet.getRadius() + target.getRadius(); 280 loc.scale(holdRadius); 281 Vector2f.add(loc, target.getLocation(), loc); 282 SectorEntityToken holdLoc = target.getContainingLocation().createToken(loc); 283 284 holdLoc.setCircularOrbit(target, 285 Misc.getAngleInDegrees(target.getLocation(), fleet.getLocation()), 286 holdRadius, 1000000f); 287 fleet.getContainingLocation().addEntity(holdLoc); 288 Misc.fadeAndExpire(holdLoc, 5f); 289 290 fleet.addAssignmentAtStart(FleetAssignment.HOLD, holdLoc, 0.5f, capText, new Script() { 291 public void run() { 292 if (target.isAlive()) { 293 // re-figure-out the type to avoid duplicates 294 BuildObjectiveParams params = new BuildObjectiveParams(); 295 params.faction = fleet.getFaction(); 296 params.fleet = fleet; 297 params.stableLoc = target; 298 BuildObjectiveTypePicker pick = Global.getSector().getGenericPlugins().pickPlugin(BuildObjectiveTypePicker.class, params); 299 String type = null; 300 if (pick != null) { 301 type = pick.pickObjectiveToBuild(params); 302 } 303 304 Objectives o = new Objectives(target); 305 o.build(type, fleet.getFaction().getId()); 306 } 307 clearTempAssignments(fleet); 308 } 309 }); 310 FleetAssignmentDataAPI curr = fleet.getCurrentAssignment(); 311 if (curr != null) { 312 curr.setCustom(TEMP_ASSIGNMENT); 313 } 314 315 float dist = Misc.getDistance(target, fleet); 316 if (dist > fleet.getRadius() + target.getRadius() + 300f) { 317 318 fleet.addAssignmentAtStart(FleetAssignment.DELIVER_CREW, target, 3f, moveText, null); 319 curr = fleet.getCurrentAssignment(); 320 if (curr != null) { 321 curr.setCustom(TEMP_ASSIGNMENT); 322 } 323 } 324 325 } 326 327 328 329 public static String TEMP_ASSIGNMENT = "temp_PAV4"; 330 public static String TEMP_BUSY_REASON = "temp_PAV4"; 331 protected void clearTempAssignments(CampaignFleetAPI fleet) { 332 Misc.setFlagWithReason(fleet.getMemoryWithoutUpdate(), 333 MemFlags.FLEET_BUSY, TEMP_BUSY_REASON, false, 0f); 334 for (FleetAssignmentDataAPI curr : fleet.getAI().getAssignmentsCopy()) { 335 if (TEMP_ASSIGNMENT.equals(curr.getCustom())) { 336 fleet.getAI().removeAssignment(curr); 337 } 338 } 339 } 340 341 342 343 344 345 346 347 protected boolean canTakeAction() { 348 if (!RouteManager.isPlayerInSpawnRange(fleet)) return false; 349 350 if (fleet.getBattle() != null) return false; 351 if (fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.FLEET_BUSY)) { 352 return false; 353 } 354 355 if (fleet.isCurrentAssignment(FleetAssignment.GO_TO_LOCATION_AND_DESPAWN)) return false; 356 357 MemoryAPI mem = fleet.getMemoryWithoutUpdate(); 358 if (Misc.flagHasReason(mem, MemFlags.FLEET_BUSY, TEMP_BUSY_REASON)) return false; 359 360 361 if (fleet.getAI() instanceof ModularFleetAIAPI) { 362 ModularFleetAIAPI ai = (ModularFleetAIAPI) fleet.getAI(); 363 if (ai.getAssignmentModule().areAssignmentsFrozen()) return false; 364 } 365 366 CampaignFleetAPI pursueTarget = mem.getFleet(FleetAIFlags.PURSUIT_TARGET); 367 CampaignFleetAPI fleeingFrom = mem.getFleet(FleetAIFlags.NEAREST_FLEEING_FROM); 368 369 if (pursueTarget != null || fleeingFrom != null) { 370 return false; 371 } 372 return true; 373 } 374 375 376 377 378 protected void checkColonyAction() { 379 if (!canTakeAction()) return; 380 381 if (fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.RECENTLY_PERFORMED_RAID)) { 382 return; 383 } 384 385 MarketAPI closest = null; 386 float minDist = Float.MAX_VALUE; 387 388// if (fleet.getFaction().getId().equals(Factions.HEGEMONY)) { 389// System.out.println("wefwefwe"); 390// } 391 392 for (MarketAPI market : Misc.getMarketsInLocation(fleet.getContainingLocation())) { 393 if (delegate != null) { 394 if (!delegate.canRaid(fleet, market)) continue; 395 } else { 396 if (!market.getFaction().isHostileTo(fleet.getFaction()) && 397 !Misc.isFleetMadeHostileToFaction(fleet, market.getFaction())) continue; 398 } 399 400 float dist = Misc.getDistance(fleet, market.getPrimaryEntity()); 401 if (dist < minDist) { 402 closest = market; 403 minDist = dist; 404 } 405 } 406 407 if (closest == null || minDist > 2000f) return; 408 409 for (CampaignFleetAPI other : Misc.getNearbyFleets(closest.getPrimaryEntity(), 2000f)) { 410 if (other == fleet) continue; 411 412 if (other.isHostileTo(fleet)) { 413 VisibilityLevel vis = other.getVisibilityLevelTo(fleet); 414 boolean canSee = vis == VisibilityLevel.COMPOSITION_AND_FACTION_DETAILS || vis == VisibilityLevel.COMPOSITION_DETAILS; 415 if (!canSee && other.getFaction() != fleet.getFaction()) continue; 416 417 if (other.getAI() instanceof ModularFleetAIAPI) { 418 ModularFleetAIAPI ai = (ModularFleetAIAPI) other.getAI(); 419 if (ai.isFleeing()) continue; 420 if (ai.isMaintainingContact()) continue; 421 422 if (ai.getTacticalModule().getTarget() == fleet) return; 423 424 MemoryAPI mem = other.getMemoryWithoutUpdate(); 425 boolean smuggler = mem.getBoolean(MemFlags.MEMORY_KEY_SMUGGLER); 426 boolean trader = mem.getBoolean(MemFlags.MEMORY_KEY_TRADE_FLEET); 427 if (smuggler || trader) continue; 428 } 429 if (other.getFleetPoints() > fleet.getFleetPoints() * 0.25f || other.isStationMode()) return; 430 } 431 432 if (other.getFaction() == fleet.getFaction()) { 433 if (other.isStationMode()) continue; 434 435 boolean otherFromSameRaid = delegate != null && delegate.canRaid(other, closest); 436 if (!(Misc.isRaider(other) && !Misc.isWarFleet(other) && !otherFromSameRaid)) continue; 437 438 if (other.getFleetPoints() > fleet.getFleetPoints()) return; 439 if (other.getFleetPoints() == fleet.getFleetPoints()) { 440 float dist = Misc.getDistance(other, closest.getPrimaryEntity()); 441 if (dist < minDist) return; 442 } 443 } 444 } 445 446 giveRaidOrder(closest); 447 } 448 449 450 protected void giveRaidOrder(final MarketAPI target) { 451 clearTempAssignments(fleet); 452 453 Misc.setFlagWithReason(fleet.getMemoryWithoutUpdate(), 454 MemFlags.FLEET_BUSY, TEMP_BUSY_REASON, true, (1.5f + (float) Math.random())); 455 456 String name = target.getName(); 457 String capText = "raiding " + name; 458 String moveText = "moving to raid " + name; 459 if (delegate != null) { 460 String s = delegate.getRaidApproachText(fleet, target); 461 if (s != null) moveText = s; 462 463 s = delegate.getRaidActionText(fleet, target); 464 if (s != null) capText = s; 465 } 466 467 Vector2f loc = Misc.getUnitVectorAtDegreeAngle( 468 Misc.getAngleInDegrees(target.getPrimaryEntity().getLocation(), fleet.getLocation())); 469 float holdRadius = fleet.getRadius() * 0.5f + target.getPrimaryEntity().getRadius(); 470 loc.scale(holdRadius); 471 Vector2f.add(loc, target.getPrimaryEntity().getLocation(), loc); 472 SectorEntityToken holdLoc = target.getContainingLocation().createToken(loc); 473 holdLoc.setCircularOrbit(target.getPrimaryEntity(), 474 Misc.getAngleInDegrees(target.getPrimaryEntity().getLocation(), fleet.getLocation()), 475 holdRadius, 1000000f); 476 fleet.getContainingLocation().addEntity(holdLoc); 477 Misc.fadeAndExpire(holdLoc, 5f); 478 479 final int fpAtStart = fleet.getFleetPoints(); 480 //holdLoc = Global.getSector().getPlayerFleet(); 481 fleet.addAssignmentAtStart(FleetAssignment.HOLD, holdLoc, 0.5f, capText, new Script() { 482 public void run() { 483 if (fpAtStart == fleet.getFleetPoints()) { 484 boolean raided = false; 485 if (delegate != null) { 486 if (delegate.canRaid(fleet, target)) { 487 delegate.performRaid(fleet, target); 488 raided = true; 489 } 490 } else { 491 new MarketCMD(target.getPrimaryEntity()).doGenericRaid(fleet.getFaction(), 492 MarketCMD.getRaidStr(fleet)); 493 raided = true; 494 } 495 496 if (raided) { 497 fleet.getMemoryWithoutUpdate().set(MemFlags.RECENTLY_PERFORMED_RAID, true, 3f); 498 } 499 clearTempAssignments(fleet); 500 } 501 } 502 }); 503 FleetAssignmentDataAPI curr = fleet.getCurrentAssignment(); 504 if (curr != null) { 505 curr.setCustom(TEMP_ASSIGNMENT); 506 } 507 508 float dist = Misc.getDistance(target.getPrimaryEntity(), fleet); 509 //if (dist > fleet.getRadius() + target.getPrimaryEntity().getRadius() + 300f) { 510 if (dist > fleet.getRadius() + target.getPrimaryEntity().getRadius()) { 511 fleet.addAssignmentAtStart(FleetAssignment.DELIVER_CREW, holdLoc, 3f, moveText, null); 512 curr = fleet.getCurrentAssignment(); 513 if (curr != null) { 514 curr.setCustom(TEMP_ASSIGNMENT); 515 } 516 } 517 } 518 519 520 521 522} 523 524 525 526 527 528 529 530 531 532