001package com.fs.starfarer.api.impl.campaign.command; 002 003import java.util.ArrayList; 004import java.util.HashSet; 005import java.util.LinkedHashSet; 006import java.util.List; 007import java.util.Set; 008 009import com.fs.starfarer.api.EveryFrameScript; 010import com.fs.starfarer.api.Global; 011import com.fs.starfarer.api.campaign.CampaignFleetAPI; 012import com.fs.starfarer.api.campaign.FactionAPI; 013import com.fs.starfarer.api.campaign.SectorEntityToken; 014import com.fs.starfarer.api.campaign.StarSystemAPI; 015import com.fs.starfarer.api.campaign.ai.CampaignFleetAIAPI.ActionType; 016import com.fs.starfarer.api.campaign.econ.MarketAPI; 017import com.fs.starfarer.api.campaign.listeners.ObjectiveEventListener; 018import com.fs.starfarer.api.impl.campaign.MilitaryResponseScript; 019import com.fs.starfarer.api.impl.campaign.MilitaryResponseScript.MilitaryResponseParams; 020import com.fs.starfarer.api.impl.campaign.fleets.EconomyFleetRouteManager; 021import com.fs.starfarer.api.impl.campaign.fleets.RouteManager; 022import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.OptionalFleetData; 023import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteData; 024import com.fs.starfarer.api.impl.campaign.ids.Factions; 025import com.fs.starfarer.api.impl.campaign.ids.MemFlags; 026import com.fs.starfarer.api.impl.campaign.ids.Tags; 027import com.fs.starfarer.api.impl.campaign.intel.events.HostileActivityEventIntel; 028import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.Objectives; 029import com.fs.starfarer.api.impl.campaign.tutorial.TutorialMissionIntel; 030import com.fs.starfarer.api.plugins.BuildObjectiveTypePicker; 031import com.fs.starfarer.api.plugins.BuildObjectiveTypePicker.BuildObjectiveParams; 032import com.fs.starfarer.api.util.CountingMap; 033import com.fs.starfarer.api.util.Misc; 034import com.fs.starfarer.api.util.TimeoutTracker; 035import com.fs.starfarer.api.util.WeightedRandomPicker; 036 037public class WarSimScript implements EveryFrameScript, ObjectiveEventListener { 038 039 public static enum LocationDanger { 040 NONE(0.01f), 041 MINIMAL(0.1f), 042 LOW(0.2f), 043 MEDIUM(0.3f), 044 HIGH(0.5f), 045 EXTREME(0.8f), 046 ; 047 048 public static LocationDanger [] vals = values(); 049 050 public float enemyStrengthFraction; 051 private LocationDanger(float enemyStrengthFraction) { 052 this.enemyStrengthFraction = enemyStrengthFraction; 053 } 054 055 public LocationDanger next() { 056 int index = this.ordinal() + 1; 057 if (index >= vals.length) index = vals.length - 1; 058 return vals[index]; 059 } 060 public LocationDanger prev() { 061 int index = this.ordinal() - 1; 062 if (index < 0) index = 0; 063 return vals[index]; 064 } 065 066 } 067 068 069 070 071 072 public static final String KEY = "$core_warSimScript"; 073 074 public static final float CHECK_DAYS = 10f; 075 public static final float CHECK_PROB = 0.5f; 076 077 078 public static WarSimScript getInstance() { 079 Object test = Global.getSector().getMemoryWithoutUpdate().get(KEY); 080 return (WarSimScript) test; 081 } 082 083 protected TimeoutTracker<String> timeouts = new TimeoutTracker<String>(); 084 085 protected List<StarSystemAPI> queue = new ArrayList<StarSystemAPI>(); 086 087 public WarSimScript() { 088 Global.getSector().getMemoryWithoutUpdate().set(KEY, this); 089 Global.getSector().getListenerManager().addListener(this); 090 091 for (StarSystemAPI system : Global.getSector().getEconomy().getStarSystemsWithMarkets()) { 092 String sid = getStarSystemTimeoutId(system); 093 timeouts.add(sid, 2f + (float) Math.random() * 3f); 094 } 095 } 096 097 protected Object readResolve() { 098 if (timeouts == null) { 099 timeouts = new TimeoutTracker<String>(); 100 } 101 return this; 102 } 103 104 public void advance(float amount) { 105 //if (true) return; 106 107 if (TutorialMissionIntel.isTutorialInProgress()) { 108 return; 109 } 110 111 float days = Misc.getDays(amount); 112 113 timeouts.advance(days); 114 115 if (queue.isEmpty()) { 116 queue = Global.getSector().getEconomy().getStarSystemsWithMarkets(); 117 } 118 119 if (!queue.isEmpty()) { 120 StarSystemAPI curr = queue.remove(0); 121 processStarSystem(curr); 122 } 123 } 124 125 public void processStarSystem(StarSystemAPI system) { 126 String sid = getStarSystemTimeoutId(system); 127 if (timeouts.contains(sid)) return; 128 timeouts.add(sid, 2f + (float) Math.random() * 3f); 129 130 CountingMap<FactionAPI> str = getFactionStrengths(system); 131 132 boolean inSpawnRange = RouteManager.isPlayerInSpawnRange(system.getCenter()); 133 134 List<FactionAPI> factions = new ArrayList<FactionAPI>(str.keySet()); 135 136// if (system.getName().toLowerCase().contains("old milix")) { 137// System.out.println("wefwefwe"); 138// } 139 140// if (system.isCurrentLocation()) { 141// System.out.println("ff23f23f32"); 142// } 143 144 for (SectorEntityToken obj : system.getEntitiesWithTag(Tags.OBJECTIVE)) { 145 List<FactionAPI> contenders = new ArrayList<FactionAPI>(); 146 147 // figure out if anyone that doesn't own it thinks they should own it 148 for (FactionAPI faction : factions) { 149 if (wantsToOwnObjective(faction, str, obj)) { 150 contenders.add(faction); 151 String id = getControlTimeoutId(obj, faction); 152 if (!timeouts.contains(id)) { 153 addObjectiveActionResponse(obj, faction, obj.getFaction()); 154 } 155 } else if (faction == obj.getFaction()) { 156 contenders.add(faction); 157 } 158 } 159 160 if (!inSpawnRange) { 161 String id = getControlSimTimeoutId(obj); 162 if (timeouts.contains(id)) continue; 163 164 timeouts.add(id, 10f + (float) Math.random() * 30f); 165 166 WeightedRandomPicker<FactionAPI> picker = new WeightedRandomPicker<FactionAPI>(); 167 float max = 0f; 168 for (FactionAPI faction : contenders) { 169 float curr = str.getCount(faction) + getStationStrength(faction, system, obj); 170 if (curr > max) { 171 max = curr; 172 } 173 } 174 if (max <= 0) continue; 175 176 for (FactionAPI faction : contenders) { 177 float curr = str.getCount(faction) + getStationStrength(faction, system, obj); 178 float w = (curr / max) - 0.5f; 179 picker.add(faction, w); 180 } 181 182 FactionAPI winner = picker.pick(); 183 if (winner != null && winner != obj.getFaction()) { 184 Objectives o = new Objectives(obj); 185 o.control(winner.getId()); 186 } 187 } 188 } 189 190 191 for (SectorEntityToken sLoc : system.getEntitiesWithTag(Tags.STABLE_LOCATION)) { 192 if (sLoc.hasTag(Tags.NON_CLICKABLE)) continue; 193 if (sLoc.hasTag(Tags.FADING_OUT_AND_EXPIRING)) continue; 194 if (!inSpawnRange) { 195 String id = getBuildSimTimeoutId(sLoc); 196 if (timeouts.contains(id)) continue; 197 198 timeouts.add(id, 20f + (float) Math.random() * 20f); 199 200 WeightedRandomPicker<FactionAPI> picker = new WeightedRandomPicker<FactionAPI>(); 201 float max = 0f; 202 for (FactionAPI faction : factions) { 203 float curr = str.getCount(faction) + getStationStrength(faction, system, sLoc); 204 if (curr > max) { 205 max = curr; 206 } 207 } 208 if (max <= 0) continue; 209 210 for (FactionAPI faction : factions) { 211 float curr = str.getCount(faction) + getStationStrength(faction, system, sLoc); 212 float w = (curr / max) - 0.5f; 213 picker.add(faction, w); 214 } 215 216 FactionAPI winner = picker.pick(); 217 if (winner != null && winner != sLoc.getFaction()) { 218 BuildObjectiveParams params = new BuildObjectiveParams(); 219 params.faction = winner; 220 params.fleet = null; 221 params.stableLoc = sLoc; 222 BuildObjectiveTypePicker pick = Global.getSector().getGenericPlugins().pickPlugin(BuildObjectiveTypePicker.class, params); 223 String type = null; 224 if (pick != null) { 225 type = pick.pickObjectiveToBuild(params); 226 } 227 if (type != null) { 228 Objectives o = new Objectives(sLoc); 229 o.build(type, winner.getId()); 230 } 231 } 232 } 233 } 234 } 235 236 /** 237 * If it doesn't already own it, it's owned by an enemy, and the faction either 238 * has the closest market to it or is the strongest in-system faction. 239 * 240 * Or: owned by a non-hostile faction that has no colony presence in the system, and this faction does 241 * @param faction 242 * @param str 243 * @param o 244 * @return 245 */ 246 protected boolean wantsToOwnObjective(FactionAPI faction, CountingMap<FactionAPI> str, SectorEntityToken o) { 247 if (o.getFaction() == faction) return false; 248 if (!o.getFaction().isHostileTo(faction) && !o.getFaction().isNeutralFaction()) { 249// for (MarketAPI curr : Misc.getMarketsInLocation(o.getContainingLocation())) { 250// if (curr.getFaction() == o.getFaction() && 251// !curr.getFaction().isNeutralFaction() && 252// !curr.getFaction().isPlayerFaction()) { 253// return false; 254// } 255// } 256// return true; 257 258 boolean ownerHasColonyInSystem = false; 259 for (MarketAPI curr : Misc.getMarketsInLocation(o.getContainingLocation())) { 260 if (curr.getFaction() == o.getFaction() && 261 !curr.getFaction().isNeutralFaction()) { 262 ownerHasColonyInSystem = true; 263 break; 264 } 265 } 266 if (ownerHasColonyInSystem) return false; 267 return true; 268 } 269 270 float minDist = Float.MAX_VALUE; 271 MarketAPI closest = null; 272 boolean haveInSystemMarkets = false; 273 for (MarketAPI market : Misc.getMarketsInLocation(o.getContainingLocation())) { 274 float dist = Misc.getDistance(market.getPrimaryEntity(), o); 275 if (dist < minDist) { 276 minDist = dist; 277 closest = market; 278 } 279 if (faction == market.getFaction()) { 280 haveInSystemMarkets = true; 281 } 282 } 283 284 if (closest != null && closest.getFaction() == faction) { 285 return true; 286 } 287 288 // pirate-like factions will try to pick up objectives that are far away from any markets 289 if (faction.getCustomBoolean(Factions.CUSTOM_PIRATE_BEHAVIOR)) { 290 if (minDist > 8000) { 291 return true; 292 } 293 } 294 295 if (!haveInSystemMarkets && closest != null && !closest.getFaction().isHostileTo(faction)) { 296 return false; 297 } 298 299 int maxStr = 0; 300 FactionAPI strongest = null; 301 for (FactionAPI curr : str.keySet()) { 302 int s = str.getCount(curr); 303 if (s > maxStr) { 304 maxStr = s; 305 strongest = curr; 306 } 307 } 308 309 return strongest == faction; 310 } 311 312 313 314 public void reportObjectiveChangedHands(SectorEntityToken objective, FactionAPI from, FactionAPI to) { 315 addObjectiveActionResponse(objective, from, to); 316 } 317 318 319 public void reportObjectiveDestroyed(SectorEntityToken objective, SectorEntityToken stableLocation, FactionAPI enemy) { 320 String id = getBuildSimTimeoutId(stableLocation); 321 timeouts.add(id, 40f + (float) Math.random() * 20f, 100f); 322 323 addObjectiveActionResponse(objective, objective.getFaction(), null); 324 } 325 326 327 protected String getStarSystemTimeoutId(StarSystemAPI system) { 328 String id = "starsystem_" + system.getId(); 329 return id; 330 } 331 332 protected String getBuildSimTimeoutId(SectorEntityToken objective) { 333 String id = "sim_build_" + objective.getId(); 334 return id; 335 } 336 337 protected String getControlSimTimeoutId(SectorEntityToken objective) { 338 String id = "sim_changedhands_" + objective.getId(); 339 return id; 340 } 341 342 protected String getControlTimeoutId(SectorEntityToken objective, FactionAPI faction) { 343 String id = faction.getId() + "_" + objective.getId(); 344 return id; 345 } 346 347 protected void addObjectiveActionResponse(SectorEntityToken objective, FactionAPI faction, FactionAPI enemy) { 348 if (faction.isNeutralFaction()) return; 349 if (faction.getCustomBoolean(Factions.CUSTOM_NO_WAR_SIM)) return; 350 351 if (enemy != null && enemy.isNeutralFaction()) return; 352 if (enemy != null && !faction.isHostileTo(enemy)) return; 353 354 String id = getControlTimeoutId(objective, faction); 355 if (timeouts.contains(id)) return; 356 357 if (isAlreadyFightingFor(objective, faction)) { // an MRS from some other source, such as a raid 358 return; 359 } 360 361 MilitaryResponseParams params = new MilitaryResponseParams(ActionType.HOSTILE, 362 objective.getId(), 363 faction, 364 objective, 365 0.4f, 366 20f + (float) Math.random() * 20f); 367 MilitaryResponseScript script = new MilitaryResponseScript(params); 368 objective.getContainingLocation().addScript(script); 369 370 timeouts.add(id, params.responseDuration * 2f); 371 } 372 373 374 375 376 public boolean isDone() { 377 return false; 378 } 379 380 public boolean runWhilePaused() { 381 return false; 382 } 383 384 385 386 387 public static CountingMap<FactionAPI> getFactionStrengths(StarSystemAPI system) { 388 CountingMap<FactionAPI> result = new CountingMap<FactionAPI>(); 389 390 Set<FactionAPI> factions = new LinkedHashSet<FactionAPI>(); 391// if (system.getName().startsWith("Askonia")) { 392// System.out.println("wefewfew"); 393// } 394 395 for (CampaignFleetAPI fleet : system.getFleets()) { 396 if (fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_TRADE_FLEET)) continue; 397 if (fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_SMUGGLER)) continue; 398 factions.add(fleet.getFaction()); 399 } 400 401 for (RouteData route : RouteManager.getInstance().getRoutesInLocation(system)) { 402 String id = route.getFactionId(); 403 if (id == null) continue; 404 FactionAPI faction = Global.getSector().getFaction(id); 405 factions.add(faction); 406 } 407 408 for (FactionAPI faction : factions) { 409 if (faction.getCustomBoolean(Factions.CUSTOM_NO_WAR_SIM)) continue; 410 411 int strength = (int) getFactionStrength(faction, system); 412 if (strength > 0) { 413 result.add(faction, strength); 414 } 415 } 416 return result; 417 } 418 419 420 421 public static float getRelativeEnemyStrength(String factionId, StarSystemAPI system) { 422 float enemyStrength = getEnemyStrength(factionId, system); 423 float factionStrength = getFactionStrength(factionId, system); 424 float f = enemyStrength / Math.max(1f, factionStrength + enemyStrength); 425 return f; 426 } 427 428 public static float getRelativeFactionStrength(String factionId, StarSystemAPI system) { 429 float enemyStrength = getEnemyStrength(factionId, system); 430 float factionStrength = getFactionStrength(factionId, system); 431 float f = factionStrength / Math.max(1f, factionStrength + enemyStrength); 432 return f; 433 } 434 435 public static float getEnemyStrength(String factionId, StarSystemAPI system) { 436 return getEnemyStrength(Global.getSector().getFaction(factionId), system, false); 437 } 438 public static float getEnemyStrength(FactionAPI faction, StarSystemAPI system) { 439 return getEnemyStrength(faction, system, false); 440 } 441 public static float getEnemyStrength(String factionId, StarSystemAPI system, boolean assumeHostileToPlayer) { 442 return getEnemyStrength(Global.getSector().getFaction(factionId), system, assumeHostileToPlayer); 443 } 444 public static float getEnemyStrength(FactionAPI faction, StarSystemAPI system, boolean assumeHostileToPlayer) { 445 float enemyStr = 0; 446 Set<String> seen = new HashSet<String>(); 447 if (EconomyFleetRouteManager.ENEMY_STRENGTH_CHECK_EXCLUDE_PIRATES) { 448 seen.add(Factions.PIRATES); 449 } 450 451 452 for (MarketAPI target : Misc.getMarketsInLocation(system)) { 453 if (!(assumeHostileToPlayer && target.getFaction().isPlayerFaction())) { 454 if (!target.getFaction().isHostileTo(faction)) continue; 455 } 456 457 if (seen.contains(target.getFactionId())) continue; 458 seen.add(target.getFactionId()); 459 enemyStr += WarSimScript.getFactionStrength(target.getFaction(), system); 460 } 461 462 if (faction.isPlayerFaction()) { 463 HostileActivityEventIntel intel = HostileActivityEventIntel.get(); 464 //HostileActivityIntel intel = HostileActivityIntel.get(system); 465 if (intel != null) { 466 enemyStr += intel.getVeryApproximateFPStrength(system); 467 } 468 } 469 470 return enemyStr; 471 } 472 473 public static float getFactionStrength(String factionId, StarSystemAPI system) { 474 return getFactionStrength(Global.getSector().getFaction(factionId), system); 475 } 476 public static float getFactionStrength(FactionAPI faction, StarSystemAPI system) { 477 float strength = 0f; 478 479// if (system.getName().toLowerCase().contains("naraka") && Factions.PIRATES.equals(faction.getId())) { 480// System.out.println("wefwefwe"); 481// } 482 483 Set<CampaignFleetAPI> seenFleets = new HashSet<CampaignFleetAPI>(); 484 for (CampaignFleetAPI fleet : system.getFleets()) { 485 if (fleet.getFaction() != faction) continue; 486 if (fleet.isStationMode()) continue; 487 if (fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_TRADE_FLEET)) continue; 488 if (fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_SMUGGLER)) continue; 489 490 if (fleet.isPlayerFleet()) continue; 491 492// if (EconomyFleetRouteManager.FACTION_STRENGTH_CHECK_EXCLUDE_PIRATES && 493// fleet.getFaction().getId().equals(Factions.PIRATES)) { 494// continue; 495// } 496 497 strength += fleet.getEffectiveStrength(); 498 499 seenFleets.add(fleet); 500 } 501 502 for (RouteData route : RouteManager.getInstance().getRoutesInLocation(system)) { 503 if (route.getActiveFleet() != null && seenFleets.contains(route.getActiveFleet())) continue; 504 505 OptionalFleetData data = route.getExtra(); 506 if (data == null) continue; 507 if (route.getFactionId() == null) continue; 508 if (!faction.getId().equals(route.getFactionId())) continue; 509 510// if (EconomyFleetRouteManager.FACTION_STRENGTH_CHECK_EXCLUDE_PIRATES && 511// route.getFactionId().equals(Factions.PIRATES)) { 512// continue; 513// } 514 515 strength += data.getStrengthModifiedByDamage(); 516 } 517 518 return strength; 519 } 520 521 522 public static float getStationStrength(FactionAPI faction, StarSystemAPI system, SectorEntityToken from) { 523 float strength = 0f; 524 525 for (CampaignFleetAPI fleet : system.getFleets()) { 526 if (!fleet.isStationMode()) continue; 527 if (fleet.getFaction() != faction) continue; 528 529 float maxDist = Misc.getBattleJoinRange() * 3f; 530 531 float dist = Misc.getDistance(from, fleet); 532 if (dist < maxDist) { 533 strength += fleet.getEffectiveStrength(); 534 } 535 } 536 537 return strength; 538 } 539 540 public TimeoutTracker<String> getTimeouts() { 541 return timeouts; 542 } 543 544 545 public static void removeFightOrdersFor(SectorEntityToken target, FactionAPI faction) { 546 for (EveryFrameScript s : target.getContainingLocation().getScripts()) { 547 if (s instanceof MilitaryResponseScript) { 548 MilitaryResponseScript script = (MilitaryResponseScript) s; 549 if (script.getParams() != null && script.getParams().target == target && 550 script.getParams().faction == faction) { 551 script.forceDone(); 552 } 553 } 554 } 555 } 556 557 public static void setNoFightingForObjective(SectorEntityToken objective, FactionAPI faction, float timeout) { 558 removeFightOrdersFor(objective, faction); 559 if (timeout > 0) { 560 WarSimScript wss = getInstance(); 561 String id = wss.getControlTimeoutId(objective, faction); 562 wss.timeouts.add(id, timeout); 563 } 564 } 565 566 public static void removeNoFightingTimeoutForObjective(SectorEntityToken objective, FactionAPI faction) { 567 WarSimScript wss = getInstance(); 568 String id = wss.getControlTimeoutId(objective, faction); 569 wss.timeouts.remove(id); 570 } 571 572 public static boolean isAlreadyFightingFor(SectorEntityToken objective, FactionAPI faction) { 573 for (EveryFrameScript s : objective.getContainingLocation().getScripts()) { 574 if (s instanceof MilitaryResponseScript) { 575 MilitaryResponseScript script = (MilitaryResponseScript) s; 576 if (script.getParams() != null && script.getParams().target == objective && 577 script.getParams().faction == faction) { 578 return true; 579 } 580 } 581 } 582 return false; 583 584 } 585 586 public static LocationDanger getDangerFor(FactionAPI faction, StarSystemAPI system) { 587 if (system == null) return LocationDanger.NONE; 588 return getDangerFor(getFactionStrength(faction, system), getEnemyStrength(faction, system)); 589 } 590 public static LocationDanger getDangerFor(String factionId, StarSystemAPI system) { 591 if (system == null) return LocationDanger.NONE; 592 return getDangerFor(getFactionStrength(factionId, system), getEnemyStrength(factionId, system)); 593 } 594 public static LocationDanger getDangerFor(float factionStrength, float enemyStrength) { 595 if (enemyStrength < 100) return LocationDanger.NONE; 596 597 float f = enemyStrength / Math.max(1f, factionStrength + enemyStrength); 598 for (LocationDanger level : LocationDanger.vals) { 599 float test = level.enemyStrengthFraction + (level.next().enemyStrengthFraction - level.enemyStrengthFraction) * 0.5f; 600 if (level == LocationDanger.NONE) test = LocationDanger.NONE.enemyStrengthFraction; 601 if (test >= f) { 602 return level; 603 } 604 } 605 return LocationDanger.EXTREME; 606 } 607} 608 609 610 611 612 613