1package com.fs.starfarer.api.impl.campaign.events.nearby;
4import java.text.DecimalFormat;
5import java.text.ParseException;
6import java.util.HashSet;
8import java.util.Locale;
10import java.util.Random;
13import org.lwjgl.util.vector.Vector2f;
15import com.fs.starfarer.api.Global;
16import com.fs.starfarer.api.campaign.CampaignFleetAPI;
17import com.fs.starfarer.api.campaign.CargoAPI;
18import com.fs.starfarer.api.campaign.CustomCampaignEntityAPI;
19import com.fs.starfarer.api.campaign.FactionAPI;
20import com.fs.starfarer.api.campaign.InteractionDialogAPI;
21import com.fs.starfarer.api.campaign.LocationAPI;
22import com.fs.starfarer.api.campaign.RepLevel;
23import com.fs.starfarer.api.campaign.SectorEntityToken;
24import com.fs.starfarer.api.campaign.StarSystemAPI;
25import com.fs.starfarer.api.campaign.TextPanelAPI;
26import com.fs.starfarer.api.campaign.econ.MarketAPI;
27import com.fs.starfarer.api.campaign.events.CampaignEventTarget;
28import com.fs.starfarer.api.campaign.rules.MemoryAPI;
29import com.fs.starfarer.api.characters.PersonAPI;
30import com.fs.starfarer.api.combat.ShipVariantAPI;
31import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.CustomRepImpact;
32import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActionEnvelope;
33import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActions;
34import com.fs.starfarer.api.impl.campaign.DerelictShipEntityPlugin;
35import com.fs.starfarer.api.impl.campaign.DerelictShipEntityPlugin.DerelictShipData;
36import com.fs.starfarer.api.impl.campaign.events.BaseEventPlugin;
37import com.fs.starfarer.api.impl.campaign.fleets.PirateFleetManager;
38import com.fs.starfarer.api.impl.campaign.fleets.RouteManager;
39import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.OptionalFleetData;
40import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteData;
41import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteFleetSpawner;
42import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteSegment;
43import com.fs.starfarer.api.impl.campaign.ids.Abilities;
44import com.fs.starfarer.api.impl.campaign.ids.Commodities;
45import com.fs.starfarer.api.impl.campaign.ids.Entities;
46import com.fs.starfarer.api.impl.campaign.ids.Factions;
47import com.fs.starfarer.api.impl.campaign.ids.FleetTypes;
48import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
49import com.fs.starfarer.api.impl.campaign.ids.Tags;
50import com.fs.starfarer.api.impl.campaign.intel.bases.PirateBaseManager;
51import com.fs.starfarer.api.impl.campaign.intel.misc.DistressCallIntel;
52import com.fs.starfarer.api.impl.campaign.procgen.themes.BaseThemeGenerator;
53import com.fs.starfarer.api.impl.campaign.procgen.themes.RuinsFleetRouteManager;
54import com.fs.starfarer.api.impl.campaign.procgen.themes.SalvageSpecialAssigner;
55import com.fs.starfarer.api.impl.campaign.rulecmd.AddRemoveCommodity;
56import com.fs.starfarer.api.impl.campaign.rulecmd.BaseCommandPlugin;
57import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.TransmitterTrapSpecial.TransmitterTrapSpecialData;
58import com.fs.starfarer.api.util.IntervalUtil;
59import com.fs.starfarer.api.util.Misc;
60import com.fs.starfarer.api.util.Misc.Token;
61import com.fs.starfarer.api.util.TimeoutTracker;
62import com.fs.starfarer.api.util.WeightedRandomPicker;
72 public static enum DistressEventType {
101 Object readResolve() {
143 if (!playerFleet.isInHyperspace())
return;
146 if (Misc.isInAbyss(playerFleet)) {
150 if ((
float) Math.random() < skipProb)
return;
152 WeightedRandomPicker<String> factions = SalvageSpecialAssigner.getNearbyFactions(
null, playerFleet,
156 if (params !=
null) {
160 CustomCampaignEntityAPI
entity = (CustomCampaignEntityAPI) BaseThemeGenerator.addSalvageEntity(
162 Entities.WRECK, Factions.NEUTRAL, params);
163 entity.addTag(Tags.EXPIRES);
164 entity.setDiscoverable(
false);
165 SalvageSpecialAssigner.assignSpecials(
entity,
false);
168 float distFromPlayer = 3000f + (float) Math.random() * 2000f;
169 Vector2f loc = Misc.getPointAtRadius(playerFleet.getLocationInHyperspace(), distFromPlayer,
new Random());
171 entity.getLocation().x = loc.x;
172 entity.getLocation().y = loc.y;
175 float angle = Misc.getAngleInDegrees(loc, playerFleet.getLocation());
177 angle = angle - arc /2f + arc * (float) Math.random();
178 float speed = 10f + 10f * (float) Math.random();
180 float depth = Misc.getAbyssalDepth(loc);
181 speed *= (0.5f + 0.5f * (1f - depth));
183 Vector2f vel = Misc.getUnitVectorAtDegreeAngle(angle);
185 entity.getVelocity().set(vel);
202 public static class NESpawnData {
203 public DistressEventType type;
204 public LocationAPI location;
205 public SectorEntityToken jumpPoint;
210 if (!playerFleet.isInHyperspace())
return;
211 if (playerFleet.isInHyperspaceTransition())
return;
213 WeightedRandomPicker<StarSystemAPI> systems =
new WeightedRandomPicker<StarSystemAPI>();
214 OUTER:
for (StarSystemAPI system : Misc.getNearbyStarSystems(playerFleet,
Global.
getSettings().
getFloat(
"distressCallEventRangeLY"))) {
218 if (system.hasPulsar())
continue;
219 if (system.hasTag(Tags.SYSTEM_CUT_OFF_FROM_HYPER))
continue;
220 if (system.hasTag(Tags.THEME_HIDDEN))
continue;
223 float sincePlayerVisit = system.getDaysSinceLastPlayerVisit();
228 boolean validTheme =
false;
229 for (String tag : system.getTags()) {
235 if (!validTheme)
continue;
237 for (CampaignFleetAPI fleet : system.getFleets()) {
238 if (!fleet.getFaction().isHostileTo(Factions.INDEPENDENT))
continue OUTER;
241 if (!Misc.getMarketsInLocation(system).isEmpty())
continue;
249 if ((
float) Math.random() >= p && !
TEST_MODE)
return;
252 StarSystemAPI system = systems.pick();
253 if (system ==
null)
return;
258 WeightedRandomPicker<DistressEventType> picker =
new WeightedRandomPicker<DistressEventType>();
259 picker.add(DistressEventType.NORMAL, 10f);
260 picker.add(DistressEventType.PIRATE_AMBUSH, 10f);
261 picker.add(DistressEventType.PIRATE_AMBUSH_TRAP, 10f);
262 picker.add(DistressEventType.DERELICT_SHIP, 10f);
264 DistressEventType type = picker.pick();
265 if (
TEST_MODE) type = DistressEventType.PIRATE_AMBUSH;
267 if (type == DistressEventType.NORMAL) {
269 }
else if (type == DistressEventType.PIRATE_AMBUSH) {
271 }
else if (type == DistressEventType.PIRATE_AMBUSH_TRAP ||
TEST_MODE) {
273 }
else if (type == DistressEventType.DERELICT_SHIP) {
283 DistressCallIntel intel =
new DistressCallIntel(system);
288 SectorEntityToken jumpPoint = Misc.getDistressJumpPoint(system);
289 if (jumpPoint ==
null)
return;
292 WeightedRandomPicker<String> factions = SalvageSpecialAssigner.getNearbyFactions(
null, system.getLocation(),
295 if (params ==
null)
return;
297 params.durationDays = 60f;
298 CustomCampaignEntityAPI derelict = (CustomCampaignEntityAPI) BaseThemeGenerator.addSalvageEntity(
299 system, Entities.WRECK, Factions.NEUTRAL, params);
300 derelict.addTag(Tags.EXPIRES);
302 float radius = 400f + 400f * (float) Math.random();
303 float maxRadius = Math.max(300, jumpPoint.getCircularOrbitRadius() * 0.33f);
304 if (radius > maxRadius) radius = maxRadius;
306 float orbitDays = radius / (5f + Misc.random.nextFloat() * 20f);
307 float angle = (float) Math.random() * 360f;
308 derelict.setCircularOrbit(jumpPoint, angle, radius, orbitDays);
310 SalvageSpecialAssigner.assignSpecialForDistressDerelict(derelict);
314 SectorEntityToken jumpPoint = Misc.getDistressJumpPoint(system);
315 if (jumpPoint ==
null)
return;
318 WeightedRandomPicker<String> factions = SalvageSpecialAssigner.getNearbyFactions(
null, system.getLocation(),
321 if (params ==
null)
return;
323 params.durationDays = 60f;
324 CustomCampaignEntityAPI derelict = (CustomCampaignEntityAPI) BaseThemeGenerator.addSalvageEntity(
325 system, Entities.WRECK, Factions.NEUTRAL, params);
326 derelict.addTag(Tags.EXPIRES);
328 float radius = 400f + 400f * (float) Math.random();
329 float maxRadius = Math.max(300, jumpPoint.getCircularOrbitRadius() * 0.33f);
330 if (radius > maxRadius) radius = maxRadius;
332 float orbitDays = radius / (5f + Misc.random.nextFloat() * 20f);
333 float angle = (float) Math.random() * 360f;
334 derelict.setCircularOrbit(jumpPoint, angle, radius, orbitDays);
337 TransmitterTrapSpecialData data =
new TransmitterTrapSpecialData();
339 data.maxRange = 20000f;
340 data.nearbyFleetFaction = Factions.PIRATES;
341 data.useAllFleetsInRange =
true;
342 Misc.setSalvageSpecial(derelict, data);
344 int numPirates =
new Random().nextInt(3) + 1;
345 for (
int i = 0; i < numPirates; i++) {
347 NESpawnData dcd =
new NESpawnData();
348 dcd.type = DistressEventType.PIRATE_AMBUSH_TRAP;
349 dcd.location = system;
350 dcd.jumpPoint = jumpPoint;
352 OptionalFleetData extra =
new OptionalFleetData();
353 extra.factionId = Factions.PIRATES;
355 RouteData route = RouteManager.getInstance().addRoute(
"dcd_" +
getId(),
null,
356 Misc.genRandomSeed(), extra,
this, dcd);
357 float waitDays = 30f + (float) Math.random() * 10f;
358 route.addSegment(
new RouteSegment(waitDays, jumpPoint));
376 SectorEntityToken jumpPoint = Misc.getDistressJumpPoint(system);
377 if (jumpPoint ==
null)
return;
379 int numPirates =
new Random().nextInt(3) + 1;
382 for (
int i = 0; i < numPirates; i++) {
383 NESpawnData dcd =
new NESpawnData();
384 dcd.type = DistressEventType.PIRATE_AMBUSH;
385 dcd.location = system;
386 dcd.jumpPoint = jumpPoint;
388 OptionalFleetData extra =
new OptionalFleetData();
389 extra.factionId = Factions.PIRATES;
391 RouteData route = RouteManager.getInstance().addRoute(
"dcd_" +
getId(),
null,
392 Misc.genRandomSeed(), extra,
this, dcd);
393 float waitDays = 30f + (float) Math.random() * 10f;
394 route.addSegment(
new RouteSegment(waitDays, jumpPoint));
411 SectorEntityToken jumpPoint = Misc.getDistressJumpPoint(system);
412 if (jumpPoint ==
null)
return;
414 NESpawnData dcd =
new NESpawnData();
415 dcd.type = DistressEventType.NORMAL;
416 dcd.location = system;
417 dcd.jumpPoint = jumpPoint;
419 OptionalFleetData extra =
new OptionalFleetData();
420 extra.factionId = Factions.INDEPENDENT;
422 RouteData route = RouteManager.getInstance().addRoute(
"dcd_" +
getId(),
null,
423 Misc.genRandomSeed(), extra,
this, dcd);
424 float waitDays = 30f + (float) Math.random() * 10f;
425 route.addSegment(
new RouteSegment(waitDays, jumpPoint));
480 NESpawnData data = (NESpawnData) route.getCustom();
482 if (data.type == DistressEventType.PIRATE_AMBUSH_TRAP) {
483 float tf = PirateBaseManager.getInstance().getStandardTimeFactor();
484 int points = (int) (10 +
new Random().nextInt(20) * tf);
486 CampaignFleetAPI fleet = PirateFleetManager.createPirateFleet(points,
null, data.location.getLocation());
488 fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_LOW_REP_IMPACT,
true);
489 data.location.addEntity(fleet);
490 Vector2f loc = Misc.getPointAtRadius(data.jumpPoint.getLocation(), 500f + (float) Math.random() * 200f);
491 fleet.setLocation(loc.x, loc.y);
493 Misc.makeHostile(fleet);
496 }
else if (data.type == DistressEventType.PIRATE_AMBUSH) {
497 float tf = PirateBaseManager.getInstance().getStandardTimeFactor();
498 int points = (int) (10 +
new Random().nextInt(20) * tf);
500 CampaignFleetAPI fleet = PirateFleetManager.createPirateFleet(points,
null, data.location.getLocation());
502 fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_LOW_REP_IMPACT,
true);
503 data.location.addEntity(fleet);
504 Vector2f loc = Misc.getPointAtRadius(data.jumpPoint.getLocation(), 500f + (float) Math.random() * 200f);
505 fleet.setLocation(loc.x, loc.y);
507 Misc.makeHostile(fleet);
510 }
else if (data.type == DistressEventType.NORMAL) {
512 WeightedRandomPicker<String> typePicker =
new WeightedRandomPicker<String>();
513 typePicker.add(FleetTypes.SCAVENGER_SMALL, 10f);
514 typePicker.add(FleetTypes.SCAVENGER_MEDIUM, 10f);
515 typePicker.add(FleetTypes.SCAVENGER_LARGE, 10f);
516 String type = typePicker.pick();
517 type = FleetTypes.SCAVENGER_SMALL;
518 boolean pirate = (float) Math.random() < 0.5f;
520 CampaignFleetAPI fleet = RuinsFleetRouteManager.createScavenger(
521 type, data.location.getLocation(),
523 if (fleet ==
null)
return null;
524 if (Misc.getSourceMarket(fleet) ==
null)
return null;
526 data.location.addEntity(fleet);
528 fleet.removeAbility(Abilities.EMERGENCY_BURN);
531 fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_NO_JUMP,
true);
533 Misc.setFlagWithReason(fleet.getMemoryWithoutUpdate(), MemFlags.ENTITY_MISSION_IMPORTANT,
534 "distress",
true, 1000f);
535 fleet.getMemoryWithoutUpdate().set(
"$ne_eventRef",
this);
536 fleet.getMemoryWithoutUpdate().set(
"$distress",
true);
539 fleet.getMemoryWithoutUpdate().set(
"$distressTurnHostile",
true);
543 Vector2f loc = Misc.getPointAtRadius(data.jumpPoint.getLocation(), 400f + (float) Math.random() * 200f);
544 fleet.setLocation(loc.x, loc.y);
556 public boolean callEvent(String ruleId, InteractionDialogAPI dialog, List<Token> params, Map<String, MemoryAPI> memoryMap) {
557 String action = params.get(0).getString(memoryMap);
560 CargoAPI cargo = playerFleet.getCargo();
562 FactionAPI playerFaction = playerFleet.getFaction();
563 Color color = playerFaction.getColor();
564 Color bad = Misc.getNegativeHighlightColor();
565 Color highlight = Misc.getHighlightColor();
567 TextPanelAPI text = dialog.getTextPanel();
569 MemoryAPI
memory = BaseCommandPlugin.getEntityMemory(memoryMap);
571 boolean tookCrew =
memory.getBoolean(
"$playerTookDistressCrewRecently");
572 if (action.equals(
"initDistress")) {
574 CampaignFleetAPI fleet = (CampaignFleetAPI) dialog.getInteractionTarget();
575 MarketAPI source = Misc.getSourceMarket(fleet);
577 float returnDistLY = 0;
578 if (source !=
null) {
579 returnDistLY = Misc.getDistanceLY(fleet.getLocationInHyperspace(), source.getLocationInHyperspace());
581 returnDistLY = Misc.getDistanceLY(fleet.getLocationInHyperspace(),
new Vector2f());
584 int fuel = (int) (returnDistLY * Math.max(1, fleet.getLogistics().getFuelCostPerLightYear()));
586 if (fuel < 10) fuel = 10;
587 fuel = (int) (Math.ceil(fuel / 10f) * 10);
592 int crew = (int) (fleet.getFleetData().getMinCrew() * 0.33f);
593 int takeOnCrew = Math.min(crew, cargo.getFreeCrewSpace());
595 memory.set(
"$distressFuel", fuel, 0f);
597 memory.set(
"$distressCredits", Misc.getWithDGS(credits), 0f);
598 memory.set(
"$distressCrewTakeOn", takeOnCrew, 0f);
599 memory.set(
"$distressCrew", crew, 0f);
601 if (
memory.getBoolean(
"$distressTurnHostile")) {
602 memory.set(
"$distressFuelHostileThreshold", fuel, 0f);
605 }
else if (action.equals(
"takeDistressCrew")) {
606 int crew = (int)
memory.getFloat(
"$distressCrewTakeOn");
607 int needed = (int)
memory.getFloat(
"$distressCrew");
609 boolean enough = crew >= needed;
612 AddRemoveCommodity.addCommodityGainText(Commodities.CREW, crew, text);
614 float repChange = (int) (crew / 20);
615 if (repChange < 1) repChange = 1;
616 if (repChange > 5) repChange = 5;
617 adjustRep(repChange,
null, dialog.getInteractionTarget().getActivePerson().getFaction(),
618 dialog.getInteractionTarget().getActivePerson(), text);
624 CampaignFleetAPI fleet = (CampaignFleetAPI) dialog.getInteractionTarget();
628 }
else if (action.equals(
"sellDistressFuel")) {
629 int fuel = (int)
memory.getFloat(
"$distressFuel");
630 int credits = (int)
memory.getFloat(
"$distressCredits");
632 cargo.removeFuel(fuel);
633 cargo.getCredits().add(credits);
635 AddRemoveCommodity.addCommodityLossText(Commodities.FUEL, fuel, text);
636 AddRemoveCommodity.addCreditsGainText(credits, text);
641 int crew = (int)
memory.getFloat(
"$distressCrewTakeOn");
642 float repChange = (int) (crew / 20);
643 if (repChange < 1) repChange = 1;
644 if (repChange > 5) repChange = 5;
645 adjustRep(-repChange, RepLevel.INHOSPITABLE, dialog.getInteractionTarget().getActivePerson().getFaction(),
646 dialog.getInteractionTarget().getActivePerson(), text);
649 }
else if (action.equals(
"scaredDistressFuel")) {
650 int fuel = (int)
memory.getFloat(
"$distressFuel");
653 cargo.removeFuel(fuel);
656 AddRemoveCommodity.addCommodityLossText(Commodities.FUEL, fuel, text);
661 }
else if (action.equals(
"giveDistressFuel")) {
662 int fuel = (int)
memory.getFloat(
"$distressFuel");
663 int credits = (int)
memory.getFloat(
"$distressCredits");
665 cargo.removeFuel(fuel);
668 AddRemoveCommodity.addCommodityLossText(Commodities.FUEL, fuel, text);
672 float repChange = (int) (credits / 1000);
673 if (repChange > 10) repChange = 10;
674 adjustRep(repChange,
null, dialog.getInteractionTarget().getActivePerson().getFaction(),
675 dialog.getInteractionTarget().getActivePerson(), text);
699 protected void adjustRep(
float repChangePercent, RepLevel limit, FactionAPI
faction, PersonAPI person, TextPanelAPI text) {
700 if (repChangePercent != 0) {
701 CustomRepImpact impact =
new CustomRepImpact();
702 impact.delta = repChangePercent * 0.01f;
703 impact.limit = limit;
705 new RepActionEnvelope(RepActions.CUSTOM, impact,
709 if (person !=
null) {
712 new RepActionEnvelope(RepActions.CUSTOM, impact,
713 null, text,
true), person);
720 Map<String, String> map = super.getTokenReplacements();
731 return super.getHighlightColors(stageId);
737 return super.getEventTarget();
746 return CampaignEventCategory.DO_NOT_SHOW_IN_MESSAGE_FILTER;
753 public static void main(String[] args)
throws ParseException {
754 Locale.setDefault(Locale.GERMAN);
760 DecimalFormat format =
new DecimalFormat(
"###,###,###,###,###");
761 System.out.println(format.parse(
"25,000").floatValue());
static SettingsAPI getSettings()
static SectorAPI getSector()
static float getDefaultSModProb()
static DerelictShipData createRandom(String factionId, DerelictType type, Random random)
static float getBaseDuration(HullSize size)
CampaignEventTarget eventTarget
static void undistress(SectorEntityToken fleet)
static void scuttleShips(CampaignFleetAPI fleet, int crewFreed)
IntervalUtil distressCallInterval
static float DISTRESS_MAX_PROB
void maybeSpawnDerelictShip()
TimeoutTracker< String > skipForDistressCalls
void maybeSpawnDistressCall()
boolean shouldRepeat(RouteData route)
void generateDistressCallAmbush(StarSystemAPI system)
static void main(String[] args)
void reportAboutToBeDespawnedByRouteManager(RouteData route)
static float DISTRESS_REPEAT_TIMEOUT
CampaignEventCategory getEventCategory()
void adjustRep(float repChangePercent, RepLevel limit, FactionAPI faction, PersonAPI person, TextPanelAPI text)
Color[] getHighlightColors(String stageId)
boolean shouldCancelRouteAfterDelayCheck(RouteData route)
static float DERELICT_SKIP_PROB
boolean showAllMessagesIfOngoing()
static Set< String > distressCallAllowedThemes
void init(String type, CampaignEventTarget eventTarget)
void advance(float amount)
static float DISTRESS_PROB_PER_SYSTEM
IntervalUtil derelictShipInterval
CampaignFleetAPI spawnFleet(RouteData route)
void generateDistressCallAmbushTrap(StarSystemAPI system)
static float DISTRESS_MIN_CHECK_INTERVAL
Map< String, String > getTokenReplacements()
static float DISTRESS_ALREADY_WAS_NEARBY_TIMEOUT
CampaignEventTarget getEventTarget()
static float DISTRESS_MIN_SINCE_PLAYER_IN_SYSTEM
String[] getHighlights(String stageId)
void generateDistressDerelictShip(StarSystemAPI system)
static float DERELICT_SKIP_PROB_ABYSS
static float DISTRESS_MAX_CHECK_INTERVAL
boolean callEvent(String ruleId, InteractionDialogAPI dialog, List< Token > params, Map< String, MemoryAPI > memoryMap)
void generateDistressCallNormal(StarSystemAPI system)
ShipVariantAPI getVariant(String variantId)
float getFloat(String key)