001package com.fs.starfarer.api.impl.campaign.missions; 002 003import java.util.ArrayList; 004import java.util.Arrays; 005import java.util.List; 006import java.util.Map; 007import java.util.Random; 008 009import org.lwjgl.util.vector.Vector2f; 010 011import com.fs.starfarer.api.Global; 012import com.fs.starfarer.api.campaign.CampaignFleetAPI; 013import com.fs.starfarer.api.campaign.FactionAPI; 014import com.fs.starfarer.api.campaign.FactionAPI.ShipPickMode; 015import com.fs.starfarer.api.campaign.InteractionDialogAPI; 016import com.fs.starfarer.api.campaign.JumpPointAPI; 017import com.fs.starfarer.api.campaign.JumpPointAPI.JumpDestination; 018import com.fs.starfarer.api.campaign.LocationAPI; 019import com.fs.starfarer.api.campaign.PlanetAPI; 020import com.fs.starfarer.api.campaign.SectorEntityToken; 021import com.fs.starfarer.api.campaign.StarSystemAPI; 022import com.fs.starfarer.api.campaign.ai.FleetAIFlags; 023import com.fs.starfarer.api.campaign.econ.MarketAPI; 024import com.fs.starfarer.api.campaign.listeners.GateTransitListener; 025import com.fs.starfarer.api.campaign.rules.MemoryAPI; 026import com.fs.starfarer.api.characters.PersonAPI; 027import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.CustomRepImpact; 028import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActionEnvelope; 029import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActions; 030import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepRewards; 031import com.fs.starfarer.api.impl.campaign.ids.Factions; 032import com.fs.starfarer.api.impl.campaign.ids.Tags; 033import com.fs.starfarer.api.impl.campaign.missions.hub.HubMissionWithSearch; 034import com.fs.starfarer.api.impl.campaign.missions.hub.HubMissionWithSearch.RequiredSystemTags; 035import com.fs.starfarer.api.impl.campaign.missions.hub.HubMissionWithTriggers; 036import com.fs.starfarer.api.impl.campaign.missions.hub.MissionTrigger.TriggerAction; 037import com.fs.starfarer.api.impl.campaign.missions.hub.MissionTrigger.TriggerActionContext; 038import com.fs.starfarer.api.impl.campaign.missions.hub.ReqMode; 039import com.fs.starfarer.api.impl.campaign.procgen.StarSystemGenerator; 040import com.fs.starfarer.api.util.Misc; 041import com.fs.starfarer.api.util.Misc.Token; 042import com.fs.starfarer.api.util.WeightedRandomPicker; 043 044public class DelayedFleetEncounter extends HubMissionWithTriggers implements GateTransitListener { 045 046 public static String TRIGGER_REP_LOSS_MINOR = "DFEFWTRepLossMinor"; 047 public static String TRIGGER_REP_LOSS_MEDIUM = "DFEFWTRepLossMedium"; 048 public static String TRIGGER_REP_LOSS_HIGH = "DFEFWTRepLossHigh"; 049 050 public static enum EncounterType { 051 OUTSIDE_SYSTEM, 052 IN_HYPER_EN_ROUTE, 053 JUMP_IN_NEAR_PLAYER, 054 FROM_SOMEWHERE_IN_SYSTEM, 055 } 056 057 public static enum EncounterLocation { 058 ANYWHERE, 059 POPULATED_SYSTEM, 060 NEAR_CORE, 061 MIDRANGE, 062 FRINGE, 063 } 064 065 public static float RADIUS_FROM_CORE = 30000f; // may send fleet when within this radius from core 066 public static float BASE_DAYS_IN_SYSTEM_BEFORE_AMBUSH_IN_HYPER = 5f; 067 public static float BASE_DAYS_IN_SYSTEM_BEFORE_IN_SYSTEM_ATTACK = 10f; 068 public static float BASE_TIMEOUT = 10f; 069 070 public static float BASE_DELAY_VERY_SHORT = 365f * 0.25f; 071 public static float BASE_DELAY_SHORT = 365f * 0.67f; 072 public static float BASE_DELAY_MEDIUM = 365f * 2f; 073 public static float BASE_DELAY_LONG = 365f * 5f; 074 075 public static float BASE_ONLY_CHECK_IN_SYSTEM_DAYS = 15f; 076 077 public enum Stage { 078 WAITING, 079 SPAWN_FLEET, 080 ENDED, 081 } 082 083 public static float getRandomValue(float base) { 084 return StarSystemGenerator.getNormalRandom(Misc.random, base * 0.75f, base * 1.25f); 085 } 086 087 public static String TIMEOUT_KEY = "$core_dfe_timeout"; 088 public static boolean isInTimeout() { 089 return Global.getSector().getMemoryWithoutUpdate().getBoolean(TIMEOUT_KEY); 090 } 091 public static void setTimeout() { 092 Global.getSector().getMemoryWithoutUpdate().set(TIMEOUT_KEY, true, getRandomValue(BASE_TIMEOUT)); 093 } 094 095 public class CanSpawnFleetConditionChecker implements ConditionChecker { 096 protected StarSystemAPI lastSystemPlayerWasIn = null; 097 protected float daysInSystem = 0f; 098 protected boolean conditionsMet = false; 099 protected EncounterType typePicked = null; 100 protected Vector2f location; 101 protected SectorEntityToken foundEntity; 102 103 protected float daysBeforeInHyper = getRandomValue(BASE_DAYS_IN_SYSTEM_BEFORE_AMBUSH_IN_HYPER); 104 protected float daysBeforeInSystem = getRandomValue(BASE_DAYS_IN_SYSTEM_BEFORE_IN_SYSTEM_ATTACK); 105 106 107 public boolean conditionsMet() { 108 doCheck(); 109 return conditionsMet; 110 } 111 112 public void advance(float amount) { 113 if (conditionsMet) return; 114 115 float days = Global.getSector().getClock().convertToDays(amount); 116 CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet(); 117 118 119 StarSystemAPI curr = playerFleet.getStarSystem(); 120 if (curr != null) { 121 if (curr != lastSystemPlayerWasIn) { 122 daysInSystem = 0f; 123 } 124 lastSystemPlayerWasIn = curr; 125 daysInSystem += days; 126 } 127 } 128 129 public void doCheck() { 130 if (isInTimeout()) return; 131 if (conditionsMet) return; 132 133 CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet(); 134 if (playerFleet.getFleetPoints() > estimatedFleetPoints * playerFleetSizeAbortMult) { 135 return; 136 } 137 138 if (madeGateTransit && initialTransitFrom != null && 139 Misc.getDistanceLY(initialTransitFrom.getLocation(), 140 playerFleet.getLocationInHyperspace()) > 2f) { 141 return; 142 } 143 144 boolean onlyCheckInSystem = true; 145 StageData stage = getData(currentStage); 146 if (stage != null && stage.elapsed > onlyCheckForSpawnInSystemDays) { 147 onlyCheckInSystem = false; 148 } 149 150 151 if (isPlayerInRightRangeBand(lastSystemPlayerWasIn)) { 152 if (allowedTypes.contains(EncounterType.IN_HYPER_EN_ROUTE) && !onlyCheckInSystem) { 153 if (playerFleet.isInHyperspace()) { 154 float maxSpeed = Misc.getSpeedForBurnLevel(playerFleet.getFleetData().getBurnLevel()); 155 //float threshold = Misc.getSpeedForBurnLevel(15); 156 float currSpeed = playerFleet.getVelocity().length(); 157 if (currSpeed >= maxSpeed * 0.9f) { 158 //Misc.findNearestJumpPointThatCouldBeExitedFrom(playerFleet); 159 float dist = getRandomValue(2500f); 160 float dir = Misc.getAngleInDegrees(playerFleet.getVelocity()); 161 dir += 75f - 150f * genRandom.nextFloat(); 162 location = Misc.getUnitVectorAtDegreeAngle(dir); 163 location.scale(dist); 164 Vector2f.add(location, playerFleet.getLocation(), location); 165 conditionsMet = true; 166 typePicked = EncounterType.IN_HYPER_EN_ROUTE; 167 //getPreviousCreateFleetAction().params.locInHyper = playerFleet.getLocationInHyperspace(); 168 return; 169 } 170 } 171 } 172 if (allowedTypes.contains(EncounterType.OUTSIDE_SYSTEM)) { 173 if (playerFleet.isInHyperspace() && daysInSystem > daysBeforeInHyper && lastSystemPlayerWasIn != null) { 174 float dist = Misc.getDistance(lastSystemPlayerWasIn.getLocation(), playerFleet.getLocationInHyperspace()); 175 if (dist < 3000f) { 176 conditionsMet = true; 177 typePicked = EncounterType.OUTSIDE_SYSTEM; 178 return; 179 } 180 } 181 } 182 if (allowedTypes.contains(EncounterType.FROM_SOMEWHERE_IN_SYSTEM)) { 183 if (playerFleet.getStarSystem() == lastSystemPlayerWasIn && daysInSystem > daysBeforeInSystem && 184 lastSystemPlayerWasIn != null) { 185 conditionsMet = true; 186 typePicked = EncounterType.FROM_SOMEWHERE_IN_SYSTEM; 187 return; 188 } 189 } 190 if (allowedTypes.contains(EncounterType.JUMP_IN_NEAR_PLAYER)) { 191 if (playerFleet.getStarSystem() == lastSystemPlayerWasIn && daysInSystem > daysBeforeInSystem && 192 lastSystemPlayerWasIn != null) { 193 // spawn from: 194 // a nearby jump-point 195 // a gas giant gravity well 196 // a planet gravity well (transverse jump) 197 // a star gravity well 198 SectorEntityToken entity = Misc.findNearestJumpPointTo(playerFleet); 199 if (entity != null) { 200 float dist = Misc.getDistance(playerFleet, entity); 201 if (dist < 3000f) { 202 conditionsMet = true; 203 } 204 } 205 if (!conditionsMet) { 206 entity = Misc.findNearestPlanetTo(playerFleet, true, false); 207 if (entity != null) { 208 float dist = Misc.getDistance(playerFleet, entity); 209 if (dist < 3000f) { 210 conditionsMet = true; 211 } 212 } 213 } 214 // only jump in near gas giants; don't fall back to other planets/stars 215 // it feels too weird/forced if the fleet can just jump in anywhere 216// if (!conditionsMet) { 217// entity = Misc.findNearestPlanetTo(playerFleet, false, false); 218// if (entity != null) { 219// float dist = Misc.getDistance(playerFleet, entity); 220// if (dist < 3000f) { 221// conditionsMet = true; 222// } 223// } 224// } 225// if (!conditionsMet) { 226// entity = Misc.findNearestPlanetTo(playerFleet, false, true); 227// if (entity != null) { 228// float dist = Misc.getDistance(playerFleet, entity); 229// if (dist < 3000f) { 230// conditionsMet = true; 231// } 232// } 233// } 234 235 if (conditionsMet) { 236 foundEntity = entity; 237 typePicked = EncounterType.JUMP_IN_NEAR_PLAYER; 238 return; 239 } 240 } 241 } 242 } 243 } 244 245 protected boolean isPlayerInRightRangeBand(LocationAPI system) { 246 //if (allowedLocations.contains(EncounterLocation.ANYWHERE)) return true; 247 CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet(); 248 249 if (system instanceof StarSystemAPI && system == playerFleet.getContainingLocation()) { 250 if (requiredTags != null) { 251 for (RequiredSystemTags req : requiredTags) { 252 if (!req.systemMatchesRequirement((StarSystemAPI) system)) { 253 return false; 254 } 255 } 256 } 257 258 List<MarketAPI> markets = Misc.getMarketsInLocation(system); 259 if (requireLargestMarketNotHostileToFaction != null) { 260 MarketAPI largest = null; 261 MarketAPI largestHostile = null; 262 int maxSize = 0; 263 int maxHostileSize = 0; 264 for (MarketAPI market : markets) { 265 if (market.getSize() > maxSize) { 266 largest = market; 267 maxSize = market.getSize(); 268 } 269 if (market.getFaction().isHostileTo(requireLargestMarketNotHostileToFaction)) { 270 if (market.getSize() > maxHostileSize) { 271 largestHostile = market; 272 maxHostileSize = market.getSize(); 273 } 274 } 275 } 276 if (largestHostile != null && maxHostileSize > maxSize) { 277 return false; 278 } 279 } 280 if (requiredFactionPresence != null) { 281 boolean found = false; 282 for (MarketAPI market : markets) { 283 if (requiredFactionPresence.contains(market.getFactionId())) { 284 found = true; 285 break; 286 } 287 } 288 if (!found) { 289 return false; 290 } 291 } 292 } 293 294 Vector2f coreCenter = new Vector2f(); 295 296 float fringeRange = 46000; 297 298 float nearMarketRange = 5000f; 299 boolean nearCoreMarket = false; 300 boolean nearAnyMarket = false; 301 MarketAPI nearest = null; 302 float minDist = Float.MAX_VALUE; 303 304 float count = 0f; 305 for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) { 306 if (market.isHidden()) continue; 307 if (market.getContainingLocation().hasTag(Tags.THEME_CORE_POPULATED)) { 308 Vector2f.add(coreCenter, market.getLocationInHyperspace(), coreCenter); 309 count++; 310 311 if (!nearCoreMarket) { 312 float dist = Misc.getDistance(market.getLocation(), playerFleet.getLocationInHyperspace()); 313 nearCoreMarket = dist < nearMarketRange; 314 if (dist < minDist) { 315 nearest = market; 316 minDist = dist; 317 } 318 } 319 } else if (!nearAnyMarket) { 320 float dist = Misc.getDistance(market.getLocation(), playerFleet.getLocationInHyperspace()); 321 nearAnyMarket = dist < nearMarketRange; 322 if (dist < minDist) { 323 nearest = market; 324 minDist = dist; 325 } 326 } 327 } 328 329 if ((nearCoreMarket || nearAnyMarket) && !allowInsidePopulatedSystems && nearest != null) { 330 if (!Global.getSector().getPlayerFleet().isInHyperspace() && 331 system == nearest.getStarSystem()) { 332 return false; 333 } 334 } 335 336 337 if (count > 0) { 338 coreCenter.scale(1f / count); 339 } 340 341 if (nearCoreMarket && allowedLocations.contains(EncounterLocation.NEAR_CORE)) { 342 return true; 343 } 344 if (nearAnyMarket && allowedLocations.contains(EncounterLocation.POPULATED_SYSTEM)) { 345 return true; 346 } 347 348 for (EncounterLocation location : allowedLocations) { 349 if (location == EncounterLocation.NEAR_CORE) continue; 350 if (location == EncounterLocation.POPULATED_SYSTEM) continue; 351 352 //location = EncounterLocation.FRINGE; 353 354 float distFromCore = Misc.getDistance(coreCenter, playerFleet.getLocationInHyperspace()); 355 356 if (location == EncounterLocation.MIDRANGE) { 357 if (distFromCore > fringeRange || nearCoreMarket) { 358 continue; 359 } 360 } 361 362 if (location == EncounterLocation.FRINGE) { 363 if (distFromCore < fringeRange || nearCoreMarket) { 364 continue; 365 } 366 } 367 368 return true; 369 } 370 return false; 371 } 372 } 373 374 public class DFEPlaceFleetAction implements TriggerAction { 375 public DFEPlaceFleetAction() { 376 } 377 378 public void doAction(TriggerActionContext context) { 379 setTimeout(); 380 381// protected EncounterType typePicked = null; 382// protected Vector2f location; 383// protected SectorEntityToken foundEntity; 384 CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet(); 385 playerFleet.getContainingLocation().addEntity(context.fleet); 386 387 if (checker.typePicked == EncounterType.OUTSIDE_SYSTEM) { 388 Vector2f loc = Misc.getPointAtRadius(playerFleet.getLocationInHyperspace(), 1000f); 389 context.fleet.setLocation(loc.x, loc.y); 390 } else if (checker.typePicked == EncounterType.FROM_SOMEWHERE_IN_SYSTEM) { 391 WeightedRandomPicker<MarketAPI> from = new WeightedRandomPicker<MarketAPI>(genRandom); 392 for (MarketAPI curr : Global.getSector().getEconomy().getMarkets(playerFleet.getContainingLocation())) { 393 float w = 0f; 394 if (curr.getFaction() == context.fleet.getFaction()) { 395 w = curr.getSize() * 10000f; 396 } else if (!curr.getFaction().isHostileTo(context.fleet.getFaction())) { 397 w = curr.getSize(); 398 } 399 if (w > 0f) { 400 from.add(curr, w); 401 } 402 } 403 MarketAPI market = from.pick(); 404 if (market != null) { 405 float dir = Misc.getAngleInDegrees(playerFleet.getLocation(), market.getPrimaryEntity().getLocation()); 406 Vector2f loc = HubMissionWithTriggers.pickLocationWithinArc(genRandom, playerFleet, 407 dir, 30f, 3000f, 3000f, 3000f); 408 context.fleet.setLocation(loc.x, loc.y); 409 } else { 410 Vector2f loc = Misc.getPointAtRadius(playerFleet.getLocation(), 3000f); 411 context.fleet.setLocation(loc.x, loc.y); 412 } 413 } else if (checker.typePicked == EncounterType.JUMP_IN_NEAR_PLAYER) { 414 JumpDestination dest = new JumpDestination(checker.foundEntity, null); 415 if (checker.foundEntity instanceof JumpPointAPI) { 416 JumpPointAPI jp = (JumpPointAPI) checker.foundEntity; 417 jp.open(); 418 context.fleet.setLocation(jp.getLocation().x, jp.getLocation().y); 419 } else if (checker.foundEntity instanceof PlanetAPI) { 420 dest.setMinDistFromToken(checker.foundEntity.getRadius() + 50f); 421 dest.setMaxDistFromToken(checker.foundEntity.getRadius() + 400f); 422 } 423 context.fleet.updateFleetView(); // so that ship views exist and can do the jump-in warping animation 424 context.fleet.getContainingLocation().removeEntity(context.fleet); 425 Global.getSector().getHyperspace().addEntity(context.fleet); 426 context.fleet.setLocation(1000000000, 0); 427 Global.getSector().doHyperspaceTransition(context.fleet, null, dest); 428 } else if (checker.typePicked == EncounterType.IN_HYPER_EN_ROUTE) { 429 context.fleet.setLocation(checker.location.x, checker.location.y); 430 } 431 432 433 // this is only relevant if an intercept isn't ordered; the triggerIntercept method sets this 434 // as well. 435 float radius = 2000f * (0.5f + 0.5f * genRandom.nextFloat()); 436 Vector2f approximatePlayerLoc = Misc.getPointAtRadius(playerFleet.getLocation(), radius); 437 context.fleet.getMemoryWithoutUpdate().set(FleetAIFlags.PLACE_TO_LOOK_FOR_TARGET, approximatePlayerLoc, 2f); 438 } 439 } 440 441 442 protected float minDelay; 443 protected float maxDelay; 444 protected float onlyCheckForSpawnInSystemDays; 445 protected String globalEndFlag; 446 447 protected List<EncounterType> allowedTypes = new ArrayList<EncounterType>(); 448 protected List<EncounterLocation> allowedLocations = new ArrayList<EncounterLocation>(); 449 protected boolean allowInsidePopulatedSystems = true; 450 protected String requireLargestMarketNotHostileToFaction = null; 451 protected List<String> requiredFactionPresence = null; 452 protected List<RequiredSystemTags> requiredTags = null; 453 454 protected boolean canBeAvoidedByGateTransit = true; 455 protected boolean madeGateTransit = false; 456 protected LocationAPI initialTransitFrom = null; 457 458 protected CanSpawnFleetConditionChecker checker; 459 460 public DelayedFleetEncounter(Random random, String missionId) { 461 if (random == null) random = new Random(Misc.genRandomSeed()); 462 setGenRandom(random); 463 setNoRepChanges(); 464 globalEndFlag = "$" + "dfe"+ "_" + missionId + "_" + Misc.genUID(); 465 setMissionId(missionId); 466 467 setTypes(EncounterType.OUTSIDE_SYSTEM, EncounterType.JUMP_IN_NEAR_PLAYER, EncounterType.IN_HYPER_EN_ROUTE); 468 // don't, since in-system attacks are a jump-in near the player, so it's probably fine anyway 469 // and possibly interesting if not fine 470 //requireDFESystemTags(ReqMode.NOT_ANY, Tags.THEME_UNSAFE); 471 472 onlyCheckForSpawnInSystemDays = BASE_ONLY_CHECK_IN_SYSTEM_DAYS * (0.5f + genRandom.nextFloat()); 473 Global.getSector().getListenerManager().addListener(this); 474 } 475 476 public void setCanNotBeAvoidedByGateTransit() { 477 canBeAvoidedByGateTransit = false; 478 } 479 480 public void reportFleetTransitingGate(CampaignFleetAPI fleet, SectorEntityToken gateFrom, SectorEntityToken gateTo) { 481 if (!fleet.isPlayerFleet() || gateFrom == null) return; 482 483 if (getCurrentStage() == Stage.WAITING && 484 getElapsedInCurrentStage() < waitDays * 0.9f) { 485 return; 486 } 487 488 float dist = Misc.getDistanceLY(gateFrom, gateTo); 489 if (dist > 2f) { 490 madeGateTransit = true; 491 } 492 if (initialTransitFrom == null) { 493 initialTransitFrom = gateFrom.getContainingLocation(); 494 } 495 } 496 497 @Override 498 protected void notifyEnding() { 499 super.notifyEnding(); 500 Global.getSector().getListenerManager().removeListener(this); 501 } 502 503 public void setAllowInsidePopulatedSystems(boolean allowInsidePopulatedSystems) { 504 this.allowInsidePopulatedSystems = allowInsidePopulatedSystems; 505 } 506 public void setRequireLargestMarketNotHostileToFaction(String requireLargestMarketNotHostileToFaction) { 507 this.requireLargestMarketNotHostileToFaction = requireLargestMarketNotHostileToFaction; 508 } 509 public void setRequireFactionPresence(String ... factions) { 510 if (factions == null || factions.length <= 0) { 511 requiredFactionPresence = null; 512 } else { 513 requiredFactionPresence = new ArrayList<String>(); 514 requiredFactionPresence.addAll(Arrays.asList(factions)); 515 } 516 } 517 518 public void clearDFESystemTagRequirements() { 519 requiredTags = null; 520 } 521 public void requireDFESystemTags(ReqMode mode, String ... tags) { 522 if (requiredTags == null) { 523 requiredTags = new ArrayList<HubMissionWithSearch.RequiredSystemTags>(); 524 } 525 RequiredSystemTags req = new RequiredSystemTags(mode, tags); 526 requiredTags.add(req); 527 } 528 529 public void setEncounterInHyper() { 530 setTypes(EncounterType.OUTSIDE_SYSTEM, EncounterType.IN_HYPER_EN_ROUTE); 531 } 532 public void setEncounterOutsideSystem() { 533 setTypes(EncounterType.OUTSIDE_SYSTEM); 534 } 535 public void setEncounterInSystemFromJumpPoint() { 536 setTypes(EncounterType.JUMP_IN_NEAR_PLAYER); 537 } 538 public void setEncounterFromSomewhereInSystem() { 539 setTypes(EncounterType.FROM_SOMEWHERE_IN_SYSTEM); 540 } 541 public void setEncounterInHyperEnRoute() { 542 setTypes(EncounterType.IN_HYPER_EN_ROUTE); 543 } 544 545 public void setTypes(EncounterType ... types) { 546 allowedTypes.clear(); 547 allowedTypes.addAll(Arrays.asList(types)); 548 } 549 public void setLocations(boolean allowInsidePopulatedSystems, String requireLargestMarketNotHostileToFaction, 550 EncounterLocation ... locations) { 551 allowedLocations.clear(); 552 allowedLocations.addAll(Arrays.asList(locations)); 553 setAllowInsidePopulatedSystems(allowInsidePopulatedSystems); 554 setRequireLargestMarketNotHostileToFaction(requireLargestMarketNotHostileToFaction); 555 } 556 557 public void setDelay(float minDays, float maxDays) { 558 this.minDelay = minDays; 559 this.maxDelay = maxDays; 560 } 561 562 public void setDelay(float base) { 563 this.minDelay = base * 0.5f; 564 this.maxDelay = base * 1.5f; 565 } 566 567 public void setLocationAnyPopulated(boolean allowInsidePopulatedSystems, String requireLargestMarketNotHostileToFaction) { 568 setLocations(allowInsidePopulatedSystems, requireLargestMarketNotHostileToFaction, 569 EncounterLocation.POPULATED_SYSTEM); 570 } 571 572 public void setLocationCoreOnly(boolean allowInsidePopulatedSystems, String requireLargestMarketNotHostileToFaction) { 573 setLocations(allowInsidePopulatedSystems, requireLargestMarketNotHostileToFaction, 574 EncounterLocation.NEAR_CORE); 575 } 576 577 public void setLocationOuterSector(boolean allowInsidePopulatedSystems, String requireLargestMarketNotHostileToFaction) { 578 setLocations(allowInsidePopulatedSystems, requireLargestMarketNotHostileToFaction, 579 EncounterLocation.FRINGE, EncounterLocation.MIDRANGE); 580 } 581 582 public void setLocationAnywhere(boolean allowInsidePopulatedSystems, String requireLargestMarketNotHostileToFaction) { 583 setLocations(allowInsidePopulatedSystems, requireLargestMarketNotHostileToFaction, 584 EncounterLocation.ANYWHERE); 585 } 586 587 public void setLocationFringeOnly(boolean allowInsidePopulatedSystems, String requireLargestMarketNotHostileToFaction) { 588 setLocations(allowInsidePopulatedSystems, requireLargestMarketNotHostileToFaction, 589 EncounterLocation.FRINGE); 590 } 591 592 public void setLocationInnerSector(boolean allowInsidePopulatedSystems, String requireLargestMarketNotHostileToFaction) { 593 setLocations(allowInsidePopulatedSystems, requireLargestMarketNotHostileToFaction, 594 EncounterLocation.NEAR_CORE, EncounterLocation.MIDRANGE); 595 } 596 597 598 public void setDelayNone() { 599 setDelay(0f); 600 } 601 602 public void setDelayVeryShort() { 603 setDelay(BASE_DELAY_VERY_SHORT); 604 } 605 606 public void setDelayShort() { 607 setDelay(BASE_DELAY_SHORT); 608 } 609 610 public void setDelayMedium() { 611 setDelay(BASE_DELAY_MEDIUM); 612 } 613 614 public void setDelayLong() { 615 setDelay(BASE_DELAY_LONG); 616 } 617 618 public void triggerSetStandardAggroInterceptFlags() { 619 triggerMakeHostileAndAggressive(); 620 triggerFleetAllowLongPursuit(); 621 triggerSetFleetAlwaysPursue(); 622 triggerOrderFleetInterceptPlayer(); 623 triggerOrderFleetMaybeEBurn(); 624 } 625 626 627 @Override 628 protected void advanceImpl(float amount) { 629 super.advanceImpl(amount); 630 631 //System.out.println("Wait: " + waitDays); 632 //System.out.println("Elapsed: " + getElapsedInCurrentStage()); 633 634 if (getCurrentStage() == Stage.SPAWN_FLEET && checker != null) { 635 checker.advance(amount); 636 } 637 638 } 639 640 protected float waitDays; 641 public void beginCreate() { 642 checker = new CanSpawnFleetConditionChecker(); 643 644 if (minDelay <= 0 && maxDelay <= 0) { 645 // this check does NOT apply to in hyper in route 646 // so, if "immediate" spawn, assuming all encounter types are allowed: 647 // - wait a bit before spawning when player exits system 648 // - wait a bit longer before it jumps into the system 649 // - and wait a little bit (i.e. only check in-system spawn) before spawning in-hyper-en-route 650 checker.daysBeforeInHyper = (0.5f + (0.5f + genRandom.nextFloat())) * 2f; 651 checker.daysBeforeInSystem = (0.5f + (0.5f + genRandom.nextFloat())) * 3f; 652 onlyCheckForSpawnInSystemDays = 0.5f + (0.5f + genRandom.nextFloat()); 653 } 654 655 waitDays = minDelay + (maxDelay - minDelay) * genRandom.nextFloat(); 656 connectWithDaysElapsed(Stage.WAITING, Stage.SPAWN_FLEET, waitDays); 657 setStartingStage(Stage.WAITING); 658 setSuccessStage(Stage.ENDED); 659 setStageOnGlobalFlag(Stage.ENDED, globalEndFlag); 660 661 setStageOnCustomCondition(Stage.ENDED, new ConditionChecker() { 662 public boolean conditionsMet() { 663 CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet(); 664 // abort if player is too strong 665 if (playerFleet.getFleetPoints() > estimatedFleetPoints * playerFleetSizeAbortMult) { 666 if ((getCurrentStage() == Stage.WAITING && 667 getElapsedInCurrentStage() > waitDays * 0.9f) || 668 getCurrentStage() == Stage.SPAWN_FLEET) { 669 return true; 670 } 671 return false; 672 } 673 674 // abort if player made a gate transit shortly before the encounter would trigger 675 // the "shortly before" part is handled in reportFleetTransitingGate() 676 // and also check that they didn't transit back to their original location 677 if (madeGateTransit && initialTransitFrom != null && 678 Misc.getDistanceLY(initialTransitFrom.getLocation(), 679 playerFleet.getLocationInHyperspace()) > 2f) { 680 return true; 681 } 682 return false; 683 } 684 }); 685 686 beginCustomTrigger(checker, Stage.SPAWN_FLEET); 687 triggerMakeAllFleetFlagsPermanent(); 688 } 689 690 public void endCreate() { 691 triggerSetGlobalMemoryValue(globalEndFlag, true); 692 triggerCustomAction(new DFEPlaceFleetAction()); 693 endTrigger(); 694 accept(null, null); 695 } 696 697 698 public void triggerSetAdjustStrengthBasedOnQuality(boolean randomize, float quality) { 699 if (randomize) { 700 triggerRandomizeFleetProperties(); 701 } 702 setQuality(quality); 703 setUseQualityInsteadOfQualityFraction(true); 704 triggerAutoAdjustFleetStrengthMajor(); 705 setUseQualityInsteadOfQualityFraction(false); 706 } 707 708 protected PersonAPI personForRepLoss = null; 709 public void setFleetWantsThing(String originalFactionId, 710 String thing, 711 String thingItOrThey, 712 String thingDesc, 713 int paymentOffered, 714 boolean aggressiveIfDeclined, 715 ComplicationRepImpact repImpact, 716 String failTrigger, 717 PersonAPI personForRepLoss) { 718 719 this.personForRepLoss = personForRepLoss; 720 721 triggerSetFleetMissionRef("$" + getMissionId() + "_ref"); 722 triggerSetFleetMissionRef("$fwt_ref"); 723 724 if (aggressiveIfDeclined) { 725 triggerSetPirateFleet(); 726 triggerMakeHostileAndAggressive(); 727 } 728 729 if (repImpact == ComplicationRepImpact.LOW) { 730 triggerMakeLowRepImpact(); 731 } else if (repImpact == ComplicationRepImpact.NONE) { 732 triggerMakeNoRepImpact(); 733 } 734 735 triggerFleetAllowLongPursuit(); 736 triggerSetFleetAlwaysPursue(); 737 738 FactionAPI faction = Global.getSector().getFaction(originalFactionId); 739 if (faction.getCustomBoolean(Factions.CUSTOM_SPAWNS_AS_INDEPENDENT)) { 740 triggerSetFleetFaction(Factions.INDEPENDENT); 741 triggerSetFleetMemoryValue("$fwt_originalFaction", originalFactionId); 742 } 743 744 triggerSetFleetMemoryValue("$fwt_wantsThing", true); 745 triggerSetFleetMemoryValue("$fwt_aggressive", aggressiveIfDeclined); 746 triggerSetFleetMemoryValue("$fwt_thing", getWithoutArticle(thing)); 747 triggerSetFleetMemoryValue("$fwt_Thing", Misc.ucFirst(getWithoutArticle(thing))); 748 triggerSetFleetMemoryValue("$fwt_theThing", thing); 749 triggerSetFleetMemoryValue("$fwt_TheThing", Misc.ucFirst(thing)); 750 triggerSetFleetMemoryValue("$fwt_payment", Misc.getWithDGS(paymentOffered)); 751 triggerSetFleetMemoryValue("$fwt_itOrThey", thingItOrThey); 752 triggerSetFleetMemoryValue("$fwt_ItOrThey", Misc.ucFirst(thingItOrThey)); 753 754 String thingItOrThem = "them"; 755 if ("it".equals(thingItOrThey)) thingItOrThem = "it"; 756 triggerSetFleetMemoryValue("$fwt_itOrThem", thingItOrThem); 757 triggerSetFleetMemoryValue("$fwt_ItOrThem", Misc.ucFirst(thingItOrThem)); 758 759 triggerSetFleetMemoryValue("$fwt_thingDesc", thingDesc); 760 triggerSetFleetMemoryValue("$fwt_ThingDesc", Misc.ucFirst(thingDesc)); 761 762 if (failTrigger == null) { 763 failTrigger = "FWTDefaultFailTrigger"; 764 } 765 triggerSetFleetMemoryValue("$fwt_missionFailTrigger", failTrigger); 766 } 767 768 769// public void setAbortWhenPlayerFleetTooStrong() { 770// playerFleetSizeAbortMult = 2f; 771// } 772 773 public void setAlwaysAbort() { 774 playerFleetSizeAbortMult = 0f; 775 } 776 public void setDoNotAbortWhenPlayerFleetTooStrong() { 777 playerFleetSizeAbortMult = 100000000f; 778 } 779 780 public void setPlayerFleetSizeAbortMult(float playerFleetSizeAbortMult) { 781 this.playerFleetSizeAbortMult = playerFleetSizeAbortMult; 782 } 783 784 protected FleetSize fleetSize = FleetSize.MEDIUM; 785 protected float estimatedFleetPoints = 0f; 786 protected float playerFleetSizeAbortMult = 2f; 787 protected void computeThresholdPoints(String factionId) { 788 FactionAPI faction = Global.getSector().getFaction(factionId); 789 float maxPoints = faction.getApproximateMaxFPPerFleet(ShipPickMode.PRIORITY_THEN_ALL); 790 estimatedFleetPoints = fleetSize.maxFPFraction * maxPoints; 791 } 792 793 public void triggerFleetSetFaction(String factionId) { 794 computeThresholdPoints(factionId); 795 super.triggerSetFleetFaction(factionId); 796 } 797 798 @Override 799 public void triggerCreateFleet(FleetSize size, FleetQuality quality, String factionId, String type, SectorEntityToken roughlyWhere) { 800 fleetSize = size; 801 computeThresholdPoints(factionId); 802 super.triggerCreateFleet(size, quality, factionId, type, roughlyWhere); 803 } 804 @Override 805 public void triggerCreateFleet(FleetSize size, FleetQuality quality, String factionId, String type, StarSystemAPI roughlyWhere) { 806 fleetSize = size; 807 computeThresholdPoints(factionId); 808 super.triggerCreateFleet(size, quality, factionId, type, roughlyWhere); 809 } 810 @Override 811 public void triggerCreateFleet(FleetSize size, FleetQuality quality, String factionId, String type, Vector2f locInHyper) { 812 fleetSize = size; 813 computeThresholdPoints(factionId); 814 super.triggerCreateFleet(size, quality, factionId, type, locInHyper); 815 } 816 817 @Override 818 protected boolean create(MarketAPI createdAt, boolean barEvent) { 819 return false; 820 } 821 @Override 822 public String getBaseName() { 823 return null; 824 } 825 826 @Override 827 public boolean isHidden() { 828 return true; 829 } 830 831 832 @Override 833 protected boolean callAction(String action, String ruleId, InteractionDialogAPI dialog, List<Token> params, Map<String, MemoryAPI> memoryMap) { 834 float repLossPerson = 0f; 835 float repLossFaction = 0f; 836 if ("repLossMinor".equals(action)) { 837 repLossPerson = -RepRewards.SMALL; 838 repLossFaction = -RepRewards.TINY; 839 } else if ("repLossMedium".equals(action)) { 840 repLossPerson = -RepRewards.HIGH; 841 repLossFaction = -RepRewards.SMALL; 842 } else if ("repLossHigh".equals(action)) { 843 repLossPerson = -RepRewards.EXTREME; 844 repLossFaction = -RepRewards.HIGH; 845 } 846 847 if (repLossPerson != 0 && personForRepLoss != null) { 848 CustomRepImpact impact = new CustomRepImpact(); 849 impact.delta = repLossPerson; 850 Global.getSector().adjustPlayerReputation( 851 new RepActionEnvelope(RepActions.CUSTOM, impact, 852 null, dialog.getTextPanel(), true), personForRepLoss); 853 854 impact.delta = repLossFaction; 855 Global.getSector().adjustPlayerReputation( 856 new RepActionEnvelope(RepActions.CUSTOM, impact, 857 null, dialog.getTextPanel(), true), personForRepLoss.getFaction().getId()); 858 859 return true; 860 } 861 return super.callAction(action, ruleId, dialog, params, memoryMap); 862 } 863 864 865} 866 867 868 869