001package com.fs.starfarer.api.impl.campaign.intel.group; 002 003import java.util.ArrayList; 004import java.util.List; 005import java.util.Random; 006import java.util.Set; 007 008import java.awt.Color; 009 010import org.lwjgl.util.vector.Vector2f; 011 012import com.fs.starfarer.api.Global; 013import com.fs.starfarer.api.campaign.CampaignFleetAPI; 014import com.fs.starfarer.api.campaign.FactionAPI; 015import com.fs.starfarer.api.campaign.LocationAPI; 016import com.fs.starfarer.api.campaign.SectorEntityToken; 017import com.fs.starfarer.api.campaign.StarSystemAPI; 018import com.fs.starfarer.api.campaign.econ.Industry; 019import com.fs.starfarer.api.campaign.econ.MarketAPI; 020import com.fs.starfarer.api.campaign.rules.MemoryAPI; 021import com.fs.starfarer.api.impl.campaign.command.WarSimScript; 022import com.fs.starfarer.api.impl.campaign.fleets.RouteLocationCalculator; 023import com.fs.starfarer.api.impl.campaign.fleets.RouteManager; 024import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.OptionalFleetData; 025import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteData; 026import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteFleetSpawner; 027import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteSegment; 028import com.fs.starfarer.api.impl.campaign.ids.Conditions; 029import com.fs.starfarer.api.impl.campaign.ids.Factions; 030import com.fs.starfarer.api.impl.campaign.ids.Industries; 031import com.fs.starfarer.api.impl.campaign.ids.MemFlags; 032import com.fs.starfarer.api.impl.campaign.ids.Tags; 033import com.fs.starfarer.api.impl.campaign.intel.BaseIntelPlugin; 034import com.fs.starfarer.api.impl.campaign.intel.group.GenericRaidFGI.GenericRaidParams; 035import com.fs.starfarer.api.impl.campaign.missions.FleetCreatorMission; 036import com.fs.starfarer.api.impl.campaign.procgen.themes.RouteFleetAssignmentAI.TravelState; 037import com.fs.starfarer.api.ui.LabelAPI; 038import com.fs.starfarer.api.ui.SectorMapAPI; 039import com.fs.starfarer.api.ui.TooltipMakerAPI; 040import com.fs.starfarer.api.util.CountingMap; 041import com.fs.starfarer.api.util.Misc; 042 043/** 044 * Uses a single RouteData as a placeholder to control the movement and spawning of multiple fleets 045 * to make it easier to sync them up. Once the fleets have spawned, they will not auto-despawn and will 046 * carry out their actions until the action sequence is completed. 047 * 048 * @author Alex 049 * 050 * Copyright 2023 Fractal Softworks, LLC 051 */ 052/** 053 * @author Alex 054 * 055 * Copyright 2023 Fractal Softworks, LLC 056 */ 057public abstract class FleetGroupIntel extends BaseIntelPlugin implements RouteFleetSpawner { 058 059 public static interface FGIEventListener { 060 public void reportFGIAborted(FleetGroupIntel intel); 061 } 062 063 public static String ABORT_UPDATE = "abort_update"; 064 public static String FLEET_LAUNCH_UPDATE = "fleet_launch_update"; 065 066 public static final String KEY_SPAWN_FP = "$core_fgiSpawnFP"; 067 068 public static final String NEVER_STRAGGLER = "$core_fgiNeverStraggler"; 069 public static final String KEY_POTENTIAL_STRAGGLER = "$core_fgiMaybeStraggler"; 070 public static final String KEY_STRAGGLER_RETURN_COUNTDOWN = "$core_fgiStragglerReturnCountdown"; 071 072 073 public static boolean DEBUG = false; 074 075 076 protected Random random = new Random(); 077 protected List<FGAction> actions = new ArrayList<FGAction>(); 078 protected RouteData route; 079 protected RouteSegment prevSegment; 080 protected List<CampaignFleetAPI> fleets = new ArrayList<CampaignFleetAPI>(); 081 protected boolean spawnedFleets = false; 082 083 protected boolean doIncrementalSpawn = true; 084 protected List<CampaignFleetAPI> spawning = new ArrayList<CampaignFleetAPI>(); 085 protected LocationAPI spawnLocation = null; 086 protected float spawnDelay = 0f; 087 protected float elapsed = 0f; 088 089 protected boolean aborted = false; 090 protected float totalFPSpawned = 0f; 091 protected float fleetAbortsMissionFPFraction = 0.33f; 092 protected float groupAbortsMissionFPFraction = 0.33f; 093 protected SectorEntityToken returnLocation; 094 protected FactionAPI faction; 095 protected int approximateNumberOfFleets = 3; 096 097 protected FGIEventListener listener; 098 099 public FleetGroupIntel() { 100 Global.getSector().addScript(this); 101 } 102 103 protected Object readResolve() { 104 if (random == null) { 105 random = new Random(); 106 } 107 108 //random = new Random(142343232L); 109 //System.out.println("RNG CHECK 1: " + random.nextLong()); 110 return this; 111 } 112 113 public float getETAUntil(String actionId) { 114 return getETAUntil(actionId, false); 115 } 116 public float getETAUntil(String actionId, boolean untilEndOfAction) { 117 float eta = getDelayRemaining(); 118 for (FGAction action : actions) { 119 if (action.getId() != null && action.getId().equals(actionId)) { 120 if (untilEndOfAction) { 121 eta += action.getEstimatedDaysToComplete(); 122 } 123 return eta; 124 } else { 125 eta += action.getEstimatedDaysToComplete(); 126 } 127 } 128 return 0f; 129 } 130 131 @Override 132 protected void notifyEnded() { 133 super.notifyEnded(); 134 Global.getSector().removeScript(this); 135 } 136 137 138 protected boolean sourceWasEverMilitaryMarket = false; 139 protected boolean sendFleetLaunchUpdate = false; 140 protected boolean isSourceFunctionalMilitaryMarket() { 141 if (getSource() == null) return false; 142 MarketAPI market = getSource().getMarket(); 143 if (market == null || market.isPlanetConditionMarketOnly()) return false; 144 145 if (market.hasCondition(Conditions.DECIVILIZED)) { 146 return false; 147 } 148 149 Industry b = market.getIndustry(Industries.MILITARYBASE); 150 if (b == null) b = market.getIndustry(Industries.HIGHCOMMAND); 151 if (b == null || b.isDisrupted() || !b.isFunctional()) { 152 return false; 153 } 154 return true; 155 } 156 157 public boolean isInPreLaunchDelay() { 158 return route != null && route.getElapsed() <= 0f && route.getDelay() > 0; 159 } 160 161 /** 162 * route needs to be created when this method is called. 163 * @param delay 164 */ 165 public void setPreFleetDeploymentDelay(float delay) { 166 if (route != null) { 167 route.setDelay(delay); 168 } 169 } 170 171 public float getDelayRemaining() { 172 if (!isInPreLaunchDelay() || route == null) return 0f; 173 return route.getDelay(); 174 } 175 176 public float getElapsed() { 177 return elapsed; 178 } 179 180 public void setElapsed(float elapsed) { 181 this.elapsed = elapsed; 182 } 183 184 @Override 185 public void advance(float amount) { 186 super.advance(amount); 187 188 elapsed += amount; 189 190 if (isEnded() || isEnding() || isAborted()) return; 191 if (route == null) return; 192 193 if (isInPreLaunchDelay()) { 194 sendFleetLaunchUpdate = true; 195 boolean mil = isSourceFunctionalMilitaryMarket(); 196 sourceWasEverMilitaryMarket |= mil; 197 if (!mil && sourceWasEverMilitaryMarket) { 198 abort(); 199 return; 200 } 201 return; 202 } 203 204 if (sendFleetLaunchUpdate) { 205 sendFleetLaunchUpdate = false; 206 sendUpdateIfPlayerHasIntel(FLEET_LAUNCH_UPDATE, !isPlayerTargeted()); 207 } 208 209 if (!isSucceeded() && !isEnding() && !isAborted() && shouldAbort()) { 210 abort(); 211 return; 212 } 213 214 if (!spawnedFleets) { 215 if (!isSucceeded() && route.getExtra() != null && route.getExtra().damage != null && 216 1f - route.getExtra().damage < getGroupAbortsMissionFPFraction()) { 217 abort(); 218 return; 219 } 220 221 RouteSegment curr = route.getCurrent(); 222 if (curr != prevSegment || route.isExpired()) { 223 if (prevSegment != null && prevSegment.custom instanceof FGAction) { 224 FGAction action = (FGAction) prevSegment.custom; 225 action.notifySegmentFinished(prevSegment); 226 actions.remove(action); 227 notifyActionFinished(action); 228 229 if (route.isExpired()) { 230 finish(false); 231 return; 232 } 233 } 234 } 235 prevSegment = curr; 236 } else { 237 handleIncrementalSpawning(amount); 238 pruneDestroyedOrDamagedFleetsAndAbortIfNeeded(); 239 240 if (actions.isEmpty()) { 241 finish(false); 242 return; 243 } 244 245 FGAction action = actions.get(0); 246 if (action.isActionFinished()) { 247 actions.remove(0); 248 notifyActionFinished(action); 249 return; 250 } 251 252 action.directFleets(amount); 253 254 //System.out.println(action + " " + action.isActionFinished()); 255 if (action.isActionFinished()) { 256 actions.remove(0); 257 notifyActionFinished(action); 258 return; 259 } 260 } 261 262 } 263 264 protected boolean shouldAbort() { 265 return false; 266 } 267 268 protected boolean shouldSendIntelUpdateWhenActionFinished(FGAction action) { 269 // presumably, !isFailed() is correct here but not 100% sure why -am 270 if (action instanceof FGWaitAction && (isAborted() || !isFailed())) { 271 return false; 272 } 273 274 if (action instanceof FGWaitAction) { 275 FGWaitAction wait = (FGWaitAction) action; 276 if (wait.getOrigDurDays() <= 0f) return false; 277 } 278 return action != null && action.getId() != null; 279 } 280 281 protected void notifyActionFinished(FGAction action) { 282 if (action != null && shouldSendIntelUpdateWhenActionFinished(action)) { 283 sendUpdateIfPlayerHasIntel(action.getId(), !isPlayerTargeted()); 284 } 285 } 286 287 288 protected void pruneDestroyedOrDamagedFleetsAndAbortIfNeeded() { 289 if (isSucceeded()) { 290 List<CampaignFleetAPI> remove = new ArrayList<CampaignFleetAPI>(); 291 for (CampaignFleetAPI fleet : fleets) { 292 if (!fleet.isAlive()) { 293 remove.add(fleet); 294 } 295 } 296 fleets.removeAll(remove); 297 return; // already returning at this point, still clean up destroyed fleets though 298 } 299 300 checkStragglers(); 301 302 List<CampaignFleetAPI> remove = new ArrayList<CampaignFleetAPI>(); 303 float totalFP = 0f; 304 for (CampaignFleetAPI fleet : fleets) { 305 float spawnFP = fleet.getMemoryWithoutUpdate().getFloat(KEY_SPAWN_FP); 306 if (!fleet.isAlive()) { 307 remove.add(fleet); 308 } else if (fleet.getFleetPoints() <= spawnFP * getFleetAbortsMissionFPFraction()) { 309 remove.add(fleet); 310 giveReturnAssignments(fleet); 311 } else { 312 totalFP += fleet.getFleetPoints(); 313 } 314 } 315 316 fleets.removeAll(remove); 317 318 if (totalFP < totalFPSpawned * getGroupAbortsMissionFPFraction()) { 319 abort(); 320 return; 321 } 322 323 } 324 325 protected void checkStragglers() { 326 List<CampaignFleetAPI> remove = new ArrayList<CampaignFleetAPI>(); 327 CountingMap<LocationAPI> fleetLocs = new CountingMap<LocationAPI>(); 328 329 for (CampaignFleetAPI fleet : fleets) { 330 fleetLocs.add(fleet.getContainingLocation()); 331 } 332 333 LocationAPI withMostFleets = null; 334 int maxCount = 0; 335 for (LocationAPI loc : fleetLocs.keySet()) { 336 int count = fleetLocs.getCount(loc); 337 if (count > maxCount) { 338 withMostFleets = loc; 339 maxCount = count; 340 } 341 } 342 343 if (withMostFleets == null) return; 344 345 Vector2f com = new Vector2f(); 346 float weight = 0f; 347 for (CampaignFleetAPI fleet : fleets) { 348 if (fleet.getContainingLocation() != withMostFleets) continue; 349 float w = fleet.getFleetPoints(); 350 Vector2f loc = new Vector2f(fleet.getLocation()); 351 loc.scale(w); 352 Vector2f.add(com, loc, com); 353 weight += w; 354 } 355 356 if (weight < 1f) weight = 1f; 357 com.scale(1f / weight); 358 359 FGAction action = getCurrentAction(); 360 boolean canBeStragglers = action != null && 361 (action instanceof FGTravelAction || action instanceof FGWaitAction); 362 363 int maybeStragglers = 0; 364 for (CampaignFleetAPI fleet : fleets) { 365 boolean potentialStraggler = fleet.getContainingLocation() != withMostFleets; 366 if (!potentialStraggler) { 367 potentialStraggler |= Misc.getDistance(fleet.getLocation(), com) > 4000; 368 } 369 if (fleet.getMemoryWithoutUpdate().getBoolean(NEVER_STRAGGLER) || !canBeStragglers) { 370 potentialStraggler = false; 371 } 372 373 MemoryAPI mem = fleet.getMemoryWithoutUpdate(); 374 if (mem.contains(KEY_POTENTIAL_STRAGGLER)) { 375 maybeStragglers++; 376 } 377 if (!potentialStraggler && mem.contains(KEY_POTENTIAL_STRAGGLER)) { 378 mem.unset(KEY_POTENTIAL_STRAGGLER); 379 mem.unset(KEY_STRAGGLER_RETURN_COUNTDOWN); 380 } else if (mem.contains(KEY_POTENTIAL_STRAGGLER) && !mem.contains(KEY_STRAGGLER_RETURN_COUNTDOWN)) { 381 remove.add(fleet); 382// if (fleet.getName().toLowerCase().contains("tactistar")) { 383// System.out.println("fewfwef23r23r23r"); 384// } 385 giveReturnAssignments(fleet); 386 } else if (potentialStraggler && !mem.contains(KEY_POTENTIAL_STRAGGLER)) { 387 mem.set(KEY_POTENTIAL_STRAGGLER, true); 388 mem.set(KEY_STRAGGLER_RETURN_COUNTDOWN, true, getPotentialStragglerCountdownDays()); 389 } 390 } 391 392 fleets.removeAll(remove); 393 394 //System.out.println(getClass().getSimpleName() + " maybe stragglers: " + maybeStragglers + ", fleets: " + fleets.size()); 395 } 396 397 protected float getPotentialStragglerCountdownDays() { 398 //if (true) return 0.5f; 399 return 30f; 400 } 401 402 protected boolean failedButNotDefeated = false; 403 public boolean isFailedButNotDefeated() { 404 return failedButNotDefeated; 405 } 406 407 public void setFailedButNotDefeated(boolean failedButNotDefeated) { 408 this.failedButNotDefeated = failedButNotDefeated; 409 } 410 411 public void abort() { 412 finish(true); 413 } 414 public void finish(boolean isAbort) { 415 boolean wasEnding = isEnding(); 416 aborted = isAbort; 417 endAfterDelay(); 418 419 if (!isAbort) { 420 setFailedButNotDefeated(true); 421 } 422 423 if (spawnedFleets && !actions.isEmpty()) { 424 if (!actions.get(0).isActionFinished()) { 425 actions.get(0).setActionFinished(true); 426 } 427 } 428 429 if (route != null) { 430 route.expire(); 431 RouteManager.getInstance().removeRoute(route); 432 } 433 434 giveFleetsReturnAssignments(); 435 436 if (!wasEnding && isAbort) { 437 sendUpdateIfPlayerHasIntel(ABORT_UPDATE, !isPlayerTargeted()); 438 439 if (listener != null) { 440 listener.reportFGIAborted(this); 441 } 442 } 443 } 444 445 public boolean isSpawning() { 446 return spawning != null && !spawning.isEmpty(); 447 } 448 449 public boolean isAborted() { 450 return aborted; 451 } 452 453 454 protected void giveFleetsReturnAssignments() { 455 for (CampaignFleetAPI fleet : fleets) { 456 giveReturnAssignments(fleet); 457 } 458 } 459 460 protected void giveReturnAssignments(CampaignFleetAPI fleet) { 461 fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_FLEET_DO_NOT_GET_SIDETRACKED, true); 462 if (returnLocation != null) { 463 Misc.giveStandardReturnAssignments(fleet, returnLocation, null, true); 464 } else { 465 Misc.giveStandardReturnToSourceAssignments(fleet, true); 466 } 467 } 468 469 470 protected void createRoute(String factionId, int approximateTotalDifficultyPoints, 471 int approximateNumberOfFleets, Object custom) { 472 createRoute(factionId, approximateTotalDifficultyPoints, approximateNumberOfFleets, custom, null); 473 } 474 protected void createRoute(String factionId, int approximateTotalDifficultyPoints, 475 int approximateNumberOfFleets, Object custom, GenericRaidParams params) { 476 OptionalFleetData extra = new OptionalFleetData(); 477 extra.strength = getApproximateStrengthForTotalDifficultyPoints(factionId, approximateTotalDifficultyPoints); 478 extra.factionId = faction.getId(); // needed for WarSimScript etc 479 if (params != null && params.source != null) { 480 extra.quality = Misc.getShipQuality(params.source, extra.factionId); 481 } 482 route = RouteManager.getInstance().addRoute("FGI_" + getClass().getSimpleName(), null, 483 Misc.genRandomSeed(), extra, this, custom); 484 485 for (FGAction action : actions) { 486 int before = route.getSegments().size(); 487 action.addRouteSegment(route); 488 int after = route.getSegments().size(); 489 for (int i = before; i < after; i++) { 490 route.getSegments().get(i).custom = action; 491 } 492 } 493 494 this.approximateNumberOfFleets = approximateNumberOfFleets; 495 } 496 497 public void setRoute(RouteData route) { 498 this.route = route; 499 } 500 501 public CampaignFleetAPI spawnFleet(RouteData route) { 502 //System.out.println("RNG CHECK 2: " + random.nextLong()); 503 504 RouteManager.getInstance().removeRoute(route); 505 506 if (isSpawnedFleets()) return null; 507 508 if (isEnded() || isEnding()) return null; 509 if (route == null || route.getCurrent() == null) { 510 abort(); 511 return null; 512 } 513 514 RouteSegment curr = route.getCurrent(); 515 516 while (!actions.isEmpty()) { 517 FGAction action = actions.get(0); 518 if (action == curr.custom) { 519 break; 520 } 521 actions.remove(0); 522 } 523 524 if (actions.isEmpty()) { 525 abort(); 526 return(null); 527 } 528 529 if (FleetGroupIntel.DEBUG) { 530 System.out.println(getClass().getSimpleName() + ": about to spawn fleets"); 531 } 532 533 spawnFleets(); 534 setSpawnedFleets(true); 535 536 if (doIncrementalSpawn && route.getElapsed() < 0.1f) { 537 spawning.addAll(fleets); 538 fleets.clear(); 539 for (CampaignFleetAPI fleet : spawning) { 540 if (spawnLocation == null) { 541 spawnLocation = fleet.getContainingLocation(); 542 } 543 fleet.getContainingLocation().removeEntity(fleet); 544 } 545 handleIncrementalSpawning(0.1f); 546 } else { 547 for (CampaignFleetAPI fleet : fleets) { 548 int fp = fleet.getFleetPoints(); 549 fleet.getMemoryWithoutUpdate().set(KEY_SPAWN_FP, fp); 550 totalFPSpawned += fp; 551 } 552 } 553 554 555 actions.get(0).notifyFleetsSpawnedMidSegment(curr); 556 557 // don't return any created fleets - route is removed anyway and we're managing the fleets directly 558 return null; 559 } 560 561 public void setNeverStraggler(CampaignFleetAPI fleet) { 562 if (fleet == null) return; 563 fleet.getMemoryWithoutUpdate().set(NEVER_STRAGGLER, true); 564 } 565 566 public void handleIncrementalSpawning(float amount) { 567 if (spawning == null || spawning.isEmpty() || spawnLocation == null) return; 568 float days = Misc.getDays(amount); 569 spawnDelay -= days; 570 571 if (spawnDelay <= 0) { 572 spawnDelay = 0.25f + (float) Math.random() * 0.25f; 573 574 CampaignFleetAPI fleet = spawning.remove(0); 575 spawnLocation.addEntity(fleet); 576 if (getSource() != null) { 577 fleet.setLocation(getSource().getLocation().x, getSource().getLocation().y); 578 } 579 fleets.add(fleet); 580 int fp = fleet.getFleetPoints(); 581 totalFPSpawned += fp; 582 583 if (spawning.isEmpty()) { 584 spawning = null; 585 spawnLocation = null; 586 } 587 } 588 } 589 590 591 public boolean isDoIncrementalSpawn() { 592 return doIncrementalSpawn; 593 } 594 595 public void setDoIncrementalSpawn(boolean doIncrementalSpawn) { 596 this.doIncrementalSpawn = doIncrementalSpawn; 597 } 598 599 public float getTotalFPSpawned() { 600 return totalFPSpawned; 601 } 602 603 604 public void setTotalFPSpawned(float totalFPSpawned) { 605 this.totalFPSpawned = totalFPSpawned; 606 } 607 608 public void setSpawnedFleets(boolean spawnedFleets) { 609 this.spawnedFleets = spawnedFleets; 610 } 611 612 public RouteSegment getSegmentForAction(FGAction action) { 613 for (RouteSegment seg : getRoute().getSegments()) { 614 if (seg.custom == action) return seg; 615 } 616 return null; 617 } 618 619 public void removeAction(String id) { 620 FGAction action = getAction(id); 621 if (action != null) { 622 actions.remove(action); 623 } 624 } 625 public FGAction getAction(String id) { 626 for (FGAction curr : actions) { 627 if (curr.getId() != null && curr.getId().equals(id)) { 628 return curr; 629 } 630 } 631 return null; 632 } 633 634 public boolean isCurrent(String id) { 635 FGAction action = getAction(id); 636 return action != null && action == getCurrentAction(); 637 } 638 639 public FGAction getCurrentAction() { 640 if (actions.isEmpty()) return null; 641 return actions.get(0); 642 } 643 644 public boolean isSpawnedFleets() { 645 return spawnedFleets; 646 } 647 648 public int getApproximateNumberOfFleets() { 649 return approximateNumberOfFleets; 650 } 651 652 public void setApproximateNumberOfFleets(int approximateNumberOfFleets) { 653 this.approximateNumberOfFleets = approximateNumberOfFleets; 654 } 655 656 public List<CampaignFleetAPI> getFleets() { 657 return fleets; 658 } 659 660 public static TravelState getTravelState(RouteSegment segment) { 661 if (segment.isInSystem()) { 662 return TravelState.IN_SYSTEM; 663 } 664 665 if (segment.hasLeaveSystemPhase() && segment.getLeaveProgress() < 1f) { 666 return TravelState.LEAVING_SYSTEM; 667 } 668 if (segment.hasEnterSystemPhase() && segment.getEnterProgress() > 0f) { 669 return TravelState.ENTERING_SYSTEM; 670 } 671 672 return TravelState.IN_HYPER_TRANSIT; 673 } 674 675 public static LocationAPI getLocation(RouteSegment segment) { 676 return getLocationForState(segment, getTravelState(segment)); 677 } 678 679 public static LocationAPI getLocationForState(RouteSegment segment, TravelState state) { 680 switch (state) { 681 case ENTERING_SYSTEM: { 682 if (segment.to != null) { 683 return segment.to.getContainingLocation(); 684 } 685 return segment.from.getContainingLocation(); 686 } 687 case IN_HYPER_TRANSIT: return Global.getSector().getHyperspace(); 688 case IN_SYSTEM: return segment.from.getContainingLocation(); 689 case LEAVING_SYSTEM: return segment.from.getContainingLocation(); 690 } 691 return null; 692 } 693 694 695 public static void setLocationAndCoordinates(CampaignFleetAPI fleet, RouteSegment current) { 696 TravelState state = getTravelState(current); 697 if (state == TravelState.LEAVING_SYSTEM) { 698 float p = current.getLeaveProgress(); 699 SectorEntityToken jp = RouteLocationCalculator.findJumpPointToUse(fleet, current.from); 700 if (jp == null) jp = current.from; 701 RouteLocationCalculator.setLocation(fleet, p, current.from, jp); 702 } 703 else if (state == TravelState.ENTERING_SYSTEM) { 704 float p = current.getEnterProgress(); 705 SectorEntityToken jp = RouteLocationCalculator.findJumpPointToUse(fleet, current.to); 706 if (jp == null) jp = current.to; 707 RouteLocationCalculator.setLocation(fleet, p, jp, current.to); 708 } 709 else if (state == TravelState.IN_SYSTEM) { 710 float p = current.getTransitProgress(); 711 RouteLocationCalculator.setLocation(fleet, p, 712 current.from, current.to); 713 } 714 else if (state == TravelState.IN_HYPER_TRANSIT) { 715 float p = current.getTransitProgress(); 716 SectorEntityToken t1 = Global.getSector().getHyperspace().createToken( 717 current.from.getLocationInHyperspace().x, 718 current.from.getLocationInHyperspace().y); 719 SectorEntityToken t2 = Global.getSector().getHyperspace().createToken( 720 current.to.getLocationInHyperspace().x, 721 current.to.getLocationInHyperspace().y); 722 RouteLocationCalculator.setLocation(fleet, p, t1, t2); 723 } 724 } 725 726 727 public List<FGAction> getActions() { 728 return actions; 729 } 730 731 public void addAction(FGAction action, String id) { 732 action.setId(id); 733 addAction(action); 734 } 735 public void addAction(FGAction action) { 736 action.setIntel(this); 737 actions.add(action); 738 } 739 740 741 public boolean shouldCancelRouteAfterDelayCheck(RouteData route) { 742 return false; 743 } 744 745 public boolean shouldRepeat(RouteData route) { 746 return false; 747 } 748 749 public void reportAboutToBeDespawnedByRouteManager(RouteData route) { 750 // should never happen since this class removes the route and never returns the created fleets to the manager 751 } 752 753 public SectorEntityToken getReturnLocation() { 754 return returnLocation; 755 } 756 757 public void setReturnLocation(SectorEntityToken returnLocation) { 758 this.returnLocation = returnLocation; 759 } 760 761 762 public float getFleetAbortsMissionFPFraction() { 763 return fleetAbortsMissionFPFraction; 764 } 765 766 public void setFleetAbortsMissionFPFraction(float fleetAbortsMissionFPFraction) { 767 this.fleetAbortsMissionFPFraction = fleetAbortsMissionFPFraction; 768 } 769 770 public float getGroupAbortsMissionFPFraction() { 771 return groupAbortsMissionFPFraction; 772 } 773 774 public void setGroupAbortsMissionFPFraction(float groupAbortsMissionFPFraction) { 775 this.groupAbortsMissionFPFraction = groupAbortsMissionFPFraction; 776 } 777 778 @Override 779 public FactionAPI getFactionForUIColors() { 780 //return super.getFactionForUIColors(); 781 return faction; 782 } 783 784 public void setFaction(String factionId) { 785 faction = Global.getSector().getFaction(factionId); 786 787 } 788 public void setFaction(FactionAPI faction) { 789 this.faction = faction; 790 } 791 792 public FactionAPI getFaction() { 793 return faction; 794 } 795 796 public RouteData getRoute() { 797 return route; 798 } 799 800 public static float getApproximateStrengthForTotalDifficultyPoints(String factionId, int points) { 801 float mult = 50f; 802 if (factionId != null) { 803 FactionAPI faction = Global.getSector().getFaction(factionId); 804 if (faction.getCustomBoolean("pirateBehavior")) { 805 mult = 35f; 806 } 807 } 808 return points * mult; 809 } 810 811 /** 812 * Very approximately, the result is around 50 points of "effective strength" per point of difficulty. 813 * Lower (30-40) for pirates/Pathers, a bit >50 for some of the main factions. 814 * 815 * Not a lot of difference for standard/quality/quantity, since those adjust size etc to try to stay even. 816 */ 817 public static void computeSampleFleetStrengths() { 818 String factionId = null; 819 factionId = Factions.LUDDIC_CHURCH; 820 factionId = Factions.LUDDIC_PATH; 821 factionId = Factions.TRITACHYON; 822 factionId = Factions.DIKTAT; 823 factionId = Factions.REMNANTS; 824 factionId = Factions.PIRATES; 825 factionId = Factions.PERSEAN; 826 factionId = Factions.HEGEMONY; 827 factionId = Factions.INDEPENDENT; 828 829 System.out.println("---------------------------------"); 830 System.out.println("FACTION: " + factionId); 831 832 System.out.println("Difficulty\tStd\tQuality\tQuantity"); 833 for (int j = 0; j < 1; j++) { 834 for (int i = 1; i <= 10; i++) { 835 836 Vector2f loc = new Vector2f(); 837 838 float strStandard = 0; 839 float strQuality = 0; 840 float strQuantity = 0; 841 842 { 843 FleetCreatorMission m = new FleetCreatorMission(new Random()); 844 m.beginFleet(); 845 m.createStandardFleet(i, factionId, loc); 846 CampaignFleetAPI fleet = m.createFleet(); 847 if (fleet != null) { 848 strStandard = fleet.getEffectiveStrength(); 849 } 850 } 851 852 { 853 FleetCreatorMission m = new FleetCreatorMission(new Random()); 854 m.beginFleet(); 855 m.createQualityFleet(i, factionId, loc); 856 CampaignFleetAPI fleet = m.createFleet(); 857 if (fleet != null) { 858 strQuality = fleet.getEffectiveStrength(); 859 } 860 } 861 862 { 863 FleetCreatorMission m = new FleetCreatorMission(new Random()); 864 m.beginFleet(); 865 m.createQuantityFleet(i, factionId, loc); 866 CampaignFleetAPI fleet = m.createFleet(); 867 if (fleet != null) { 868 strQuantity = fleet.getEffectiveStrength(); 869 } 870 } 871 872 873 System.out.println("" + i + "\t\t" + (int)strStandard + "\t" + (int)strQuality + "\t" + (int)strQuantity); 874 } 875 System.out.println("---------------------------------"); 876 } 877 } 878 879 protected abstract boolean isPlayerTargeted(); 880 protected abstract void spawnFleets(); 881 protected abstract SectorEntityToken getSource(); 882 protected abstract SectorEntityToken getDestination(); 883 protected abstract String getBaseName(); 884 885 protected abstract void addNonUpdateBulletPoints(TooltipMakerAPI info, Color tc, Object param, 886 ListInfoMode mode, float initPad); 887 protected abstract void addUpdateBulletPoints(TooltipMakerAPI info, Color tc, Object param, 888 ListInfoMode mode, float initPad); 889 890 protected void addStatusSection(TooltipMakerAPI info, float width, float height, float opad) { 891 892 } 893 protected void addAssessmentSection(TooltipMakerAPI info, float width, float height, float opad) { 894 895 } 896 protected void addBasicDescription(TooltipMakerAPI info, float width, float height, float opad) { 897 898 } 899 900 @Override 901 public Set<String> getIntelTags(SectorMapAPI map) { 902 Set<String> tags = super.getIntelTags(map); 903 tags.add(Tags.INTEL_MILITARY); 904 905 if (Misc.isHyperspaceAnchor(getDestination())) { 906 StarSystemAPI system = Misc.getStarSystemForAnchor(getDestination()); 907 if (system != null && 908 !Misc.getMarketsInLocation(system, Factions.PLAYER).isEmpty()) { 909 tags.add(Tags.INTEL_COLONIES); 910 } 911 } 912 913 if (getDestination() != null && 914 getDestination().getContainingLocation() != null && 915 !Misc.getMarketsInLocation(getDestination().getContainingLocation(), Factions.PLAYER).isEmpty()) { 916 tags.add(Tags.INTEL_COLONIES); 917 } 918 tags.add(getFaction().getId()); 919 return tags; 920 } 921 922 public String getSortString() { 923 return "Fleet Group Movement"; 924 } 925 926 public String getSuccessPostfix() { 927 return " - Successful"; 928 } 929 930 public String getFailurePostfix() { 931 if (isInPreLaunchDelay()) { 932 return " - Aborted"; 933 } 934 if (isFailedButNotDefeated()) { 935 return " - Failed"; 936 } 937 return " - Defeated"; 938 } 939 940 public String getName() { 941 String base = getBaseName(); 942 if (isSucceeded()) { 943 return base + getSuccessPostfix(); 944 } else if (isFailed() || isAborted()) { 945 return base + getFailurePostfix(); 946 } 947 //return base + " - Over"; 948 return base; 949 } 950 951 public boolean isSucceeded() { 952 return false; 953 } 954 public boolean isFailed() { 955 return isAborted(); 956 } 957 958 @Override 959 public void createIntelInfo(TooltipMakerAPI info, ListInfoMode mode) { 960 Color c = getTitleColor(mode); 961 962 info.setParaFontDefault(); 963 964 info.addPara(getName(), c, 0f); 965 info.setParaFontDefault(); 966 addBulletPoints(info, mode); 967 } 968 969 @Override 970 public String getIcon() { 971 return getFaction().getCrest(); 972 } 973 974 public String getSmallDescriptionTitle() { 975 return getName(); 976 } 977 978 @Override 979 public IntelSortTier getSortTier() { 980// if (isPlayerTargeted() && false) { 981// return IntelSortTier.TIER_2; 982// } 983 return super.getSortTier(); 984 } 985 986 @Override 987 public SectorEntityToken getMapLocation(SectorMapAPI map) { 988 return getDestination(); 989 } 990 991 public List<ArrowData> getArrowData(SectorMapAPI map) { 992 993 SectorEntityToken src = getSource(); 994 SectorEntityToken dest = getDestination(); 995 996 if (src == null || dest == null || src.getContainingLocation() == dest.getContainingLocation()) { 997 return null; 998 } 999 1000 List<ArrowData> result = new ArrayList<ArrowData>(); 1001 1002 ArrowData arrow = new ArrowData(src, dest); 1003 arrow.color = getFactionForUIColors().getBaseUIColor(); 1004 arrow.width = 20f; 1005 result.add(arrow); 1006 1007 return result; 1008 } 1009 1010 public Random getRandom() { 1011 return random; 1012 } 1013 1014 public void setRandom(Random random) { 1015 this.random = random; 1016 } 1017 1018 protected void addBulletPoints(TooltipMakerAPI info, ListInfoMode mode) { 1019 float pad = 3f; 1020 float opad = 10f; 1021 1022 float initPad = pad; 1023 if (mode == ListInfoMode.IN_DESC) initPad = opad; 1024 1025 Color tc = getBulletColorForMode(mode); 1026 1027 1028 bullet(info); 1029 Object param = getListInfoParam(); 1030 boolean isUpdate = param != null; 1031 1032 if (!isUpdate) { 1033 addNonUpdateBulletPoints(info, tc, param, mode, initPad); 1034 } else { 1035 addUpdateBulletPoints(info, tc, param, mode, initPad); 1036 } 1037 unindent(info); 1038 } 1039 1040 protected void addFactionBulletPoint(TooltipMakerAPI info, Color tc, float initPad) { 1041 info.addPara("Faction: " + faction.getDisplayName(), initPad, tc, 1042 faction.getBaseUIColor(), faction.getDisplayName()); 1043 } 1044 1045 protected void addArrivedBulletPoint(String destName, Color destHL, TooltipMakerAPI info, Color tc, float initPad) { 1046 LabelAPI label = info.addPara("Arrived at " + destName, tc, initPad); 1047 1048 if (destHL != null) { 1049 String hl = getNameWithNoType(destName); 1050 label.setHighlightColor(destHL); 1051 label.highlightLast(hl); 1052 } 1053 } 1054 1055 public String getNameWithNoType(String systemName) { 1056 String hl = systemName; 1057 if (hl != null) { 1058 if (hl.endsWith(" system")) { 1059 hl = hl.replaceAll(" system", ""); 1060 } 1061 if (hl.endsWith(" nebula")) { 1062 hl = hl.replaceAll(" nebula", ""); 1063 } 1064 if (hl.endsWith(" star system")) { 1065 hl = hl.replaceAll(" star system", ""); 1066 } 1067 } 1068 return hl; 1069 } 1070 1071 public static enum ETAType { 1072 ARRIVING, 1073 RETURNING, 1074 DEPARTURE, 1075 DEPLOYMENT, 1076 } 1077 1078 protected void addETABulletPoints(String destName, Color destHL, boolean withDepartedText, float eta, 1079 ETAType type, TooltipMakerAPI info, Color tc, float initPad) { 1080 Color h = Misc.getHighlightColor(); 1081 1082 String hl = getNameWithNoType(destName); 1083 1084 if (type == ETAType.DEPLOYMENT) { 1085 if ((int) eta <= 0) { 1086 info.addPara("Fleet deployment imminent", tc, initPad); 1087 } else { 1088 String days = (int)eta == 1 ? "day" : "days"; 1089 info.addPara("Estimated %s " + days + " until fleet deployment", 1090 initPad, tc, h, "" + (int) eta); 1091 } 1092 return; 1093 } 1094 1095 if (type == ETAType.DEPARTURE) { 1096 if ((int) eta <= 0) { 1097 info.addPara("Departure imminent", tc, initPad); 1098 } else { 1099 String days = (int)eta == 1 ? "day" : "days"; 1100 info.addPara("Estimated %s " + days + " until departure", 1101 initPad, tc, h, "" + (int) eta); 1102 } 1103 return; 1104 } 1105 1106 LabelAPI label = null; 1107 if (withDepartedText && eta <= 0) { 1108 // operations in same location as the "from" 1109 label = info.addPara("Operating in the " + destName, tc, initPad); 1110 1111 if (destHL != null && label != null) { 1112 label.setHighlightColor(destHL); 1113 label.highlightLast(hl); 1114 } 1115 } else { 1116 if (withDepartedText) { 1117 String pre = "Departed for "; 1118 if (type == ETAType.RETURNING) { 1119 pre = "Returning to "; 1120 } 1121 label = info.addPara(pre + destName, tc, initPad); 1122 1123 if (destHL != null && label != null) { 1124 label.setHighlightColor(destHL); 1125 label.setHighlight(hl); 1126 } 1127 initPad = 0f; 1128 } 1129 if ((int) eta > 0) { 1130 String days = (int)eta == 1 ? "day" : "days"; 1131 String post = " until arrival"; 1132 if (type == ETAType.RETURNING) { 1133 post = " until return"; 1134 } 1135 if (!withDepartedText) { 1136 if (type == ETAType.RETURNING) post += " to " + destName; 1137 else if (type == ETAType.ARRIVING) post += " at " + destName; 1138 } 1139 label = info.addPara("Estimated %s " + days + post, initPad, tc, h, "" + (int) eta); 1140 1141 if (!withDepartedText && destHL != null && label != null) { 1142 label.setHighlightColors(h, destHL); 1143 label.setHighlight("" + (int) eta, hl); 1144 } 1145 } else { 1146 String pre = "Arrival at "; 1147 if (type == ETAType.RETURNING) { 1148 pre = "Return to "; 1149 } 1150 label = info.addPara(pre + destName + " is imminent", tc, initPad); 1151 1152 if (destHL != null && label != null) { 1153 label.setHighlightColor(destHL); 1154 label.highlightLast(hl); 1155 } 1156 } 1157 } 1158 } 1159 1160 1161 @Override 1162 public void createSmallDescription(TooltipMakerAPI info, float width, float height) { 1163 Color h = Misc.getHighlightColor(); 1164 Color g = Misc.getGrayColor(); 1165 Color tc = Misc.getTextColor(); 1166 float pad = 3f; 1167 float opad = 10f; 1168 1169 addBasicDescription(info, width, height, opad); 1170 1171 addAssessmentSection(info, width, height, opad); 1172 1173 addStatusSection(info, width, height, opad); 1174 1175 addBulletPoints(info, ListInfoMode.IN_DESC); 1176 } 1177 1178 protected void showMarketsInDanger(TooltipMakerAPI info, float opad, float width, StarSystemAPI system, 1179 List<MarketAPI> targets, String safeStr, String riskStr, String riskStrHighlight) { 1180 1181 Color h = Misc.getHighlightColor(); 1182 float raidStr = getRoute().getExtra().getStrengthModifiedByDamage(); 1183 float defenderStr = WarSimScript.getEnemyStrength(getFaction(), system, isPlayerTargeted()); 1184 1185 List<MarketAPI> safe = new ArrayList<MarketAPI>(); 1186 List<MarketAPI> unsafe = new ArrayList<MarketAPI>(); 1187 for (MarketAPI market : targets) { 1188 float defensiveStr = defenderStr + WarSimScript.getStationStrength(market.getFaction(), system, market.getPrimaryEntity()); 1189 if (defensiveStr > raidStr * 1.25f) { 1190 safe.add(market); 1191 } else { 1192 unsafe.add(market); 1193 } 1194 } 1195 1196 if (safe.size() == targets.size()) { 1197 info.addPara("However, all colonies " + safeStr + ", " + 1198 "owing to their orbital defenses.", opad); 1199 } else { 1200 info.addPara("The following colonies " + riskStr, opad, 1201 Misc.getNegativeHighlightColor(), riskStrHighlight); 1202 1203 FactionAPI f = Global.getSector().getPlayerFaction(); 1204 addMarketTable(info, f.getBaseUIColor(), f.getDarkUIColor(), f.getBrightUIColor(), unsafe, width, opad); 1205 } 1206 } 1207 1208 /** 1209 * -1: fleet group is weaker 1210 * 0: evenly matched 1211 * 1: fleet group is stronger 1212 * @param target 1213 * @return 1214 */ 1215 public int getRelativeFGStrength(StarSystemAPI target) { 1216 float raidStr = getRoute().getExtra().getStrengthModifiedByDamage(); 1217 float defenderStr = 0f; 1218 if (target != null) defenderStr = WarSimScript.getEnemyStrength(getFaction(), target, isPlayerTargeted()); 1219 1220 if (raidStr < defenderStr * 0.75f) { 1221 return -1; 1222 } else if (raidStr < defenderStr * 1.25f) { 1223 return 0; 1224 } else { 1225 return 1; 1226 } 1227 } 1228 1229 /** 1230 * Returns true if the defenses in the target system are weaker. 1231 * @return 1232 */ 1233 protected boolean addStrengthDesc(TooltipMakerAPI info, float opad, StarSystemAPI system, 1234 String forces, String outcomeFailure, String outcomeUncertain, String outcomeSuccess) { 1235 Color h = Misc.getHighlightColor(); 1236 1237 float raidStr = getRoute().getExtra().getStrengthModifiedByDamage(); 1238 float defenderStr = 0f; 1239 if (system != null) defenderStr = WarSimScript.getEnemyStrength(getFaction(), system, isPlayerTargeted()); 1240 1241 String strDesc = Misc.getStrengthDesc(raidStr); 1242 int numFleets = (int) getApproximateNumberOfFleets(); 1243 String fleets = "fleets"; 1244 if (numFleets == 1) fleets = "fleet"; 1245 1246 String defenderDesc = ""; 1247 String defenderHighlight = ""; 1248 Color defenderHighlightColor = h; 1249 1250 boolean potentialDanger = false; 1251 String outcome = null; 1252 if (raidStr < defenderStr * 0.75f) { 1253 defenderDesc = "The defending fleets are superior"; 1254 defenderHighlightColor = Misc.getPositiveHighlightColor(); 1255 defenderHighlight = "superior"; 1256 outcome = outcomeFailure; 1257 } else if (raidStr < defenderStr * 1.25f) { 1258 defenderDesc = "The defending fleets are evenly matched"; 1259 defenderHighlightColor = h; 1260 defenderHighlight = "evenly matched"; 1261 outcome = outcomeUncertain; 1262 potentialDanger = true; 1263 } else { 1264 defenderDesc = "The defending fleets are outmatched"; 1265 defenderHighlightColor = Misc.getNegativeHighlightColor(); 1266 defenderHighlight = "outmatched"; 1267 outcome = outcomeSuccess; 1268 potentialDanger = true; 1269 } 1270 1271 if (outcome != null) { 1272 defenderDesc += ", and " + outcome + "."; 1273 } else { 1274 defenderDesc += "."; 1275 } 1276 1277 defenderDesc = " " + defenderDesc; 1278 1279 if (system == null) defenderDesc = ""; 1280 1281 1282 LabelAPI label = info.addPara("The " + forces + " are " + 1283 "projected to be %s and likely comprised of %s " + fleets + "." + defenderDesc, 1284 opad, h, strDesc, "" + numFleets); 1285 label.setHighlight(strDesc, "" + numFleets, defenderHighlight); 1286 label.setHighlightColors(h, h, defenderHighlightColor); 1287 1288 return potentialDanger; 1289 } 1290 1291 1292 /** 1293 * Returns true if the defenses in the target system are weaker. 1294 * @return 1295 */ 1296 protected boolean addStrengthDesc(TooltipMakerAPI info, float opad, MarketAPI target, 1297 String forces, String outcomeFailure, String outcomeUncertain, String outcomeSuccess) { 1298 Color h = Misc.getHighlightColor(); 1299 1300 float raidStr = getRoute().getExtra().getStrengthModifiedByDamage(); 1301 float defenderStr = 0f; 1302 StarSystemAPI system = target.getStarSystem(); 1303 if (system != null) defenderStr = WarSimScript.getEnemyStrength(getFaction(), system, isPlayerTargeted()); 1304 1305 defenderStr += WarSimScript.getStationStrength(target.getFaction(), system, target.getPrimaryEntity()); 1306 1307 String strDesc = Misc.getStrengthDesc(raidStr); 1308 int numFleets = (int) getApproximateNumberOfFleets(); 1309 String fleets = "fleets"; 1310 if (numFleets == 1) fleets = "fleet"; 1311 1312 String defenderDesc = ""; 1313 String defenderHighlight = ""; 1314 Color defenderHighlightColor = h; 1315 1316 boolean potentialDanger = false; 1317 String outcome = null; 1318 if (raidStr < defenderStr * 0.75f) { 1319 defenderDesc = "The defending forces are superior"; 1320 defenderHighlightColor = Misc.getPositiveHighlightColor(); 1321 defenderHighlight = "superior"; 1322 outcome = outcomeFailure; 1323 } else if (raidStr < defenderStr * 1.25f) { 1324 defenderDesc = "The defending forces are evenly matched"; 1325 defenderHighlightColor = h; 1326 defenderHighlight = "evenly matched"; 1327 outcome = outcomeUncertain; 1328 potentialDanger = true; 1329 } else { 1330 defenderDesc = "The defending forces are outmatched"; 1331 defenderHighlightColor = Misc.getNegativeHighlightColor(); 1332 defenderHighlight = "outmatched"; 1333 outcome = outcomeSuccess; 1334 potentialDanger = true; 1335 } 1336 1337 if (outcome != null) { 1338 defenderDesc += ", and " + outcome + "."; 1339 } else { 1340 defenderDesc += "."; 1341 } 1342 1343 defenderDesc = " " + defenderDesc; 1344 1345 if (system == null) defenderDesc = ""; 1346 1347 1348 LabelAPI label = info.addPara("The " + forces + " are " + 1349 "projected to be %s and likely comprised of %s " + fleets + "." + defenderDesc, 1350 opad, h, strDesc, "" + numFleets); 1351 label.setHighlight(strDesc, "" + numFleets, defenderHighlight); 1352 label.setHighlightColors(h, h, defenderHighlightColor); 1353 1354 return potentialDanger; 1355 } 1356 1357 1358 1359 public FGIEventListener getListener() { 1360 return listener; 1361 } 1362 1363 public void setListener(FGIEventListener listener) { 1364 this.listener = listener; 1365 } 1366 1367 @Override 1368 public String getCommMessageSound() { 1369 if (isSendingUpdate()) { 1370 return getSoundStandardUpdate(); 1371 } 1372 return getSoundMajorPosting(); 1373 } 1374 1375 1376} 1377 1378 1379 1380