Starsector API
Loading...
Searching...
No Matches
PhaseCloakStats.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.combat;
2
3import java.awt.Color;
4
5import com.fs.starfarer.api.Global;
6import com.fs.starfarer.api.combat.MutableShipStatsAPI;
7import com.fs.starfarer.api.combat.PhaseCloakSystemAPI;
8import com.fs.starfarer.api.combat.ShipAPI;
9import com.fs.starfarer.api.combat.ShipSystemAPI;
10import com.fs.starfarer.api.impl.campaign.ids.Stats;
11import com.fs.starfarer.api.impl.campaign.ids.Tags;
12
14
15 public static Color JITTER_COLOR = new Color(255,175,255,255);
16 public static float JITTER_FADE_TIME = 0.5f;
17
18 public static float SHIP_ALPHA_MULT = 0.25f;
19 //public static float VULNERABLE_FRACTION = 0.875f;
20 public static float VULNERABLE_FRACTION = 0f;
21 public static float INCOMING_DAMAGE_MULT = 0.25f;
22
23
24 public static float MAX_TIME_MULT = 3f;
25
26// /**
27// * Top speed multiplier when at 100% disruption.
28// */
29// public static float DISRUPT_SPEED_MULT = 0.33f;
30// /**
31// * Disruption clears up at this rate. Always clears up fully when cloak is turned off.
32// */
33// public static float DISRUPT_DECAY_RATE = 0.25f;
34// /**
35// * Seconds that need to elapse since a disruption increase before it starts to decay.
36// */
37// public static float DISRUPT_DECAY_DELAY = 2f;
38//
39// // Compared to ship's max flux to figure out how quickly things disrupt.
40// public static float PROJECTILE_DAMAGE_MULT = 3f;
41// public static float BEAM_DAMAGE_MULT = 0.1f;
42// public static float MASS_DAMAGE_MULT = 1f;
43
44 public static boolean FLUX_LEVEL_AFFECTS_SPEED = true;
45 public static float MIN_SPEED_MULT = 0.33f;
46 public static float BASE_FLUX_LEVEL_FOR_MIN_SPEED = 0.5f;
47
48 protected Object STATUSKEY1 = new Object();
49 protected Object STATUSKEY2 = new Object();
50 protected Object STATUSKEY3 = new Object();
51 protected Object STATUSKEY4 = new Object();
52
53
54 public static float getMaxTimeMult(MutableShipStatsAPI stats) {
55 return 1f + (MAX_TIME_MULT - 1f) * stats.getDynamic().getValue(Stats.PHASE_TIME_BONUS_MULT);
56 }
57
58 protected boolean isDisruptable(ShipSystemAPI cloak) {
59 return cloak.getSpecAPI().hasTag(Tags.DISRUPTABLE);
60 }
61
62 protected float getDisruptionLevel(ShipAPI ship) {
63 //return disruptionLevel;
64 //if (true) return 0f;
66 float threshold = ship.getMutableStats().getDynamic().getMod(
67 Stats.PHASE_CLOAK_FLUX_LEVEL_FOR_MIN_SPEED_MOD).computeEffective(BASE_FLUX_LEVEL_FOR_MIN_SPEED);
68 if (threshold <= 0) return 1f;
69 float level = ship.getHardFluxLevel() / threshold;
70 if (level > 1f) level = 1f;
71 return level;
72 }
73 return 0f;
74 }
75
76 protected void maintainStatus(ShipAPI playerShip, State state, float effectLevel) {
77 float level = effectLevel;
78 float f = VULNERABLE_FRACTION;
79
80 ShipSystemAPI cloak = playerShip.getPhaseCloak();
81 if (cloak == null) cloak = playerShip.getSystem();
82 if (cloak == null) return;
83
84 if (level > f) {
85// Global.getCombatEngine().maintainStatusForPlayerShip(STATUSKEY1,
86// cloak.getSpecAPI().getIconSpriteName(), cloak.getDisplayName(), "can not be hit", false);
87 Global.getCombatEngine().maintainStatusForPlayerShip(STATUSKEY2,
88 cloak.getSpecAPI().getIconSpriteName(), cloak.getDisplayName(), "time flow altered", false);
89 } else {
90// float INCOMING_DAMAGE_MULT = 0.25f;
91// float percent = (1f - INCOMING_DAMAGE_MULT) * getEffectLevel() * 100;
92// Global.getCombatEngine().maintainStatusForPlayerShip(STATUSKEY3,
93// spec.getIconSpriteName(), cloak.getDisplayName(), "damage mitigated by " + (int) percent + "%", false);
94 }
95
97 if (level > f) {
98 if (getDisruptionLevel(playerShip) <= 0f) {
99 Global.getCombatEngine().maintainStatusForPlayerShip(STATUSKEY3,
100 cloak.getSpecAPI().getIconSpriteName(), "phase coils stable", "top speed at 100%", false);
101 } else {
102 //String disruptPercent = "" + (int)Math.round((1f - disruptionLevel) * 100f) + "%";
103 //String speedMultStr = Strings.X + Misc.getRoundedValue(getSpeedMult());
104 String speedPercentStr = (int) Math.round(getSpeedMult(playerShip, effectLevel) * 100f) + "%";
105 Global.getCombatEngine().maintainStatusForPlayerShip(STATUSKEY3,
106 cloak.getSpecAPI().getIconSpriteName(),
107 //"phase coils at " + disruptPercent,
108 "phase coil stress",
109 "top speed at " + speedPercentStr, true);
110 }
111 }
112 }
113 }
114
115// protected float disruptionLevel = 0f;
116// //protected Set<CombatEntityAPI> hitBy = new LinkedHashSet<CombatEntityAPI>();
117// protected TimeoutTracker<Object> hitBy = new TimeoutTracker<Object>();
118// protected float sinceHit = 1000f;
119
120 public float getSpeedMult(ShipAPI ship, float effectLevel) {
121 if (getDisruptionLevel(ship) <= 0f) return 1f;
122 return MIN_SPEED_MULT + (1f - MIN_SPEED_MULT) * (1f - getDisruptionLevel(ship) * effectLevel);
123 }
124
125 /*
126 public void advanceDisrupt(float amount, ShipAPI ship, ShipSystemAPI cloak,
127 MutableShipStatsAPI stats, String id, State state, float effectLevel) {
128 if (Global.getCombatEngine().isPaused()) {
129 return;
130 }
131
132 sinceHit += amount;
133 if (state == State.COOLDOWN || state == State.IDLE || state == State.OUT) {
134 disruptionLevel = 0f;
135 hitBy.clear();
136 } else {
137 checkForHits(ship, cloak);
138 }
139 hitBy.advance(amount);
140
141 if (sinceHit > DISRUPT_DECAY_DELAY) {
142 disruptionLevel -= DISRUPT_DECAY_RATE * amount;
143 if (disruptionLevel < 0) disruptionLevel = 0;
144 if (disruptionLevel > 1) disruptionLevel = 1;
145 }
146
147 ((PhaseCloakSystemAPI)cloak).setMinCoilJitterLevel(disruptionLevel * 1f);
148
149// float mult = getSpeedMult(effectLevel);
150// if (mult < 1f) {
151// stats.getMaxSpeed().modifyMult(id, mult);
152// } else {
153// stats.getMaxSpeed().unmodifyMult(id);
154// }
155 }
156
157 public void checkForHits(ShipAPI ship, ShipSystemAPI cloak) {
158 CombatEngineAPI engine = Global.getCombatEngine();
159
160 Vector2f loc = new Vector2f(ship.getLocation());
161 float radius = ship.getCollisionRadius();
162
163 float fluxCap = ship.getMaxFlux();
164 if (fluxCap < 1000) fluxCap = 1000;
165
166 Color core = cloak.getSpecAPI().getEffectColor1();
167 core = Color.white;
168 Color fringe = cloak.getSpecAPI().getEffectColor2();
169 fringe = Misc.interpolateColor(fringe, Color.white, 0.5f);
170// fringe = Misc.setAlpha(fringe, 255);
171
172 Iterator<Object> iter = engine.getAllObjectGrid().getCheckIterator(loc, radius * 2f, radius * 2f);
173 while (iter.hasNext()) {
174 Object curr = iter.next();
175 if (!(curr instanceof CombatEntityAPI)) continue;
176 if (curr == ship) continue;
177
178 CombatEntityAPI entity = (CombatEntityAPI) curr;
179 if (hitBy.contains(entity)) continue;
180
181 float tr = Misc.getTargetingRadius(entity.getLocation(), ship, false);
182 tr *= 1.2f;
183 float dist = Misc.getDistance(entity.getLocation(), loc);
184
185 boolean hit = false;
186 Vector2f glowLoc = null;
187 float glowDir = 0f;
188 float damage = 0f;
189 float hitMult = 1f;
190 float timeout = 1f;
191 if (entity instanceof CombatAsteroidAPI) {
192 if (dist < tr + entity.getCollisionRadius()) {
193 hit = true;
194 hitMult = 1f - dist / (tr + entity.getCollisionRadius());
195 hitMult = 0.5f + 0.5f * hitMult;
196 damage = entity.getMass() * MASS_DAMAGE_MULT * hitMult;
197 timeout = 0.5f;
198
199 glowLoc = entity.getLocation();
200 float dirToEntity = Misc.getAngleInDegrees(loc, entity.getLocation());
201 glowLoc = Misc.getUnitVectorAtDegreeAngle(dirToEntity);
202 glowLoc.scale(tr);
203 Vector2f.add(glowLoc, loc, glowLoc);
204 glowDir = Misc.getAngleInDegrees(entity.getLocation(), loc);
205 }
206 } else if (entity instanceof ShipAPI) {
207 if (dist < tr + entity.getCollisionRadius()) {
208 hit = true;
209 hitMult = 1f - dist / (tr + entity.getCollisionRadius());
210 hitMult = 0.5f + 0.5f * hitMult;
211 damage = entity.getMass() * MASS_DAMAGE_MULT * hitMult;
212 timeout = 0.5f;
213
214 glowLoc = entity.getLocation();
215 float dirToEntity = Misc.getAngleInDegrees(loc, entity.getLocation());
216 glowLoc = Misc.getUnitVectorAtDegreeAngle(dirToEntity);
217 glowLoc.scale(tr);
218 Vector2f.add(glowLoc, loc, glowLoc);
219 glowDir = Misc.getAngleInDegrees(entity.getLocation(), loc);
220 }
221 } else if (entity instanceof DamagingProjectileAPI) {
222 DamagingProjectileAPI proj = (DamagingProjectileAPI) entity;
223 if (proj.getSource() == ship) continue;
224
225 float check = tr + entity.getCollisionRadius();
226 check = tr;
227 if (dist < check) {
228 hit = true;
229 hitMult = 1f - dist / (check);
230 hitMult = 0.5f + 0.5f * hitMult;
231 damage = proj.getDamageAmount() * PROJECTILE_DAMAGE_MULT * hitMult;
232 //damage *= proj.getDamageType().getShieldMult();
233 damage += proj.getDamage().getFluxComponent() * hitMult;
234 if (entity instanceof MissileAPI) {
235 timeout = 0.5f;
236 } else {
237 timeout = 10f;
238 }
239 glowLoc = entity.getLocation();
240 glowDir = Misc.getAngleInDegrees(entity.getVelocity());
241 }
242 }
243
244 if (hit && damage > 0) {
245 float disruptAmount = damage / fluxCap;
246 disruptionLevel += disruptAmount;
247 if (disruptionLevel > 1f) disruptionLevel = 1f;
248 hitBy.add(entity, timeout);
249 sinceHit = 0f;
250 if (glowLoc != null) {
251 //float size = 2000f * hitMult * Math.min(damage/2000f, 1f);
252 //float size = 1000f * hitMult * Math.max(0.05f, disruptAmount + 0.1f);
253 float size = 1000f * hitMult * (disruptAmount + 0.05f);
254 if (size < 20) size = 20;
255 if (size > 150) size = 150;
256
257 //size *= 0.5f;
258 Vector2f glow = new Vector2f(glowLoc);
259// Vector2f per = Misc.getUnitVectorAtDegreeAngle(glowDir);
260// per.scale(size * 0.1f);
261// engine.addHitParticle(glow, ship.getVelocity(), size, hitMult, 1f, fringe);
262// engine.addNegativeParticle(glow, ship.getVelocity(), size * 0.5f, 0f, 1f, core);
263 Vector2f vel = new Vector2f(ship.getVelocity());
264 Vector2f move = Misc.getUnitVectorAtDegreeAngle(glowDir);
265 move.scale(Math.max(300f, entity.getVelocity().length() * (0.5f + (float) Math.random() * 0.5f)));
266 move.scale(-0.1f);
267 Vector2f.add(vel, move, vel);
268
269 engine.addNebulaParticle(
270 glow, vel, size, 1.5f, 0.3f, 0.5f, 1f, Misc.scaleAlpha(fringe, hitMult));
271 engine.addNegativeNebulaParticle(
272 glow, vel, size * 0.67f, 1.5f, 0.3f, 0.5f, 1f, core);
273 if (entity instanceof DamagingProjectileAPI) {
274 engine.removeEntity(entity);
275 }
276 }
277 }
278 }
279
280 for (BeamAPI beam : engine.getBeams()) {
281 if (beam.getDamage().getMultiplier() <= 0) continue;
282 Vector2f p = Misc.closestPointOnSegmentToPoint(beam.getFrom(), beam.getTo(), loc);
283 float tr = Misc.getTargetingRadius(p, ship, false);
284 //tr *= 1.2f;
285 tr += 20f;
286 float dist = Misc.getDistance(p, loc);
287 if (dist < tr) {
288 float hitMult = 1f - dist / tr;
289 hitMult = 0.5f + 0.5f * hitMult;
290 float damage = beam.getDamage().getDamage() * BEAM_DAMAGE_MULT * hitMult;
291 //damage *= beam.getDamage().getType().getShieldMult();
292 damage += beam.getDamage().getFluxComponent() * hitMult;
293 float disruptAmount = damage / fluxCap;
294 disruptionLevel += disruptAmount;
295 if (disruptionLevel > 1f) disruptionLevel = 1f;
296 sinceHit = 0f;
297// float dirToEntity = Misc.getAngleInDegrees(loc, p);
298// Vector2f glowLoc = Misc.getUnitVectorAtDegreeAngle(dirToEntity);
299// glowLoc.scale(tr);
300// Vector2f.add(glowLoc, loc, glowLoc);
301 Vector2f glowLoc = Misc.intersectSegmentAndCircle(beam.getFrom(), beam.getTo(), loc, tr * 1.2f);
302 if (glowLoc != null) {
303 //beam.getTo().set(glowLoc);
304 float size = 1000f * hitMult * (disruptAmount + 0.05f);
305 if (size < 20) size = 20;
306 if (size > 150) size = 150;
307 //size *= 0.5f;
308// engine.addHitParticle(glowLoc, ship.getVelocity(), size, hitMult, 0.3f, fringe);
309// //engine.addHitParticle(glowLoc, ship.getVelocity(), size * 0.25f, hitMult, 0.1f, core);
310// engine.addNegativeParticle(glowLoc, ship.getVelocity(), size * 0.5f, 0f, 0.3f, core);
311 float glowDir = Misc.getAngleInDegrees(beam.getTo(), beam.getFrom());
312 glowDir += (float) Math.random() * 180f - 90f;
313 Vector2f move = Misc.getUnitVectorAtDegreeAngle(glowDir);
314 move.scale(500f * (0.5f + (float) Math.random() * 0.5f));
315 move.scale(0.2f);
316 Vector2f vel = new Vector2f();
317 Vector2f.add(vel, move, vel);
318
319 engine.addNebulaParticle(
320 glowLoc, vel, size, 1.5f, 0.3f, 0.5f, 0.5f, Misc.scaleAlpha(fringe, hitMult));
321 engine.addNegativeNebulaParticle(
322 glowLoc, vel, size * 0.67f, 1.5f, 0.3f, 0.5f, 0.5f, core);
323 }
324 }
325 }
326 }
327 */
328
329
330 public void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel) {
331 ShipAPI ship = null;
332 boolean player = false;
333 if (stats.getEntity() instanceof ShipAPI) {
334 ship = (ShipAPI) stats.getEntity();
335 player = ship == Global.getCombatEngine().getPlayerShip();
336 id = id + "_" + ship.getId();
337 } else {
338 return;
339 }
340
341
342// if (effectLevel > 0) {
343// stats.getMaxSpeed().modifyMult(id, 0.3333333f);
344// } else {
345// stats.getMaxSpeed().unmodifyMult(id);
346// }
347
348 if (player) {
349 maintainStatus(ship, state, effectLevel);
350 }
351
352 if (Global.getCombatEngine().isPaused()) {
353 return;
354 }
355
356 ShipSystemAPI cloak = ship.getPhaseCloak();
357 if (cloak == null) cloak = ship.getSystem();
358 if (cloak == null) return;
359
360// if (isDisruptable(cloak)) {
361// advanceDisrupt(Global.getCombatEngine().getElapsedInLastFrame(),
362// ship, cloak, stats, id, state, effectLevel);
363// }
364
366 if (state == State.ACTIVE || state == State.OUT || state == State.IN) {
367 float mult = getSpeedMult(ship, effectLevel);
368 if (mult < 1f) {
369 stats.getMaxSpeed().modifyMult(id + "_2", mult);
370 } else {
371 stats.getMaxSpeed().unmodifyMult(id + "_2");
372 }
373 ((PhaseCloakSystemAPI)cloak).setMinCoilJitterLevel(getDisruptionLevel(ship));
374 }
375 }
376
377 if (state == State.COOLDOWN || state == State.IDLE) {
378 unapply(stats, id);
379 return;
380 }
381
382 float speedPercentMod = stats.getDynamic().getMod(Stats.PHASE_CLOAK_SPEED_MOD).computeEffective(0f);
383 float accelPercentMod = stats.getDynamic().getMod(Stats.PHASE_CLOAK_ACCEL_MOD).computeEffective(0f);
384 stats.getMaxSpeed().modifyPercent(id, speedPercentMod * effectLevel);
385 stats.getAcceleration().modifyPercent(id, accelPercentMod * effectLevel);
386 stats.getDeceleration().modifyPercent(id, accelPercentMod * effectLevel);
387
388 float speedMultMod = stats.getDynamic().getMod(Stats.PHASE_CLOAK_SPEED_MOD).getMult();
389 float accelMultMod = stats.getDynamic().getMod(Stats.PHASE_CLOAK_ACCEL_MOD).getMult();
390 stats.getMaxSpeed().modifyMult(id, speedMultMod * effectLevel);
391 stats.getAcceleration().modifyMult(id, accelMultMod * effectLevel);
392 stats.getDeceleration().modifyMult(id, accelMultMod * effectLevel);
393
394 float level = effectLevel;
395 //float f = VULNERABLE_FRACTION;
396
397
398
399 float jitterLevel = 0f;
400 float jitterRangeBonus = 0f;
401 float levelForAlpha = level;
402
403// ShipSystemAPI cloak = ship.getPhaseCloak();
404// if (cloak == null) cloak = ship.getSystem();
405
406
407 if (state == State.IN || state == State.ACTIVE) {
408 ship.setPhased(true);
409 levelForAlpha = level;
410 } else if (state == State.OUT) {
411 if (level > 0.5f) {
412 ship.setPhased(true);
413 } else {
414 ship.setPhased(false);
415 }
416 levelForAlpha = level;
417// if (level >= f) {
418// ship.setPhased(true);
419// if (f >= 1) {
420// levelForAlpha = level;
421// } else {
422// levelForAlpha = (level - f) / (1f - f);
423// }
424// float time = cloak.getChargeDownDur();
425// float fadeLevel = JITTER_FADE_TIME / time;
426// if (level >= f + fadeLevel) {
427// jitterLevel = 0f;
428// } else {
429// jitterLevel = (fadeLevel - (level - f)) / fadeLevel;
430// }
431// } else {
432// ship.setPhased(false);
433// levelForAlpha = 0f;
434//
435// float time = cloak.getChargeDownDur();
436// float fadeLevel = JITTER_FADE_TIME / time;
437// if (level < fadeLevel) {
438// jitterLevel = level / fadeLevel;
439// } else {
440// jitterLevel = 1f;
441// }
442// //jitterLevel = level / f;
443// //jitterLevel = (float) Math.sqrt(level / f);
444// }
445 }
446
447// ship.setJitter(JITTER_COLOR, jitterLevel, 1, 0, 0 + jitterRangeBonus);
448// ship.setJitterUnder(JITTER_COLOR, jitterLevel, 11, 0f, 7f + jitterRangeBonus);
449 //ship.getEngineController().fadeToOtherColor(this, spec.getEffectColor1(), new Color(0,0,0,0), jitterLevel, 1f);
450 //ship.getEngineController().extendFlame(this, -0.25f, -0.25f, -0.25f);
451
452 ship.setExtraAlphaMult(1f - (1f - SHIP_ALPHA_MULT) * levelForAlpha);
453 ship.setApplyExtraAlphaToEngines(true);
454
455
456 //float shipTimeMult = 1f + (MAX_TIME_MULT - 1f) * levelForAlpha;
457 float extra = 0f;
458// if (isDisruptable(cloak)) {
459// extra = disruptionLevel;
460// }
461 float shipTimeMult = 1f + (getMaxTimeMult(stats) - 1f) * levelForAlpha * (1f - extra);
462 stats.getTimeMult().modifyMult(id, shipTimeMult);
463 if (player) {
464 Global.getCombatEngine().getTimeMult().modifyMult(id, 1f / shipTimeMult);
465// if (ship.areAnyEnemiesInRange()) {
466// Global.getCombatEngine().getTimeMult().modifyMult(id, 1f / shipTimeMult);
467// } else {
468// Global.getCombatEngine().getTimeMult().modifyMult(id, 2f / shipTimeMult);
469// }
470 } else {
471 Global.getCombatEngine().getTimeMult().unmodify(id);
472 }
473
474// float mitigationLevel = jitterLevel;
475// if (mitigationLevel > 0) {
476// stats.getHullDamageTakenMult().modifyMult(id, 1f - (1f - INCOMING_DAMAGE_MULT) * mitigationLevel);
477// stats.getArmorDamageTakenMult().modifyMult(id, 1f - (1f - INCOMING_DAMAGE_MULT) * mitigationLevel);
478// stats.getEmpDamageTakenMult().modifyMult(id, 1f - (1f - INCOMING_DAMAGE_MULT) * mitigationLevel);
479// } else {
480// stats.getHullDamageTakenMult().unmodify(id);
481// stats.getArmorDamageTakenMult().unmodify(id);
482// stats.getEmpDamageTakenMult().unmodify(id);
483// }
484 }
485
486
487 public void unapply(MutableShipStatsAPI stats, String id) {
488// stats.getHullDamageTakenMult().unmodify(id);
489// stats.getArmorDamageTakenMult().unmodify(id);
490// stats.getEmpDamageTakenMult().unmodify(id);
491
492 ShipAPI ship = null;
493 //boolean player = false;
494 if (stats.getEntity() instanceof ShipAPI) {
495 ship = (ShipAPI) stats.getEntity();
496 //player = ship == Global.getCombatEngine().getPlayerShip();
497 //id = id + "_" + ship.getId();
498 } else {
499 return;
500 }
501
502 Global.getCombatEngine().getTimeMult().unmodify(id);
503 stats.getTimeMult().unmodify(id);
504
505 stats.getMaxSpeed().unmodify(id);
506 stats.getMaxSpeed().unmodifyMult(id + "_2");
507 stats.getAcceleration().unmodify(id);
508 stats.getDeceleration().unmodify(id);
509
510 ship.setPhased(false);
511 ship.setExtraAlphaMult(1f);
512
513 ShipSystemAPI cloak = ship.getPhaseCloak();
514 if (cloak == null) cloak = ship.getSystem();
515 if (cloak != null) {
516 ((PhaseCloakSystemAPI)cloak).setMinCoilJitterLevel(0f);
517 }
518
519// stats.getMaxSpeed().unmodify(id);
520// stats.getMaxTurnRate().unmodify(id);
521// stats.getTurnAcceleration().unmodify(id);
522// stats.getAcceleration().unmodify(id);
523// stats.getDeceleration().unmodify(id);
524 }
525
526 public StatusData getStatusData(int index, State state, float effectLevel) {
527// if (index == 0) {
528// return new StatusData("time flow altered", false);
529// }
530// float percent = (1f - INCOMING_DAMAGE_MULT) * effectLevel * 100;
531// if (index == 1) {
532// return new StatusData("damage mitigated by " + (int) percent + "%", false);
533// }
534 return null;
535 }
536}
static CombatEngineAPI getCombatEngine()
Definition Global.java:63
void unapply(MutableShipStatsAPI stats, String id)
StatusData getStatusData(int index, State state, float effectLevel)
void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel)
void maintainStatus(ShipAPI playerShip, State state, float effectLevel)
float getSpeedMult(ShipAPI ship, float effectLevel)
static float getMaxTimeMult(MutableShipStatsAPI stats)