Starsector API
Loading...
Searching...
No Matches
DistressCallAbility.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.campaign.abilities;
2
3import java.util.Random;
4
5import java.awt.Color;
6
7import org.lwjgl.util.vector.Vector2f;
8
9import com.fs.starfarer.api.Global;
10import com.fs.starfarer.api.campaign.CampaignFleetAPI;
11import com.fs.starfarer.api.campaign.JumpPointAPI;
12import com.fs.starfarer.api.campaign.SectorEntityToken;
13import com.fs.starfarer.api.campaign.SectorEntityToken.VisibilityLevel;
14import com.fs.starfarer.api.campaign.StarSystemAPI;
15import com.fs.starfarer.api.characters.FullName;
16import com.fs.starfarer.api.impl.campaign.econ.impl.MilitaryBase;
17import com.fs.starfarer.api.impl.campaign.fleets.FleetFactory.PatrolType;
18import com.fs.starfarer.api.impl.campaign.fleets.PirateFleetManager;
19import com.fs.starfarer.api.impl.campaign.fleets.RouteManager;
20import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.OptionalFleetData;
21import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteData;
22import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteFleetSpawner;
23import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteSegment;
24import com.fs.starfarer.api.impl.campaign.ids.Factions;
25import com.fs.starfarer.api.impl.campaign.ids.FleetTypes;
26import com.fs.starfarer.api.impl.campaign.ids.Pings;
27import com.fs.starfarer.api.impl.campaign.ids.Tags;
28import com.fs.starfarer.api.impl.campaign.procgen.themes.RuinsFleetRouteManager;
29import com.fs.starfarer.api.impl.campaign.procgen.themes.SalvageSpecialAssigner;
30import com.fs.starfarer.api.ui.LabelAPI;
31import com.fs.starfarer.api.ui.TooltipMakerAPI;
32import com.fs.starfarer.api.util.DelayedActionScript;
33import com.fs.starfarer.api.util.Misc;
34import com.fs.starfarer.api.util.TimeoutTracker;
35import com.fs.starfarer.api.util.WeightedRandomPicker;
36
37public class DistressCallAbility extends BaseDurationAbility implements RouteFleetSpawner {
38
39 public static final float NEARBY_USE_TIMEOUT_DAYS = 20f;
40 public static final float NEARBY_USE_RADIUS_LY = 5f;
41
42 public static final float DAYS_TO_TRACK_USAGE = 365f;
43
44 public static enum DistressCallOutcome {
45 NOTHING,
46 HELP,
47 PIRATES,
48 }
49
50 public static class DistressResponseData {
51 public DistressCallOutcome outcome;
52 public JumpPointAPI inner;
53 public JumpPointAPI outer;
54 }
55
56
57 public static class AbilityUseData {
58 public long timestamp;
59 public Vector2f location;
60 public AbilityUseData(long timestamp, Vector2f location) {
61 this.timestamp = timestamp;
62 this.location = location;
63 }
64
65 }
66
67 protected boolean performed = false;
68 protected int numTimesUsed = 0;
69 protected long lastUsed = 0;
70
72
73 protected Object readResolve() {
74 super.readResolve();
75 if (uses == null) {
77 }
78 return this;
79 }
80
81 @Override
82 protected void activateImpl() {
85 if (level != VisibilityLevel.NONE) {
87 }
88
89 performed = false;
90 }
91
92 }
93
94 @Override
95 protected void applyEffect(float amount, float level) {
96 CampaignFleetAPI fleet = getFleet();
97 if (fleet == null) return;
98
99 if (!performed) {
101 performed = true;
102 return;
103 }
104
106 picker.add(DistressCallOutcome.HELP, 10f);
107 if (numTimesUsed > 2) {
109 float pirates = 10f + uses * 2f;
110 picker.add(DistressCallOutcome.PIRATES, pirates);
111
112 float nothing = 10f + uses * 2f;
113 picker.add(DistressCallOutcome.NOTHING, nothing);
114 }
115
116 DistressCallOutcome outcome = picker.pick();
117 //outcome = DistressCallOutcome.HELP;
118
119 if (outcome != DistressCallOutcome.NOTHING) {
120 float delay = 10f + 10f * (float) Math.random();
121 if (numTimesUsed == 0) {
122 delay = 1f + 2f * (float) Math.random();
123 }
124 //delay = 0f;
125 addResponseScript(delay, outcome);
126 }
127
128 numTimesUsed++;
130 performed = true;
131
132 AbilityUseData data = new AbilityUseData(lastUsed, fleet.getLocationInHyperspace());
133 uses.add(data, DAYS_TO_TRACK_USAGE);
134 }
135 }
136
137 public boolean wasUsedNearby(float withinDays, Vector2f locInHyper, float withinRangeLY) {
138 for (AbilityUseData data : uses.getItems()) {
139 float daysSinceUse = Global.getSector().getClock().getElapsedDaysSince(data.timestamp);
140 if (daysSinceUse <= withinDays) {
141 float range = Misc.getDistanceLY(locInHyper, data.location);
142 if (range <= withinRangeLY) return true;
143 }
144 }
145 return false;
146 }
147
148
149 @Override
150 public void advance(float amount) {
151 super.advance(amount);
152
153 float days = Global.getSector().getClock().convertToDays(amount);
154 uses.advance(days);
155 }
156
158 return uses;
159 }
160
162 return uses.getItems().size();
163 }
164
165 protected void addResponseScript(float delayDays, DistressCallOutcome outcome) {
166 final CampaignFleetAPI player = getFleet();
167 if (player == null) return;
168 if (!(player.getContainingLocation() instanceof StarSystemAPI)) return;
169
170 final StarSystemAPI system = (StarSystemAPI) player.getContainingLocation();
171
172 final JumpPointAPI inner = Misc.getDistressJumpPoint(system);
173 if (inner == null) return;
174
175 JumpPointAPI outerTemp = null;
176 if (inner.getDestinations().size() >= 1) {
177 SectorEntityToken test = inner.getDestinations().get(0).getDestination();
178 if (test instanceof JumpPointAPI) {
179 outerTemp = (JumpPointAPI) test;
180 }
181 }
182 final JumpPointAPI outer = outerTemp;
183 if (outer == null) return;
184
185
186 if (outcome == DistressCallOutcome.HELP) {
187 addHelpScript(delayDays, system, inner, outer);
188 } else if (outcome == DistressCallOutcome.PIRATES) {
189 addPiratesScript(delayDays, system, inner, outer);
190 }
191
192 }
193
194 protected void addPiratesScript(float delayDays,
195 final StarSystemAPI system,
196 final JumpPointAPI inner,
197 final JumpPointAPI outer) {
199 @Override
200 public void doAction() {
202 if (player == null) return;
203
204 int numPirates = new Random().nextInt(3) + 1;
205 for (int i = 0; i < numPirates; i++) {
206 DistressResponseData data = new DistressResponseData();
207 data.outcome = DistressCallOutcome.PIRATES;
208 data.inner = inner;
209 data.outer = outer;
210
211 OptionalFleetData extra = new OptionalFleetData();
212 extra.factionId = Factions.PIRATES;
213
214 RouteData route = RouteManager.getInstance().addRoute("dca_" + getId(), null,
215 Misc.genRandomSeed(), extra, DistressCallAbility.this, data);
216 float waitDays = 15f + (float) Math.random() * 10f;
217 route.addSegment(new RouteSegment(waitDays, inner));
218 }
219 }
220 });
221 }
222
223 protected void addHelpScript(float delayDays,
224 final StarSystemAPI system,
225 final JumpPointAPI inner,
226 final JumpPointAPI outer) {
228 @Override
229 public void doAction() {
230 DistressResponseData data = new DistressResponseData();
231 data.outcome = DistressCallOutcome.HELP;
232 data.inner = inner;
233 data.outer = outer;
234
235 RouteData route = RouteManager.getInstance().addRoute("dca_" + getId(), null,
236 Misc.genRandomSeed(), null, DistressCallAbility.this, data);
237 float waitDays = 15f + (float) Math.random() * 10f;
238 route.addSegment(new RouteSegment(waitDays, inner));
239 }
240 });
241 }
242
243
244
245
246 public boolean isUsable() {
247 if (!super.isUsable()) return false;
248 if (getFleet() == null) return false;
249
250 CampaignFleetAPI fleet = getFleet();
251 if (fleet.isInHyperspace() || fleet.isInHyperspaceTransition()) return false;
252
253 if (fleet.getContainingLocation() != null && fleet.getContainingLocation().hasTag(Tags.SYSTEM_ABYSSAL)) {
254 return false;
255 }
256
257 return true;
258 }
259
260
261 @Override
262 protected void deactivateImpl() {
263 cleanupImpl();
264 }
265
266 @Override
267 protected void cleanupImpl() {
268 CampaignFleetAPI fleet = getFleet();
269 if (fleet == null) return;
270 }
271
272
273 @Override
274 public void createTooltip(TooltipMakerAPI tooltip, boolean expanded) {
275
276 CampaignFleetAPI fleet = getFleet();
277 if (fleet == null) return;
278
279 Color gray = Misc.getGrayColor();
280 Color highlight = Misc.getHighlightColor();
281 Color bad = Misc.getNegativeHighlightColor();
282
284 LabelAPI title = tooltip.addTitle(spec.getName());
285 } else {
286 tooltip.addSpacer(-10f);
287 }
288
289 float pad = 10f;
290
291 tooltip.addPara("May be used by a stranded fleet to punch a distress signal through to hyperspace, " +
292 "asking nearby fleets to bring aid in the form of fuel and supplies. " +
293 "Help may take many days to arrive, if it arrives at all, and taking advantage " +
294 "of it will result in a progressively higher reduction in standing with the responders.", pad);
295
296 tooltip.addPara("By long-standing convention, the fleet in distress is expected to meet any responders at the " +
297 "innermost jump-point inside a star system.", pad, highlight,
298 "innermost jump-point");
299
300 tooltip.addPara("The signal is non-directional and carries no data, and is therefore not useful for " +
301 "calling for help in a tactical situation.", pad);
302
304 if (fleet.isInHyperspace()) {
305 tooltip.addPara("Can not be used in hyperspace.", bad, pad);
306 }
307 if (fleet.getContainingLocation() != null && fleet.getContainingLocation().hasTag(Tags.SYSTEM_ABYSSAL)) {
308 tooltip.addPara("Can not be used in star systems deep within abyssal hyperspace.", bad, pad);
309 }
310 }
311
312 addIncompatibleToTooltip(tooltip, expanded);
313
314 }
315
316 public boolean hasTooltip() {
317 return true;
318 }
319
320
321 public CampaignFleetAPI spawnFleet(RouteData route) {
322
323 DistressResponseData data = (DistressResponseData) route.getCustom();
324
326 if (player == null) return null;
327
328 if (data.outcome == DistressCallOutcome.HELP) {
330 null, data.inner,
331 10f, 10f, 0f);
332
333 String faction = factions.pick();
334// faction = Factions.HEGEMONY;
335// faction = Factions.PIRATES;
336 if (faction == null) return null;
337
338 //int fuelNeeded = DistressResponse.getNeededFuel(player);
339
340 CampaignFleetAPI fleet = null;
341 if (Factions.INDEPENDENT.equals(faction)) {
343// typePicker.add(FleetTypes.SCAVENGER_SMALL, 5f); // too little fuel to bother
344
345 //if (fuelNeeded < 750) {
346 typePicker.add(FleetTypes.SCAVENGER_MEDIUM, 10f); // 500+ fuel
347 typePicker.add(FleetTypes.SCAVENGER_LARGE, 5f); // 1000+ fuel
348 String type = typePicker.pick();
349
351 type, data.inner.getLocationInHyperspace(),
352 route, null, false, null);
353 } else {
355// picker.add(PatrolType.FAST, 5f);
356// picker.add(PatrolType.COMBAT, 10f);
357 picker.add(PatrolType.HEAVY, 5f);
358 PatrolType type = picker.pick();
359
360 fleet = MilitaryBase.createPatrol(type, 15f, faction, route, null, data.inner.getLocationInHyperspace(), route.getRandom());
361 //fleet = PatrolFleetManager.createPatrolFleet(type, null, faction, data.inner.getLocationInHyperspace(), 0f);
362 }
363 if (fleet == null) return null;
364
365 if (Misc.getSourceMarket(fleet) == null) return null;
366
367
368 if (numTimesUsed == 1) {
369 FullName name = new FullName("Mel", "Greenish", fleet.getCommander().getGender());
370 fleet.getCommander().setName(name);
371 fleet.getFlagship().setShipName("IS In All Circumstances");
372 }
373
374 Misc.makeImportant(fleet, "distressResponse", 30f);
375 fleet.getMemoryWithoutUpdate().set("$distressResponse", true);
376
378
379 if (!player.isInHyperspace() &&
381 player.getCargo().getFuel() <= 0)) {
382
383 Vector2f loc = data.outer.getLocation();
384 fleet.setLocation(loc.x, loc.y + fleet.getRadius() + 100f);
385 } else {
386 float dir = (float) Math.random() * 360f;
387 if (player.isInHyperspace()) {
388 dir = Misc.getAngleInDegrees(player.getLocation(), data.inner.getLocationInHyperspace());
389 dir += (float) Math.random() * 120f - 60f;
390 }
391 Vector2f loc = Misc.getUnitVectorAtDegreeAngle(dir);
392 loc.scale(3000f + 1000f * (float) Math.random());
393 Vector2f.add(data.inner.getLocationInHyperspace(), loc, loc);
394 fleet.setLocation(loc.x, loc.y + fleet.getRadius() + 100f);
395 }
396
397 fleet.addScript(new DistressCallResponseAssignmentAI(fleet, data.inner.getStarSystem(),
398 data.inner, data.outer));
399
400 return fleet;
401 } else if (data.outcome == DistressCallOutcome.PIRATES) {
402 int points = 5 + new Random().nextInt(15);
403
405 if (fleet == null) return null;
406 if (Misc.getSourceMarket(fleet) == null) return null;
407
409
410 if (!player.isInHyperspace() &&
412 player.getCargo().getFuel() <= 0)) {
413
414 Vector2f loc = data.outer.getLocation();
415 fleet.setLocation(loc.x, loc.y + fleet.getRadius() + 100f);
416 } else {
417 float dir = (float) Math.random() * 360f;
418 if (player.isInHyperspace()) {
419 dir = Misc.getAngleInDegrees(player.getLocation(), data.inner.getLocationInHyperspace());
420 dir += (float) Math.random() * 120f - 60f;
421 }
422 Vector2f loc = Misc.getUnitVectorAtDegreeAngle(dir);
423 loc.scale(3000f + 1000f * (float) Math.random());
424 Vector2f.add(data.inner.getLocationInHyperspace(), loc, loc);
425 fleet.setLocation(loc.x, loc.y + fleet.getRadius() + 100f);
426 }
427
428 fleet.addScript(new DistressCallResponsePirateAssignmentAI(fleet, data.inner.getStarSystem(), data.inner, data.outer));
429
430 return fleet;
431 }
432
433
434 return null;
435 }
436
437
438 public void reportAboutToBeDespawnedByRouteManager(RouteData route) {
439 // don't respawn since the assignment AI is not set up to handle it well, it'll
440 // just basically start over
441 route.expire();
442 }
443
444 public boolean shouldCancelRouteAfterDelayCheck(RouteData route) {
445 return false;
446 }
447
448 public boolean shouldRepeat(RouteData route) {
449 return false;
450 }
451
452}
453
454
455
456
457
static boolean CODEX_TOOLTIP_MODE
Definition Global.java:15
static SectorAPI getSector()
Definition Global.java:65
void addIncompatibleToTooltip(TooltipMakerAPI tooltip, boolean expanded)
void addHelpScript(float delayDays, final StarSystemAPI system, final JumpPointAPI inner, final JumpPointAPI outer)
void addResponseScript(float delayDays, DistressCallOutcome outcome)
void createTooltip(TooltipMakerAPI tooltip, boolean expanded)
void addPiratesScript(float delayDays, final StarSystemAPI system, final JumpPointAPI inner, final JumpPointAPI outer)
boolean wasUsedNearby(float withinDays, Vector2f locInHyper, float withinRangeLY)
static CampaignFleetAPI createPatrol(PatrolType type, String factionId, RouteData route, MarketAPI market, Vector2f locInHyper, Random random)
static CampaignFleetAPI createPirateFleet(int combatPoints, RouteData route, Vector2f locInHyper)
RouteData addRoute(String source, MarketAPI market, Long seed, OptionalFleetData extra, RouteFleetSpawner spawner)
static CampaignFleetAPI createScavenger(String type, Vector2f locInHyper, MarketAPI source, boolean pirate, Random random)
static WeightedRandomPicker< String > getNearbyFactions(Random random, SectorEntityToken entity)
static MarketAPI getSourceMarket(CampaignFleetAPI fleet)
Definition Misc.java:2162
static float getDistanceLY(SectorEntityToken from, SectorEntityToken to)
Definition Misc.java:602
static Vector2f getUnitVectorAtDegreeAngle(float degrees)
Definition Misc.java:1196
static Color getNegativeHighlightColor()
Definition Misc.java:802
static void makeImportant(SectorEntityToken entity, String reason)
Definition Misc.java:4051
static JumpPointAPI getDistressJumpPoint(StarSystemAPI system)
Definition Misc.java:3803
static Color getGrayColor()
Definition Misc.java:826
static Color getHighlightColor()
Definition Misc.java:792
static float getAngleInDegrees(Vector2f v)
Definition Misc.java:1126
List< JumpDestination > getDestinations()
void addEntity(SectorEntityToken entity)
void addScript(EveryFrameScript script)
EveryFrameScript addPing(SectorEntityToken entity, String pingType)
void addScript(EveryFrameScript script)
void set(String key, Object value)
LabelAPI addPara(String format, float pad, Color hl, String... highlights)
UIComponentAPI addSpacer(float height)