Starsector API
Loading...
Searching...
No Matches
ShroudedThunderheadHullmod.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.combat.dweller;
2
3import java.awt.Color;
4
5import org.lwjgl.util.vector.Vector2f;
6
7import com.fs.starfarer.api.Global;
8import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType;
9import com.fs.starfarer.api.campaign.CargoStackAPI;
10import com.fs.starfarer.api.campaign.SpecialItemData;
11import com.fs.starfarer.api.combat.CollisionClass;
12import com.fs.starfarer.api.combat.CombatEngineAPI;
13import com.fs.starfarer.api.combat.CombatEntityAPI;
14import com.fs.starfarer.api.combat.DamageAPI;
15import com.fs.starfarer.api.combat.DamageType;
16import com.fs.starfarer.api.combat.DamagingProjectileAPI;
17import com.fs.starfarer.api.combat.EmpArcEntityAPI;
18import com.fs.starfarer.api.combat.EmpArcEntityAPI.EmpArcParams;
19import com.fs.starfarer.api.combat.ShipAPI;
20import com.fs.starfarer.api.combat.ShipAPI.HullSize;
21import com.fs.starfarer.api.combat.listeners.DamageDealtModifier;
22import com.fs.starfarer.api.impl.campaign.ids.Items;
23import com.fs.starfarer.api.impl.combat.NegativeExplosionVisual.NEParams;
24import com.fs.starfarer.api.impl.combat.RiftCascadeMineExplosion;
25import com.fs.starfarer.api.impl.combat.dweller.DwellerShroud.DwellerShroudParams;
26import com.fs.starfarer.api.impl.combat.threat.EnergyLashSystemScript.DelayedCombatActionPlugin;
27import com.fs.starfarer.api.loading.DamagingExplosionSpec;
28import com.fs.starfarer.api.ui.Alignment;
29import com.fs.starfarer.api.ui.TooltipMakerAPI;
30import com.fs.starfarer.api.util.Misc;
31import com.fs.starfarer.api.util.TimeoutTracker;
32import com.fs.starfarer.api.util.WeightedRandomPicker;
33
35
36 public static float MAX_RANGE = 3000f;
37
38 public static float RECENT_HIT_DUR = 5f;
39 public static float MAX_TIME_SINCE_RECENT_HIT = 0.1f;
40 public static float WEIGHT_PER_RECENT_HIT = 1f;
41 public static float MISFIRE_WEIGHT = 10f;
42
43 public static float MIN_REFIRE_DELAY = 0.22f;
44 public static float MAX_REFIRE_DELAY = 0.44f;
45 public static float REFIRE_RATE_MULT = 1f;
46
47 public static float FLUX_PER_DAMAGE = 1f;
48 public static float MIN_DAMAGE = 200f;
49 public static float MAX_DAMAGE = 500f;
50 public static float EMP_MULT = 2f;
51
52 public static class ShroudedThunderheadDamageDealtMod implements DamageDealtModifier {
53 public ShipAPI ship;
54 public ShroudedThunderheadDamageDealtMod(ShipAPI ship) {
55 this.ship = ship;
56 }
57 public String modifyDamageDealt(Object param,
58 CombatEntityAPI target, DamageAPI damage,
59 Vector2f point, boolean shieldHit) {
60 if (param instanceof DamagingProjectileAPI) {
63 if (spec != null && spec.getCollisionClassIfByFighter() == CollisionClass.GAS_CLOUD) {
64 return null;
65 }
66 } else if ((damage.isDps() && !damage.isForceHardFlux()) || damage.getDamage() <= 0f) {
67 return null;
68 }
69
70 if (target != null) {
71 ShroudedThunderheadHullmodData data = getData(ship);
72 RecentHitData hit = new RecentHitData();
73 hit.param = param;
74 hit.target = target;
75 hit.point = new Vector2f(point);
76 hit.damage = damage;
77 hit.shieldHit = shieldHit;
78 //data.recentHits.add(hit, 1.5f);
79 data.recentHits.add(hit, RECENT_HIT_DUR);
80 }
81 return null;
82 }
83 }
84
85 @Override
86 public void applyEffectsAfterShipCreation(ShipAPI ship, String id) {
87 super.applyEffectsAfterShipCreation(ship, id);
88 ship.addListener(new ShroudedThunderheadDamageDealtMod(ship));
89 }
90
91 public String getDescriptionParam(int index, HullSize hullSize) {
92 return null;
93 }
94
95 @Override
97 return Global.getSettings().createCargoStack(CargoItemType.SPECIAL,
99 }
100
101 public static String DATA_KEY = "core_ShroudedThunderheadHullmod_data_key";
102 public static class RecentHitData {
103 Object param;
104 CombatEntityAPI target;
105 Vector2f point;
106 DamageAPI damage;
107 boolean shieldHit;
108 }
109 public static class ShroudedThunderheadHullmodData {
110 float untilArc = 0f;
112
113 boolean hasRecentEnoughHits() {
114 for (RecentHitData curr : recentHits.getItems()) {
115 float remaining = recentHits.getRemaining(curr);
116 if (remaining >= RECENT_HIT_DUR - MAX_TIME_SINCE_RECENT_HIT) return true;
117 }
118 return false;
119 }
120
121 float getHitProbability() {
122 float recent = recentHits.getItems().size() * WEIGHT_PER_RECENT_HIT;
123 return recent / (recent + MISFIRE_WEIGHT);
124 }
125
126 RecentHitData pickRecentHit() {
127 if (!hasRecentEnoughHits()) return null;
128 WeightedRandomPicker<RecentHitData> picker = new WeightedRandomPicker<>();
129 for (RecentHitData curr : recentHits.getItems()) {
130 float remaining = recentHits.getRemaining(curr);
131 if (remaining < RECENT_HIT_DUR - MAX_TIME_SINCE_RECENT_HIT * 2f) continue;
132 picker.add(curr, WEIGHT_PER_RECENT_HIT);
133 }
134 RecentHitData misfire = new RecentHitData();
135 picker.add(misfire, MISFIRE_WEIGHT);
136 return picker.pick();
137 }
138 }
139
140 public static ShroudedThunderheadHullmodData getData(ShipAPI ship) {
142 String key = DATA_KEY + "_" + ship.getId();
143 ShroudedThunderheadHullmodData data = (ShroudedThunderheadHullmodData) engine.getCustomData().get(key);
144 if (data == null) {
145 data = new ShroudedThunderheadHullmodData();
146 engine.getCustomData().put(key, data);
147 }
148 return data;
149 }
150
151 @Override
152 public void advanceInCombat(ShipAPI ship, float amount) {
153 super.advanceInCombat(ship, amount);
154
155 if (!ship.isAlive()) return;
156 if (amount <= 0f) return;
157
158 ShroudedThunderheadHullmodData data = getData(ship);
159
160
161 float prob = data.getHitProbability();
163 shroud.getParams().flashProbability = Math.min(0.1f + prob * 1.4f, 1f);
164 //shroud.getParams().alphaMult = 0.5f;
165
166 data.untilArc -= amount * REFIRE_RATE_MULT;
167 if (data.untilArc <= 0f) {
168 boolean hasRecentHits = data.hasRecentEnoughHits();
169 if (hasRecentHits) {
170 RecentHitData hit = data.pickRecentHit();
171 if (hit != null && hit.target != null) {
172 data.recentHits.remove(hit);
173 spawnLightning(ship, hit);
174 }
175 data.untilArc = MIN_REFIRE_DELAY + (float) Math.random() * (MAX_REFIRE_DELAY - MIN_REFIRE_DELAY);
176 }
177 }
178
179 data.recentHits.advance(amount);
180 }
181
182 public static float getPowerMult(HullSize size) {
183 switch (size) {
184 case CAPITAL_SHIP: return 1f;
185 case CRUISER: return 0.6666666667f;
186 case DESTROYER: return 0.3333333333f;
187 case FIGHTER:
188 case FRIGATE:
189 return 0f;
190 }
191 return 1f;
192 }
193
194 public static float getDamage(HullSize size) {
195 float mult = getPowerMult(size);
196 return MIN_DAMAGE + (MAX_DAMAGE - MIN_DAMAGE) * mult;
197 }
198 public static float getEMPDamage(HullSize size) {
199 return getDamage(size) * EMP_MULT;
200 }
201 public static float getFluxCost(HullSize size) {
202 return getDamage(size) * FLUX_PER_DAMAGE;
203 }
204
205 public void spawnLightning(ShipAPI ship, RecentHitData hit) {
207
208 Vector2f from = ship.getLocation();
209 Vector2f point = hit.point;
210
211 float dist = Misc.getDistance(from, point);
212
213 if (dist > MAX_RANGE) return;
214
215 float mult = getPowerMult(ship.getHullSize());
216 float damage = getDamage(ship.getHullSize());
217 float emp = getEMPDamage(ship.getHullSize());
218
219 if (FLUX_PER_DAMAGE > 0f) {
220 float fluxCost = getFluxCost(ship.getHullSize());
221 if (!deductFlux(ship, fluxCost)) {
222 return;
223 }
224 }
225
227 if (shroud != null) {
228 float angle = Misc.getAngleInDegrees(ship.getLocation(), point);
229 from = Misc.getUnitVectorAtDegreeAngle(angle + 90f - 180f * (float) Math.random());
230 from.scale((0.5f + (float) Math.random() * 0.25f) * shroud.getShroudParams().maxOffset * shroud.getShroudParams().overloadArcOffsetMult);
231 Vector2f.add(ship.getLocation(), from, from);
232 }
233
234
236
237 EmpArcParams params = new EmpArcParams();
238 params.segmentLengthMult = 8f;
239 params.zigZagReductionFactor = 0.15f;
240 //params.fadeOutDist = ship.getCollisionRadius() * 0.5f;
241 params.fadeOutDist = 50f;
242 params.minFadeOutMult = 10f;
243// params.flickerRateMult = 0.7f;
244 params.flickerRateMult = 0.3f;
245// params.flickerRateMult = 0.05f;
246// params.glowSizeMult = 3f;
247// params.brightSpotFullFraction = 0.5f;
248
249 params.movementDurOverride = Math.max(0.05f, dist / arcSpeed);
250
251 float arcWidth = 40f + mult * 40f;
252 float explosionRadius = 40f + mult * 40f;
253
254 //Color color = weapon.getSpec().getGlowColor();
256 EmpArcEntityAPI arc = (EmpArcEntityAPI)engine.spawnEmpArcVisual(from, ship, point, null,
257 arcWidth, // thickness
258 color,
259 new Color(255,255,255,255),
260 params
261 );
262 arc.setCoreWidthOverride(arcWidth / 2f);
263
264 arc.setRenderGlowAtStart(false);
265 arc.setFadedOutAtStart(true);
266 arc.setSingleFlickerMode(true);
267
268 float volume = 0.75f + 0.25f * mult;
269 float pitch = 1f + 0.25f * (1f - mult);
270 Global.getSoundPlayer().playSound("rift_lightning_fire", pitch, volume, from, ship.getVelocity());
271
272 if (shroud != null) {
273 DwellerShroudParams shroudParams = shroud.getShroudParams();
274 params = new EmpArcParams();
275 params.segmentLengthMult = 4f;
276 params.glowSizeMult = 4f;
277 params.flickerRateMult = 0.5f + (float) Math.random() * 0.5f;
278 params.flickerRateMult *= 1.5f;
279
280 //Color fringe = shroudParams.overloadArcFringeColor;
281 Color fringe = color;
282 Color core = Color.white;
283
284 float thickness = shroudParams.overloadArcThickness;
285
286 //Vector2f to = Misc.getPointAtRadius(from, 1f);
287
288 float angle = Misc.getAngleInDegrees(from, ship.getLocation());
289 angle = angle + 90f * ((float) Math.random() - 0.5f);
290 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(angle);
291 dist = shroudParams.maxOffset * shroud.getShroudParams().overloadArcOffsetMult;
292 dist = dist * 0.5f + dist * 0.5f * (float) Math.random();
293 //dist *= 1.5f;
294 dist *= 0.5f;
295 dir.scale(dist);
296 Vector2f to = Vector2f.add(from, dir, new Vector2f());
297
299 from, ship, to, ship, thickness, fringe, core, params);
300
301 arc.setCoreWidthOverride(shroudParams.overloadArcCoreThickness);
302 arc.setSingleFlickerMode(false);
303 //arc.setRenderGlowAtStart(false);
304 }
305
306
307
308 float explosionDelay = params.movementDurOverride * 0.8f;
309 Global.getCombatEngine().addPlugin(new DelayedCombatActionPlugin(explosionDelay, new Runnable() {
310 @Override
311 public void run() {
313 0.1f, // duration
314 explosionRadius, // radius
315 explosionRadius * 0.5f, // coreRadius
316 damage, // maxDamage
317 damage / 2f, // minDamage
318 CollisionClass.PROJECTILE_NO_FF, // collisionClass
319 CollisionClass.GAS_CLOUD, // collisionClassByFighter - using to flag it as from this effect
320 3f, // particleSizeMin
321 3f, // particleSizeRange
322 0.5f, // particleDuration
323 0, // particleCount
324 new Color(255,255,255,0), // particleColor
325 new Color(255,100,100,0) // explosionColor
326 );
327 spec.setMinEMPDamage(emp * 0.5f);
328 spec.setMaxEMPDamage(emp);
329
330 spec.setDamageType(DamageType.ENERGY);
331 spec.setUseDetailedExplosion(false);
332 spec.setSoundSetId("rift_lightning_explosion");
333 spec.setSoundVolume(0.5f + 0.5f * mult);
334
335 DamagingProjectileAPI explosion = engine.spawnDamagingExplosion(spec, ship, point);
336
337 //explosion.addDamagedAlready(target);
338 //color = new Color(255,75,75,255);
339
340 // float baseSize = 10f;
341 //
342 // NEParams p = RiftCascadeMineExplosion.createStandardRiftParams(
343 // color, baseSize);
344 // //p.hitGlowSizeMult = 0.5f;
345 // p.noiseMult = 6f;
346 // p.thickness = 25f;
347 // p.fadeOut = 0.5f;
348 // p.spawnHitGlowAt = 1f;
349 // p.additiveBlend = true;
350 // p.blackColor = Color.white;
351 // p.underglow = null;
352 // p.withNegativeParticles = false;
353 // p.withHitGlow = false;
354 // p.fadeIn = 0f;
355 // //p.numRiftsToSpawn = 1;
356 //
357 // RiftCascadeMineExplosion.spawnStandardRift(explosion, p);
358
360 color = new Color(255,75,75,255);
362 color, 14f + 6f * mult);
363 p.fadeOut = 0.5f + 0.5f * mult;
364 p.hitGlowSizeMult = 0.6f;
365 p.thickness = 50f;
366 //p.thickness = 25f;
367
368
369 // p.hitGlowSizeMult = 0.5f;
370 // p.thickness = 25f;
371 // p.fadeOut = 0.25f;
372
374 }
375 }));
376
377 }
378
379 @Override
380 public boolean shouldAddDescriptionToTooltip(HullSize hullSize, ShipAPI ship, boolean isForModSpec) {
381 return false;
382 }
383
384 @Override
385 public void addPostDescriptionSection(TooltipMakerAPI tooltip, HullSize hullSize, final ShipAPI ship, float width, boolean isForModSpec) {
386 float pad = 3f;
387 float opad = 10f;
388 Color h = Misc.getHighlightColor();
389 Color bad = Misc.getNegativeHighlightColor();
390
391
392 tooltip.addPara("Fires rift lightning bolts at locations recently hit by this ship's weapons.", opad);
393
394 tooltip.addPara("The amount of damage and the flux generated depend on the size of the ship the "
395 + "hullmod is installed on. The probability of a bolt being fired increases with the number "
396 + "of hits landed over the previous %s seconds. "
397 + "Bolts are not triggered by weapons dealing soft flux damage, such as beams.", opad,
398 Misc.getHighlightColor(), "" + (int)Math.round(RECENT_HIT_DUR));
399
400 //tooltip.addSectionHeading("Reload capacity", Alignment.MID, opad);
401
402 if (isForModSpec || (ship == null && !Global.CODEX_TOOLTIP_MODE)) return;
403
404 tooltip.setBgAlpha(0.9f);
405
406 HullSize [] sizes = new HullSize[] {HullSize.FRIGATE, HullSize.DESTROYER, HullSize.CRUISER, HullSize.CAPITAL_SHIP};
407
408 float damW = 87f;
409 float empW = 87f;
410 float fluxW = 87f;
411 float sizeW = width - damW - empW - fluxW - 10f;
413 20f, true, true,
414 new Object [] {"Ship size", sizeW, "Damage", damW, "EMP", empW, "Flux cost", fluxW});
415
416 for (HullSize size : sizes) {
417 float damage = getDamage(size);
418 float emp = getEMPDamage(size);
419 float fluxCost = getFluxCost(size);
420
421 Color c = Misc.getGrayColor();
422 if (size == hullSize || Global.CODEX_TOOLTIP_MODE) {
424 }
425 tooltip.addRow(Alignment.MID, c, Misc.getHullSizeStr(size),
426 Alignment.MID, c, "" + (int) Math.round(damage),
427 Alignment.MID, c, "" + (int) Math.round(emp),
428 Alignment.MID, c, "" + "" + (int) Math.round(fluxCost));
429 }
430 tooltip.addTable("", 0, opad);
431
432 tooltip.addSpacer(5f);
433
434 addCrewCasualties(tooltip, opad);
435
436// if (Global.CODEX_TOOLTIP_MODE) {
437// return;
438// }
439 }
440
441
442// @Override
443// public String getSModDescriptionParam(int index, HullSize hullSize, ShipAPI ship) {
444// if (index == 0) return "" + (int) Math.round(SMOD_CR_PENALTY * 100f) + "%";
445// if (index == 1) return "" + (int) Math.round(SMOD_MAINTENANCE_PENALTY) + "%";
446// return null;
447// }
448//
449// @Override
450// public boolean isSModEffectAPenalty() {
451// return true;
452// }
453}
454
455
456
457
458
459
460
461
462
463
464
465
466
467
static SettingsAPI getSettings()
Definition Global.java:57
static boolean CODEX_TOOLTIP_MODE
Definition Global.java:15
static SoundPlayerAPI getSoundPlayer()
Definition Global.java:49
static CombatEngineAPI getCombatEngine()
Definition Global.java:69
static void spawnStandardRift(DamagingProjectileAPI explosion, NEParams params)
static NEParams createStandardRiftParams(String minelayerId, float baseRadius)
static DwellerShroud getShroudFor(CombatEntityAPI entity)
void addPostDescriptionSection(TooltipMakerAPI tooltip, HullSize hullSize, final ShipAPI ship, float width, boolean isForModSpec)
boolean shouldAddDescriptionToTooltip(HullSize hullSize, ShipAPI ship, boolean isForModSpec)
static Vector2f getUnitVectorAtDegreeAngle(float degrees)
Definition Misc.java:1196
static Color getNegativeHighlightColor()
Definition Misc.java:802
static String getHullSizeStr(HullSize size)
Definition Misc.java:6665
static Color getBasePlayerColor()
Definition Misc.java:833
static Color getGrayColor()
Definition Misc.java:826
static Color getBrightPlayerColor()
Definition Misc.java:830
static float getDistance(SectorEntityToken from, SectorEntityToken to)
Definition Misc.java:599
static Color getHighlightColor()
Definition Misc.java:792
static Color getDarkPlayerColor()
Definition Misc.java:836
static float getAngleInDegrees(Vector2f v)
Definition Misc.java:1126
CargoStackAPI createCargoStack(CargoItemType type, Object data, CargoAPI cargo)
SoundAPI playSound(String id, float pitch, float volume, Vector2f loc, Vector2f vel)
EmpArcEntityAPI spawnEmpArcVisual(Vector2f from, CombatEntityAPI fromAnchor, Vector2f to, CombatEntityAPI toAnchor, float thickness, Color fringe, Color core)
DamagingProjectileAPI spawnDamagingExplosion(DamagingExplosionSpec spec, ShipAPI source, Vector2f location)
void addPlugin(EveryFrameCombatPlugin plugin)
void setFadedOutAtStart(boolean fadedOutAtStart)
void setRenderGlowAtStart(boolean renderGlowAtStart)
void setCoreWidthOverride(float coreWidthOverride)
void addListener(Object listener)
void addTable(String emptyText, int andMore, float pad)
UIPanelAPI beginTable(FactionAPI faction, float itemHeight, Object ... columns)
LabelAPI addPara(String format, float pad, Color hl, String... highlights)
UIComponentAPI addSpacer(float height)