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