Starsector API
Loading...
Searching...
No Matches
ExplosionEntityPlugin.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.campaign;
2
3import java.awt.Color;
4import java.util.ArrayList;
5import java.util.LinkedHashSet;
6import java.util.List;
7
8import org.lwjgl.util.vector.Vector2f;
9
10import com.fs.starfarer.api.Global;
11import com.fs.starfarer.api.campaign.CampaignEngineLayers;
12import com.fs.starfarer.api.campaign.CampaignFleetAPI;
13import com.fs.starfarer.api.campaign.LocationAPI;
14import com.fs.starfarer.api.campaign.SectorEntityToken;
15import com.fs.starfarer.api.combat.ViewportAPI;
16import com.fs.starfarer.api.fleet.FleetMemberAPI;
17import com.fs.starfarer.api.graphics.SpriteAPI;
18import com.fs.starfarer.api.impl.campaign.ids.Stats;
19import com.fs.starfarer.api.impl.campaign.terrain.HyperspaceTerrainPlugin;
20import com.fs.starfarer.api.impl.campaign.terrain.ShoveFleetScript;
21import com.fs.starfarer.api.util.Misc;
22import com.fs.starfarer.api.util.WeightedRandomPicker;
23
25
26 public static enum ExplosionFleetDamage {
27 NONE,
28 LOW,
29 MEDIUM,
30 HIGH,
31 EXTREME,
32 }
33 public static class ExplosionParams {
34 public Color color;
35 public ExplosionFleetDamage damage = ExplosionFleetDamage.NONE;
36 public float radius;
37 public float durationMult = 1f;
38 public Vector2f loc;
39 public LocationAPI where;
40 public ExplosionParams(Color color, LocationAPI where, Vector2f loc, float radius, float durationMult) {
41 this.color = color;
42 this.where = where;
43 this.loc = loc;
44 this.radius = radius;
45 this.durationMult = durationMult;
46 }
47 }
48
49
50 public static class ParticleData {
51 public Vector2f offset = new Vector2f();
52 public Vector2f vel = new Vector2f();
53 public float scale = 1f;
54 public float scaleDelta = 1f;
55 public float turnDir = 1f;
56 public float angle = 1f;
57 public float size;
58
59 public float maxDur;
60 public float elapsed;
61 public float swImpact = 1f;
62
63 public int i;
64 public int j;
65
66 public Color color;
67
68 public ParticleData(Color color, float size, float maxDur, float endScale) {
69 i = Misc.random.nextInt(4);
70 j = Misc.random.nextInt(4);
71
72 this.color = color;
73 this.size = size;
74 angle = (float) Math.random() * 360f;
75
76 this.maxDur = maxDur;
77 scaleDelta = (endScale - 1f) / maxDur;
78 scale = 1f;
79
80 turnDir = Math.signum((float) Math.random() - 0.5f) * 10f * (float) Math.random();
81
82 //turnDir = 0f;
83 }
84
85 public void setVelocity(float direction, float minSpeed, float maxSpeed) {
86 vel = Misc.getUnitVectorAtDegreeAngle(direction);
87 vel.scale(minSpeed + (maxSpeed - minSpeed) * (float) Math.random());
88 }
89
90 public void setOffset(float direction, float minDist, float maxDist) {
91 offset = Misc.getUnitVectorAtDegreeAngle(direction);
92 offset.scale(minDist + (maxDist - minDist) * (float) Math.random());
93 }
94
95 public void advance(float amount) {
96 scale += scaleDelta * amount;
97 if (scale < 0) scale = 0f;
98
99 offset.x += vel.x * amount;
100 offset.y += vel.y * amount;
101
102 angle += turnDir * amount;
103
104 elapsed += amount;
105 }
106
107 public float getBrightness() {
108 float b = 1f - (elapsed / maxDur);
109 if (b < 0) b = 0;
110 if (b > 1) b = 1;
111 return b;
112 }
113 }
114
115
116 protected ExplosionParams params;
117 protected List<ParticleData> particles = new ArrayList<ParticleData>();
118
119 transient protected SpriteAPI sprite;
120
121 protected float shockwaveRadius;
122 protected float shockwaveWidth;
123 protected float shockwaveSpeed;
124 protected float shockwaveDuration;
125 protected float shockwaveAccel;
126
127 protected float maxParticleSize;
128
129
130 public void init(SectorEntityToken entity, Object pluginParams) {
131 super.init(entity, pluginParams);
132 readResolve();
133
134 params = (ExplosionParams) pluginParams;
135
136
137 if (params.where.isCurrentLocation()) {
138 Global.getSoundPlayer().playSound("gate_explosion", 1f, 1f, params.loc, Misc.ZERO);
139 }
140
141 float baseSize = params.radius * 0.08f;
142 maxParticleSize = baseSize * 2f;
143
144 float fullArea = (float) (Math.PI * params.radius * params.radius);
145 float particleArea = (float) (Math.PI * baseSize * baseSize);
146
147 int count = (int) Math.round(fullArea / particleArea * 1f);
148
149 float durMult = 2f;
150 durMult = params.durationMult;
151
152 //baseSize *= 0.5f;
153 for (int i = 0; i < count; i++) {
154 float size = baseSize * (1f + (float) Math.random());
155
156 Color randomColor = new Color(Misc.random.nextInt(256),
157 Misc.random.nextInt(256), Misc.random.nextInt(256), params.color.getAlpha());
158 Color adjustedColor = Misc.interpolateColor(params.color, randomColor, 0.2f);
159 adjustedColor = params.color;
160 ParticleData data = new ParticleData(adjustedColor, size,
161 (0.25f + (float) Math.random()) * 2f * durMult, 3f);
162
163 float r = (float) Math.random();
164 float dist = params.radius * 0.2f * (0.1f + r * 0.9f);
165 float dir = (float) Math.random() * 360f;
166 data.setOffset(dir, dist, dist);
167
168 dir = Misc.getAngleInDegrees(data.offset);
169// data.setVelocity(dir, baseSize * 0.25f, baseSize * 0.5f);
170// data.vel.scale(1f / durMult);
171
172 data.swImpact = (float) Math.random();
173 if (i > count / 2) data.swImpact = 1;
174
175 particles.add(data);
176 }
177
178 Vector2f loc = new Vector2f(params.loc);
179 loc.x -= params.radius * 0.01f;
180 loc.y += params.radius * 0.01f;
181
182 float b = 1f;
183 params.where.addHitParticle(loc, new Vector2f(), params.radius * 1f, b, 1f * durMult, params.color);
184 loc = new Vector2f(params.loc);
185 params.where.addHitParticle(loc, new Vector2f(), params.radius * 0.4f, 0.5f, 1f * durMult, Color.white);
186
187 shockwaveAccel = baseSize * 70f / durMult;
188 //shockwaveRadius = -1500f;
189 shockwaveRadius = 0f;
190 shockwaveRadius = -params.radius * 0.5f;
191 shockwaveSpeed = params.radius * 2f / durMult;
192 shockwaveDuration = params.radius * 2f / shockwaveSpeed;
193 shockwaveWidth = params.radius * 0.5f;
194
195// shockwaveAccel = baseSize * 1500f / durMult;
196// //shockwaveRadius = -1500f;
197// shockwaveRadius = 0f;
198// shockwaveSpeed = params.radius * 4f / durMult;
199// shockwaveDuration = params.radius * 2f / shockwaveSpeed;
200// shockwaveWidth = params.radius * 0.5f;
201
202// shockwaveAccel = baseSize * 10f / durMult;
203// //shockwaveRadius = -1500f;
204// shockwaveRadius = 0f;
205// shockwaveSpeed = params.radius * 0.2f / durMult;
206// shockwaveDuration = params.radius * 2f / shockwaveSpeed;
207// shockwaveWidth = params.radius * 0.4f;
208 }
209
210 Object readResolve() {
211 sprite = Global.getSettings().getSprite("misc", "nebula_particles");
212 return this;
213 }
214
215
216 public void advance(float amount) {
217 for (ParticleData p : new ArrayList<ParticleData>(particles)) {
218 p.advance(amount);
219 if (p.elapsed >= p.maxDur) {
220 particles.remove(p);
221 }
222 }
223 if (particles.isEmpty()) {
224 entity.setExpired(true);
225 }
226
228
229 if (shockwaveDuration > 0) {
231 //shockwaveSpeed -= amount * shockwaveSpeed * 5f;
232 if (shockwaveSpeed < 0) shockwaveSpeed = 0;
233 shockwaveDuration -= amount;
234 for (ParticleData p : particles) {
235 float dist = p.offset.length();
236
237 float impact = 0f;
238 if (dist < shockwaveRadius && dist > shockwaveRadius - shockwaveWidth) {
239 impact = 1f - (shockwaveRadius - dist) / shockwaveWidth;
240 impact = -impact;
241 } else if (dist > shockwaveRadius && dist < shockwaveRadius + shockwaveWidth) {
242 impact = 1f - (dist - shockwaveRadius) / shockwaveWidth;
243 }
244
245 float speed = p.vel.length();
246 float dot = Vector2f.dot(p.offset, p.vel);
247 float threshold = shockwaveSpeed * 0.5f;
248 if (speed > threshold) {// && dot > 0) {
249 impact *= threshold / speed;
250 }
251 if (dot < 0) {
252 impact *= 0.2f;
253 }
254 //impact *= 0.1f + 0.9f * (1f - p.size / maxParticleSize);
255 impact *= p.swImpact;
256
257 Vector2f accel = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(p.offset));
258 accel.scale(impact * shockwaveAccel);
259 p.vel.x += accel.x * amount;
260 p.vel.y += accel.y * amount;
261 }
262 }
263
264 }
265
266
267 public float getRenderRange() {
268 float extra = 2000f;
269 if (params != null) extra = params.radius * 3;
270 return entity.getRadius() + extra;
271 }
272
273 public void render(CampaignEngineLayers layer, ViewportAPI viewport) {
274 float alphaMult = viewport.getAlphaMult();
275 alphaMult *= entity.getSensorFaderBrightness();
276 alphaMult *= entity.getSensorContactFaderBrightness();
277 if (alphaMult <= 0) return;
278
279 float x = entity.getLocation().x;
280 float y = entity.getLocation().y;
281
282 //Color color = params.color;
283 //color = Misc.setAlpha(color, 30);
284 float b = alphaMult;
285
286 sprite.setTexWidth(0.25f);
287 sprite.setTexHeight(0.25f);
288 sprite.setAdditiveBlend();
289
290 for (ParticleData p : particles) {
291 float size = p.size;
292 size *= p.scale;
293
294 Vector2f loc = new Vector2f(x + p.offset.x, y + p.offset.y);
295
296 float a = 1f;
297 a = 0.33f;
298
299 sprite.setTexX(p.i * 0.25f);
300 sprite.setTexY(p.j * 0.25f);
301
302 sprite.setAngle(p.angle);
303 sprite.setSize(size, size);
304 sprite.setAlphaMult(b * a * p.getBrightness());
305 sprite.setColor(p.color);
306 sprite.renderAtCenter(loc.x, loc.y);
307 }
308 }
309
310 protected LinkedHashSet<String> damagedAlready = new LinkedHashSet<String>();
311 public void applyDamageToFleets() {
312 if (params.damage == null || params.damage == ExplosionFleetDamage.NONE) {
313 return;
314 }
315
316 float shockwaveDist = 0f;
317 for (ParticleData p : particles) {
318 shockwaveDist = Math.max(shockwaveDist, p.offset.length());
319 }
320
321 for (CampaignFleetAPI fleet : entity.getContainingLocation().getFleets()) {
322 String id = fleet.getId();
323 if (damagedAlready.contains(id)) continue;
324 float dist = Misc.getDistance(fleet, entity);
325 if (dist < shockwaveDist) {
326 float damageMult = 1f - (dist / params.radius);
327 if (damageMult > 1f) damageMult = 1f;
328 if (damageMult < 0.1f) damageMult = 0.1f;
329 if (dist < entity.getRadius() + params.radius * 0.1f) damageMult = 1f;
330
331 damagedAlready.add(id);
332 applyDamageToFleet(fleet, damageMult);
333 }
334 }
335
336 }
337
338 public void applyDamageToFleet(CampaignFleetAPI fleet, float damageMult) {
339
340 List<FleetMemberAPI> members = fleet.getFleetData().getMembersListCopy();
341 if (members.isEmpty()) return;
342
343 float totalValue = 0;
344 for (FleetMemberAPI member : members) {
345 totalValue += member.getStats().getSuppliesToRecover().getModifiedValue();
346 }
347 if (totalValue <= 0) return;
348
349
350 float damageFraction = 0f;
351 switch (params.damage) {
352 case NONE:
353 return;
354 case LOW:
355 damageFraction = 0.1f;
356 break;
357 case MEDIUM:
358 damageFraction = 0.3f;
359 break;
360 case HIGH:
361 damageFraction = 0.6f;
362 break;
363 case EXTREME:
364 damageFraction = 0.9f;
365 break;
366 }
367
368 damageFraction *= damageMult;
369
370 float shoveDir = Misc.getAngleInDegrees(entity.getLocation(), fleet.getLocation());
371 fleet.addScript(new ShoveFleetScript(fleet, shoveDir, damageFraction));
372
373 if (fleet.isInCurrentLocation()) {
374 float dist = Misc.getDistance(fleet, Global.getSector().getPlayerFleet());
375 if (dist < HyperspaceTerrainPlugin.STORM_STRIKE_SOUND_RANGE) {
376 float volumeMult = 0.5f + 0.5f * damageFraction;
377 Global.getSoundPlayer().playSound("gate_explosion_fleet_impact", 1f, volumeMult, fleet.getLocation(), Misc.ZERO);
378 }
379 }
380
381 //float strikeValue = totalValue * damageFraction * (0.5f + (float) Math.random() * 0.5f);
382
383 WeightedRandomPicker<FleetMemberAPI> picker = new WeightedRandomPicker<FleetMemberAPI>();
384 for (FleetMemberAPI member : members) {
385 float w = 1f;
386 if (member.isFrigate()) w *= 0.1f;
387 if (member.isDestroyer()) w *= 0.2f;
388 if (member.isCruiser()) w *= 0.5f;
389 picker.add(member, w);
390 }
391
392 int numStrikes = picker.getItems().size();
393
394 for (int i = 0; i < numStrikes; i++) {
395 FleetMemberAPI member = picker.pick();
396 if (member == null) return;
397
398 float crPerDep = member.getDeployCost();
399 //if (crPerDep <= 0) continue;
400 float suppliesPerDep = member.getStats().getSuppliesToRecover().getModifiedValue();
401 if (suppliesPerDep <= 0 || crPerDep <= 0) return;
402 float suppliesPer100CR = suppliesPerDep * 1f / Math.max(0.01f, crPerDep);
403
404 // half flat damage, half scaled based on ship supply cost cost
405 float strikeSupplies = (250f + suppliesPer100CR) * 0.5f * damageFraction;
406 //strikeSupplies = suppliesPerDep * 0.5f * damageFraction;
407
408 float strikeDamage = strikeSupplies / suppliesPer100CR * (0.75f + (float) Math.random() * 0.5f);
409
410 //float strikeDamage = damageFraction * (0.75f + (float) Math.random() * 0.5f);
411
412 float resistance = member.getStats().getDynamic().getValue(Stats.CORONA_EFFECT_MULT);
413 strikeDamage *= resistance;
414
415 if (strikeDamage > HyperspaceTerrainPlugin.STORM_MAX_STRIKE_DAMAGE) {
416 strikeDamage = HyperspaceTerrainPlugin.STORM_MAX_STRIKE_DAMAGE;
417 }
418
419 if (strikeDamage > 0) {
420 float currCR = member.getRepairTracker().getBaseCR();
421 float crDamage = Math.min(currCR, strikeDamage);
422
423 if (crDamage > 0) {
424 member.getRepairTracker().applyCREvent(-crDamage, "explosion_" + entity.getId(),
425 "Damaged by explosion");
426 }
427
428 float hitStrength = member.getStats().getArmorBonus().computeEffective(member.getHullSpec().getArmorRating());
429 //hitStrength *= strikeDamage / crPerDep;
430 int numHits = (int) (strikeDamage / 0.1f);
431 if (numHits < 1) numHits = 1;
432 for (int j = 0; j < numHits; j++) {
433 member.getStatus().applyDamage(hitStrength);
434 }
435 //member.getStatus().applyHullFractionDamage(1f);
436 if (member.getStatus().getHullFraction() < 0.01f) {
437 member.getStatus().setHullFraction(0.01f);
438 picker.remove(member);
439 } else {
440 float w = picker.getWeight(member);
441 picker.setWeight(picker.getItems().indexOf(member), w * 0.5f);
442 }
443 }
444 //picker.remove(member);
445 }
446
447 if (fleet.isPlayerFleet()) {
448 Global.getSector().getCampaignUI().addMessage(
449 "Your fleet suffers damage from being caught in an explosion", Misc.getNegativeHighlightColor());
450 }
451 }
452
453}
454
455
456
457
458
459
460
461
462
static SettingsAPI getSettings()
Definition Global.java:51
static SoundPlayerAPI getSoundPlayer()
Definition Global.java:43
static SectorAPI getSector()
Definition Global.java:59
void render(CampaignEngineLayers layer, ViewportAPI viewport)
void init(SectorEntityToken entity, Object pluginParams)
void applyDamageToFleet(CampaignFleetAPI fleet, float damageMult)
SpriteAPI getSprite(String filename)
SoundAPI playSound(String id, float pitch, float volume, Vector2f loc, Vector2f vel)