Starsector API
Loading...
Searching...
No Matches
InterdictionPulseAbility.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.campaign.abilities;
2
3import java.awt.Color;
4
5import com.fs.starfarer.api.EveryFrameScript;
6import com.fs.starfarer.api.Global;
7import com.fs.starfarer.api.campaign.BattleAPI;
8import com.fs.starfarer.api.campaign.CampaignFleetAPI;
9import com.fs.starfarer.api.campaign.SectorEntityToken.VisibilityLevel;
10import com.fs.starfarer.api.campaign.ai.FleetAIFlags;
11import com.fs.starfarer.api.campaign.ai.ModularFleetAIAPI;
12import com.fs.starfarer.api.campaign.econ.MarketAPI;
13import com.fs.starfarer.api.campaign.rules.MemoryAPI;
14import com.fs.starfarer.api.characters.AbilityPlugin;
15import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActionEnvelope;
16import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActions;
17import com.fs.starfarer.api.impl.campaign.ids.Abilities;
18import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
19import com.fs.starfarer.api.impl.campaign.ids.Pings;
20import com.fs.starfarer.api.loading.CampaignPingSpec;
21import com.fs.starfarer.api.ui.LabelAPI;
22import com.fs.starfarer.api.ui.TooltipMakerAPI;
23import com.fs.starfarer.api.util.Misc;
24
26
27 public static class IPReactionScript implements EveryFrameScript {
28 float delay;
29 boolean done;
30 CampaignFleetAPI other;
31 CampaignFleetAPI fleet;
32 float activationDays;
39 public IPReactionScript(CampaignFleetAPI fleet, CampaignFleetAPI other, float activationDays) {
40 this.fleet = fleet;
41 this.other = other;
42 this.activationDays = activationDays;
43 delay = 0.3f + 0.3f * (float) Math.random();
44 //delay = 0f;
45 }
46 public void advance(float amount) {
47 if (done) return;
48
49 delay -= amount;
50 if (delay > 0) return;
51
52 VisibilityLevel level = fleet.getVisibilityLevelTo(other);
53 if (level == VisibilityLevel.NONE || level == VisibilityLevel.SENSOR_CONTACT) {
54 done = true;
55 return;
56 }
57
58 if (!(other.getAI() instanceof ModularFleetAIAPI)) {
59 done = true;
60 return;
61 }
62 ModularFleetAIAPI ai = (ModularFleetAIAPI) other.getAI();
63
64
65 float dist = Misc.getDistance(fleet.getLocation(), other.getLocation());
66 float speed = Math.max(1f, other.getTravelSpeed());
67 float eta = dist / speed;
68
69 float rushTime = activationDays * Global.getSector().getClock().getSecondsPerDay();
70 rushTime += 0.5f + 0.5f * (float) Math.random();
71
72 MemoryAPI mem = other.getMemoryWithoutUpdate();
73 CampaignFleetAPI pursueTarget = mem.getFleet(FleetAIFlags.PURSUIT_TARGET);
74
75 if (eta < rushTime && pursueTarget == fleet) {
76 done = true;
77 return;
78 }
79
80 float range = InterdictionPulseAbility.getRange(fleet);
81 float getAwayTime = 1f + (range - dist) / speed;
82 AbilityPlugin sb = other.getAbility(Abilities.SENSOR_BURST);
83 if (getAwayTime > rushTime && sb != null && sb.isUsable() && (float) Math.random() > 0.67f) {
84 sb.activate();
85 done = true;
86 return;
87 }
88
89 //float avoidRange = Math.min(dist, getRange(other));
90 float avoidRange = getRange(other) + 100f;
91 ai.getNavModule().avoidLocation(fleet.getContainingLocation(),
92 fleet.getLocation(), avoidRange, avoidRange + 50f, activationDays + 0.01f);
93
94 ai.getNavModule().avoidLocation(fleet.getContainingLocation(),
95 //fleet.getLocation(), dist, dist + 50f, activationDays + 0.01f);
96 Misc.getPointAtRadius(fleet.getLocation(), avoidRange * 0.5f), avoidRange, avoidRange * 1.5f + 50f, activationDays + 0.05f);
97
98 done = true;
99 }
100
101 public boolean isDone() {
102 return done;
103 }
104 public boolean runWhilePaused() {
105 return false;
106 }
107 }
108
109 public static final float MAX_EFFECT = 1f;
110 //public static final float RANGE = 1000f;
111 public static final float BASE_RANGE = 500f;
112 public static final float BASE_SECONDS = 6f;
113 public static final float STRENGTH_PER_SECOND = 200f;
114
115 //public static final float CR_COST_MULT = 0.5f;
116 public static final float DETECTABILITY_PERCENT = 100f;
117
118// public String getSpriteName() {
119// return Global.getSettings().getSpriteName("abilities", Abilities.EMERGENCY_BURN);
120// }
121
122
123 public static float getRange(CampaignFleetAPI fleet) {
124 return BASE_RANGE + fleet.getSensorRangeMod().computeEffective(fleet.getSensorStrength()) / 2f;
125 }
126
127 @Override
128 protected String getActivationText() {
129 //return Misc.ucFirst(spec.getName().toLowerCase());
130 return "Interdiction pulse";
131 }
132
133
134 protected Boolean primed = null;
135 protected Float elapsed = null;
136 protected Integer numFired = null;
137
138 @Override
139 protected void activateImpl() {
140 CampaignFleetAPI fleet = getFleet();
141 if (fleet == null) return;
142
143 Global.getSector().addPing(fleet, Pings.INTERDICT);
144
145 float range = getRange(fleet);
146 for (CampaignFleetAPI other : fleet.getContainingLocation().getFleets()) {
147 if (other == fleet) continue;
148
149 float dist = Misc.getDistance(fleet.getLocation(), other.getLocation());
150 if (dist > range + 500f) continue;
151
152 other.addScript(new IPReactionScript(fleet, other, getActivationDays()));
153 }
154
155 primed = true;
156
157 }
158
159 protected void showRangePing(float amount) {
160 CampaignFleetAPI fleet = getFleet();
161 if (fleet == null) return;
162
163 VisibilityLevel vis = fleet.getVisibilityLevelToPlayerFleet();
164 if (vis == VisibilityLevel.NONE || vis == VisibilityLevel.SENSOR_CONTACT) return;
165
166
167 boolean fire = false;
168 if (elapsed == null) {
169 elapsed = 0f;
170 numFired = 0;
171 fire = true;
172 }
173 elapsed += amount;
174 if (elapsed > 0.5f && numFired < 4) {
175 elapsed -= 0.5f;
176 fire = true;
177 }
178
179 if (fire) {
180 numFired++;
181
182 float range = getRange(fleet);
183 CampaignPingSpec custom = new CampaignPingSpec();
184 custom.setUseFactionColor(true);
185 custom.setWidth(7);
186 custom.setMinRange(range - 100f);
187 custom.setRange(200);
188 custom.setDuration(2f);
189 custom.setAlphaMult(0.25f);
190 custom.setInFraction(0.2f);
191 custom.setNum(1);
192
193 Global.getSector().addPing(fleet, custom);
194 }
195
196 }
197
198 @Override
199 protected void applyEffect(float amount, float level) {
200 CampaignFleetAPI fleet = getFleet();
201 if (fleet == null) return;
202
203 fleet.getStats().getDetectedRangeMod().modifyPercent(getModId(), DETECTABILITY_PERCENT * level, "Interdiction pulse");
204
205 //System.out.println("Level: " + level);
206
207 if (level > 0 && level < 1 && amount > 0) {
208 showRangePing(amount);
209// float activateSeconds = getActivationDays() * Global.getSector().getClock().getSecondsPerDay();
210// float speed = fleet.getVelocity().length();
211// float acc = Math.max(speed, 200f)/activateSeconds + fleet.getAcceleration();
212// float ds = acc * amount;
213// if (ds > speed) ds = speed;
214// Vector2f dv = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(fleet.getVelocity()));
215// dv.scale(ds);
216// fleet.setVelocity(fleet.getVelocity().x - dv.x, fleet.getVelocity().y - dv.y);
217 fleet.goSlowOneFrame();
218 return;
219 }
220
221 float range = getRange(fleet);
222
223 boolean playedHit = !(entity.isInCurrentLocation() && entity.isVisibleToPlayerFleet());
224 if (level == 1 && primed != null) {
225
226 if (entity.isInCurrentLocation()) {
227 Global.getSector().getMemoryWithoutUpdate().set(MemFlags.GLOBAL_INTERDICTION_PULSE_JUST_USED_IN_CURRENT_LOCATION, true, 0.1f);
228 }
229 fleet.getMemoryWithoutUpdate().set(MemFlags.JUST_DID_INTERDICTION_PULSE, true, 0.1f);
230
231 CampaignPingSpec custom = new CampaignPingSpec();
232 custom.setUseFactionColor(true);
233 custom.setWidth(15);
234 custom.setRange(range * 1.3f);
235 custom.setDuration(0.5f);
236 custom.setAlphaMult(1f);
237 custom.setInFraction(0.1f);
238 custom.setNum(1);
239 Global.getSector().addPing(fleet, custom);
240
241
242 for (CampaignFleetAPI other : fleet.getContainingLocation().getFleets()) {
243 if (other == fleet) continue;
244 if (other.getFaction() == fleet.getFaction()) continue;
245 if (other.isInHyperspaceTransition()) continue;
246
247 float dist = Misc.getDistance(fleet.getLocation(), other.getLocation());
248 if (dist > range) continue;
249
250
251 float interdictSeconds = getInterdictSeconds(fleet, other);
252 if (interdictSeconds > 0 && interdictSeconds < 1f) interdictSeconds = 1f;
253
254 VisibilityLevel vis = other.getVisibilityLevelToPlayerFleet();
255 if (vis == VisibilityLevel.COMPOSITION_AND_FACTION_DETAILS ||
256 vis == VisibilityLevel.COMPOSITION_DETAILS ||
257 (vis == VisibilityLevel.SENSOR_CONTACT && fleet.isPlayerFleet())) {
258 if (interdictSeconds <= 0) {
259 other.addFloatingText("Interdict avoided!" , fleet.getFaction().getBaseUIColor(), 1f, true);
260 continue;
261 } else {
262 other.addFloatingText("Interdict! (" + (int) Math.round(interdictSeconds) + "s)" , fleet.getFaction().getBaseUIColor(), 1f, true);
263 }
264 }
265
266 float interdictDays = interdictSeconds / Global.getSector().getClock().getSecondsPerDay();
267
268 for (AbilityPlugin ability : other.getAbilities().values()) {
269 if (!ability.getSpec().hasTag(Abilities.TAG_BURN + "+") &&
270 !ability.getSpec().hasTag(Abilities.TAG_DISABLED_BY_INTERDICT) &&
271 !ability.getId().equals(Abilities.INTERDICTION_PULSE)) continue;
272
273 float origCooldown = ability.getCooldownLeft();
274 float extra = 0;
275 if (ability.isActiveOrInProgress()) {
276 extra += ability.getSpec().getDeactivationCooldown() * ability.getProgressFraction();
277 ability.deactivate();
278
279 }
280
281 if (!ability.getSpec().hasTag(Abilities.TAG_BURN + "+")) continue;
282
283 float cooldown = interdictDays;
284 //cooldown = Math.max(cooldown, origCooldown);
285 cooldown += origCooldown;
286 cooldown += extra;
287 float max = Math.max(ability.getSpec().getDeactivationCooldown(), 2f);
288 if (cooldown > max) cooldown = max;
289 ability.setCooldownLeft(cooldown);
290 }
291
292 if (fleet.isPlayerFleet() && other.knowsWhoPlayerIs() && fleet.getFaction() != other.getFaction()) {
293 Global.getSector().adjustPlayerReputation(
294 new RepActionEnvelope(RepActions.INTERDICTED, null, null, false),
295 other.getFaction().getId());
296 }
297
298 if (!playedHit) {
299 Global.getSoundPlayer().playSound("world_interdict_hit", 1f, 1f, other.getLocation(), other.getVelocity());
300 //playedHit = true;
301 }
302 }
303
304 primed = null;
305 elapsed = null;
306 numFired = null;
307 }
308
309 }
310
311 public static float getInterdictSeconds(CampaignFleetAPI fleet, CampaignFleetAPI other) {
312 float offense = fleet.getSensorRangeMod().computeEffective(fleet.getSensorStrength());
313 float defense = other.getSensorRangeMod().computeEffective(other.getSensorStrength());
314 float diff = offense - defense;
315
316 float extra = diff / STRENGTH_PER_SECOND;
317
318 float total = BASE_SECONDS + extra;
319 if (total < 0f) total = 0f;
320 return total;// / Global.getSector().getClock().getSecondsPerDay();
321 }
322
323
324// public static float getEffectMagnitude(CampaignFleetAPI fleet, CampaignFleetAPI other) {
325// float burn = Misc.getBurnLevelForSpeed(other.getVelocity().length());
326//
327// Vector2f velDir = Misc.normalise(new Vector2f(other.getVelocity()));
328// Vector2f toFleet = Misc.normalise(Vector2f.sub(fleet.getLocation(), other.getLocation(), new Vector2f()));
329// float dot = Vector2f.dot(velDir, toFleet);
330// if (dot <= 0.05f || burn <= 1f) return 0f;
331//
332// float effect = dot;
333// if (effect < 0) effect = 0;
334// if (effect > 1) effect = 1;
335//
336// //effect *= Math.min(1f, burn / 10f);
337//
338// //return effect;
339// return Math.max(0.1f, effect);
340// }
341
342 @Override
343 protected void deactivateImpl() {
344 cleanupImpl();
345 }
346
347 @Override
348 protected void cleanupImpl() {
349 CampaignFleetAPI fleet = getFleet();
350 if (fleet == null) return;
351
352 fleet.getStats().getDetectedRangeMod().unmodify(getModId());
353 //fleet.getStats().getSensorRangeMod().unmodify(getModId());
354 //fleet.getStats().getFleetwideMaxBurnMod().unmodify(getModId());
355 //fleet.getStats().getAccelerationMult().unmodify(getModId());
356 //fleet.getCommanderStats().getDynamic().getStat(Stats.NAVIGATION_PENALTY_MULT).unmodify(getModId());
357
358 primed = null;
359 }
360
361
362 @Override
363 public boolean isUsable() {
364 return super.isUsable() &&
365 getFleet() != null;// &&
366 //getNonReadyShips().isEmpty();
367 }
368
369// protected List<FleetMemberAPI> getNonReadyShips() {
370// List<FleetMemberAPI> result = new ArrayList<FleetMemberAPI>();
371// CampaignFleetAPI fleet = getFleet();
372// if (fleet == null) return result;
373//
374// float crCostFleetMult = fleet.getStats().getDynamic().getValue(Stats.EMERGENCY_BURN_CR_MULT);
375// for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
376// //if (member.isMothballed()) continue;
377// float crLoss = member.getDeployCost() * CR_COST_MULT * crCostFleetMult;
378// if (Math.round(member.getRepairTracker().getCR() * 100) < Math.round(crLoss * 100)) {
379// result.add(member);
380// }
381// }
382// return result;
383// }
384
385// protected float computeSupplyCost() {
386// CampaignFleetAPI fleet = getFleet();
387// if (fleet == null) return 0f;
388//
389// float crCostFleetMult = fleet.getStats().getDynamic().getValue(Stats.EMERGENCY_BURN_CR_MULT);
390//
391// float cost = 0f;
392// for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
393// cost += member.getDeploymentPointsCost() * CR_COST_MULT * crCostFleetMult;
394// }
395// return cost;
396// }
397
398
399 @Override
400 public void createTooltip(TooltipMakerAPI tooltip, boolean expanded) {
401 CampaignFleetAPI fleet = getFleet();
402 if (fleet == null) return;
403
404 Color gray = Misc.getGrayColor();
405 Color highlight = Misc.getHighlightColor();
406 Color fuel = Global.getSettings().getColor("progressBarFuelColor");
407 Color bad = Misc.getNegativeHighlightColor();
408
409 LabelAPI title = tooltip.addTitle("Interdiction Pulse");
410
411 float pad = 10f;
412
413 int range = (int) getRange(fleet);
414
415
416 tooltip.addPara("Slows* the fleet and uses its active sensor network to charge and release a powerful energy pulse that " +
417 "can disrupt the drive fields of nearby fleets.", pad);
418
419 Color c = Misc.getTooltipTitleAndLightHighlightColor();
420 tooltip.addPara("The disruption interrupts any movement-related abilities (such as %s) " +
421 "and prevents their use for some time afterwards. Also interrupts charging interdiction pulses.", pad,
422 highlight, "Sustained Burn");
423
424 tooltip.addPara("The disruption lasts for %s seconds, modified by %s second for " +
425 "every %s points of difference in the fleets' sensor strengths.", pad, highlight,
426 "" + (int) BASE_SECONDS,
427 "" + (int) 1,
428 "" + (int) STRENGTH_PER_SECOND);
429
430 tooltip.addPara("Base range of %s* units, increased by half your fleet's sensor strength, " +
431 "for a total of %s units. While the pulse is charging, the range at which the fleet can be detected will " +
432 "gradually increase by up to %s.", pad, highlight,
433 "" + (int) BASE_RANGE,
434 "" + range,
435 "" + (int) DETECTABILITY_PERCENT + "%");
436
437 tooltip.addPara("A successful interdict is considered a hostile act, though not on the same level as " +
438 "open warfare.", pad);
439
440 tooltip.addPara("*2000 units = 1 map grid cell", gray, pad);
441 tooltip.addPara("*A fleet is considered slow-moving at a burn level of half that of its slowest ship.", gray, pad);
442 addIncompatibleToTooltip(tooltip, expanded);
443 }
444
445 public boolean hasTooltip() {
446 return true;
447 }
448
449
450 @Override
451 public void fleetLeftBattle(BattleAPI battle, boolean engagedInHostilities) {
452 if (engagedInHostilities) {
453 deactivate();
454 }
455 }
456
457 @Override
458 public void fleetOpenedMarket(MarketAPI market) {
459 deactivate();
460 }
461
462}
463
464
465
466
467
static SettingsAPI getSettings()
Definition Global.java:51
static SoundPlayerAPI getSoundPlayer()
Definition Global.java:43
static SectorAPI getSector()
Definition Global.java:59
void addIncompatibleToTooltip(TooltipMakerAPI tooltip, boolean expanded)
static float getInterdictSeconds(CampaignFleetAPI fleet, CampaignFleetAPI other)
void fleetLeftBattle(BattleAPI battle, boolean engagedInHostilities)
SoundAPI playSound(String id, float pitch, float volume, Vector2f loc, Vector2f vel)