Starsector API
Loading...
Searching...
No Matches
BaseFragmentMissileEffect.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.combat.threat;
2
3import java.util.LinkedHashSet;
4import java.util.Set;
5
6import java.awt.Color;
7
8import org.lwjgl.util.vector.Vector2f;
9
10import com.fs.starfarer.api.Global;
11import com.fs.starfarer.api.combat.CombatEngineAPI;
12import com.fs.starfarer.api.combat.CombatEntityAPI;
13import com.fs.starfarer.api.combat.DamageType;
14import com.fs.starfarer.api.combat.DamagingProjectileAPI;
15import com.fs.starfarer.api.combat.EmpArcEntityAPI;
16import com.fs.starfarer.api.combat.EmpArcEntityAPI.EmpArcParams;
17import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin;
18import com.fs.starfarer.api.combat.GuidedMissileAI;
19import com.fs.starfarer.api.combat.MissileAPI;
20import com.fs.starfarer.api.combat.OnFireEffectPlugin;
21import com.fs.starfarer.api.combat.ShipAPI;
22import com.fs.starfarer.api.combat.WeaponAPI;
23import com.fs.starfarer.api.combat.WeaponAPI.AIHints;
24import com.fs.starfarer.api.impl.combat.threat.RoilingSwarmEffect.RoilingSwarmParams;
25import com.fs.starfarer.api.impl.combat.threat.RoilingSwarmEffect.SwarmMember;
26import com.fs.starfarer.api.util.Misc;
27import com.fs.starfarer.api.util.WeightedRandomPicker;
28
30
31 public static enum FragmentBehaviorOnImpact {
32 STOP_AND_FADE,
33 STOP_AND_FLASH,
34 KEEP_GOING,
35 }
36
38 protected WeaponAPI weapon;
42 protected ShipAPI ship;
43
46
47
48 @Override
49 public void advance(float amount, CombatEngineAPI engine, WeaponAPI weapon) {
51 if (ship == null) return;
52
54 int active = swarm == null ? 0 : swarm.getNumActiveMembers();
55 int required = getNumFragmentsToFire();
56 boolean disable = active < required;
57 weapon.setForceDisabled(disable);
58
60 }
61
62
64 this.projectile = projectile;
65 this.weapon = weapon;
66 this.engine = engine;
67
68 if (!(projectile instanceof MissileAPI)) {
70 return;
71 }
73 if (missile.getSource() == null) {
75 return;
76 }
77
80 if (sourceSwarm == null) {
82 return;
83 }
84
86
87 SwarmMember fragment = pickPrimaryFragment();
88 if (fragment == null) {
90 return;
91 }
92
93 if (missile.getWeapon() == null || !missile.getWeapon().hasAIHint(AIHints.RANGE_FROM_SHIP_RADIUS)) {
94 missile.setStart(new Vector2f(missile.getLocation()));
95 }
96 missile.getLocation().set(fragment.loc);
97
98 // picked fragment with velocity closest to that of missile, leave the missile's velocity as is
100 missile.getVelocity().set(fragment.vel);
101 boolean setFacing = false;
103 if (missile.getAI() instanceof GuidedMissileAI) {
105 if (ai.getTarget() != null) {
107 setFacing = true;
108 }
109 }
110 }
111 if (!setFacing && fragment.vel.length() > 0.1f) {
113 }
114 }
115
116 RoilingSwarmParams params = new RoilingSwarmParams();
117 params.despawnSound = null;
118 params.maxSpeed = missile.getMaxSpeed() + 100f;
119 params.baseMembersToMaintain = 0;
120 params.removeMembersAboveMaintainLevel = false;
121 params.keepProxBasedScaleForAllMembers = true;
122 params.initialMembers = 0;
123 params.maxOffset = missile.getCollisionRadius() * 1.5f;
124
126
127 // can't use data members inside the anon class since they'll change when it fires again
128 MissileAPI missile2 = missile;
129 FragmentBehaviorOnImpact behavior = getOtherFragmentBehaviorOnImpact();
131 String explosionSoundId = getExplosionSoundId();
132 RoilingSwarmEffect missileSwarm = new RoilingSwarmEffect(missile2, params) {
133 boolean exploded = false;
134 Set<SwarmMember> stopped = new LinkedHashSet<>();
135 int origMembers = 0;
136 boolean inited = false;
137 @Override
138 public void advance(float amount) {
139 super.advance(amount);
140 //if (true) return;
141
143 if (!inited) {
144 origMembers = members.size();
145 inited = true;
146 }
147 if (origMembers > 0 && members.size() > 1 && missile2.getMaxHitpoints() > 0) {
148 float max = missile2.getMaxHitpoints();
149 float hpPerMember = max / origMembers;
150 float hpLost = max - missile2.getHitpoints();
151 int loseMembers = (int) (hpLost / hpPerMember);
152 int num = members.size();
153 int alreadyLost = origMembers - num;
154 for (SwarmMember p : members) {
155 if (p.fader.isFadingOut()) {
156 alreadyLost++;
157 }
158 }
159 int lose = loseMembers - alreadyLost;
160 if (lose > 0) {
161 despawnMembers(lose, false);
162 }
163 }
164 }
165
166 fragment.loc.set(missile2.getLocation());
167 fragment.vel.set(missile2.getVelocity());
168 if (missile2.isFizzling() && engine.isMissileAlive(missile2)) {
169 fragment.fader.setBrightness(missile2.getCurrentBaseAlpha());
170 }
171 if (missile2.didDamage()) {
172 if (behavior != FragmentBehaviorOnImpact.KEEP_GOING) {
173 CombatEntityAPI target = null;
174 if (missile2.getDamageTarget() instanceof CombatEntityAPI) {
175 target = (CombatEntityAPI) missile2.getDamageTarget();
176 }
177 for (SwarmMember p : members) {
178 if (p == fragment || stopped.contains(p)) {
179 if (p == fragment) {
180 //p.fader.setDurationOut(0.5f);
181 }
182 continue;
183 }
184 boolean hit = false;
185 if (target != null && target.getExactBounds() != null) {
186 if (target instanceof ShipAPI) {
187 ShipAPI ship = (ShipAPI) target;
188 if (ship.getShield() != null) {
189 boolean inArc = ship.getShield().isWithinArc(p.loc);
190 if (inArc) {
193 }
194 }
195 }
196 if (!hit) {
197 hit = target.isPointInBounds(p.loc);
198 }
199 } else {
200 Vector2f toP = Vector2f.sub(p.loc, fragment.loc, new Vector2f());
201 hit = Vector2f.dot(toP, fragment.vel) > 0;
202 }
203 if (hit) {
204 p.vel.set(new Vector2f());
205 if (behavior == FragmentBehaviorOnImpact.STOP_AND_FLASH) {
206 p.flash();
207 }
208 reportFragmentHit(missile2, p, this, target);
209 stopped.add(p);
210 }
211 }
212 }
213 }
214 if (explodeOnFizzling && explosionSoundId != null) {
215 if ((missile2.isFizzling() || (missile2.getHitpoints() <= 0 && !missile2.didDamage())) && !exploded) {
216 exploded = true;
217 Global.getSoundPlayer().playSound(explosionSoundId, 1f, 1f, missile2.getLocation(), missile2.getVelocity());
218 missile2.interruptContrail();
219 engine.removeEntity(missile2);
220 missile2.explode();
221 }
222 }
223
224 if ((missile2.isFizzling() || missile2.getHitpoints() <= 0) && !missile2.didDamage() && !exploded) {
225 params.minDespawnTime = 0.5f;
226 params.maxDespawnTime = 1f;
227 params.minFadeoutTime = 0.5f;
228 params.maxFadeoutTime = 1f;
229 setForceDespawn(true);
230 }
231
232 swarmAdvance(amount, missile2, this);
233 }
234
235// @Override
236// public int getNumMembersToMaintain() {
237// int base = params.baseMembersToMaintain;
238// float level = missile2.getHullLevel();
239// int maintain = (int) Math.round(level * base);
240// if (maintain < 1) maintain = 1;
241// return maintain;
242// }
243 };
244
245 sourceSwarm.removeMember(fragment);
246 missileSwarm.addMember(fragment);
247 fragment.rollOffset(missileSwarm.params, missile);
248
250 if (fragment.flash != null) {
251 fragment.flash = null;
252 }
253 fragment.flashNext = null;
254
255 fragment.flash();
256 fragment.flash.setBounceDown(false);
257 }
258
259
260 int transfer = getNumOtherMembersToTransfer();
261 if (transfer > 0) {
262 sourceSwarm.transferMembersTo(missileSwarm, transfer, fragment.loc, getRangeForNearbyFragments());
263 }
264
265 int add = getNumOtherMembersToAdd();
266 if (addNewMembersIfNotEnoughToTransfer() && missileSwarm.members.size() - 1 < transfer) {
267 add += transfer - (missileSwarm.members.size() - 1);
268 }
269 if (add > 0) {
270 missileSwarm.addMembers(add);
271 }
272
273 swarmCreated(missile, missileSwarm, sourceSwarm);
274
275 float hpLoss = getHPLossPerTransferredMember();
276 hpLoss *= 1 + transfer;
277 if (hpLoss > 0) {
279 // cause the swarm (or what's left of it) to despawn
280 if (ship.getHitpoints() <= 0) {
281 ship.setSpawnDebris(false);
282 engine.applyDamage(ship, ship.getLocation(), 100f, DamageType.ENERGY, 0f, true, false, missile, false);
283 }
284 }
285
286 if (withEMPArc()) {
287 spawnEMPArc();
288 }
289 }
290
294 protected void reportFragmentHit(MissileAPI missile, SwarmMember p, RoilingSwarmEffect swarm, CombatEntityAPI target) {
295
296 }
297
299 if (!ship.isFighter()) return 0f;
300 float hpLoss = ship.getMaxHitpoints() / (sourceSwarm.params.baseMembersToMaintain * 0.8f);
301 return hpLoss;
302 }
303
304 protected void configureMissileSwarmParams(RoilingSwarmParams params) {
305 params.flashFringeColor = new Color(255,50,50,255);
306 params.flashCoreColor = Color.white;
307 params.flashRadius = 60f;
308 params.flashCoreRadiusMult = 0.75f;
309 }
310
312 if (missile.getAI() instanceof GuidedMissileAI) {
314 if (ai.getTarget() == null) {
315 return true;
316 } else {
317 return false;
318 }
319 }
320 return true;
321 }
322
324 return false;
325 }
326
333
334 protected SwarmMember pickOuterFragmentWithinRange(float range) {
335 SwarmMember best = null;
336 float maxDist = -Float.MAX_VALUE;
338 while (!picker.isEmpty()) {
339 SwarmMember p = picker.pickAndRemove();
340 float dist = Misc.getDistance(p.loc, sourceSwarm.getAttachedTo().getLocation());
341 if (sourceSwarm.params.generateOffsetAroundAttachedEntityOval) {
342 //dist -= sourceSwarm.attachedTo.getCollisionRadius() * 0.75f;
343 dist -= Misc.getTargetingRadius(p.loc, sourceSwarm.attachedTo, false) + sourceSwarm.params.maxOffset - range * 0.5f;
344 }
345 if (dist > maxDist && dist < range) {
346 best = p;
347 maxDist = dist;
348 }
349 }
350 return best;
351 }
352
353 protected SwarmMember pickVelocityMatchingFragmentWithinRange(float range) {
354 Vector2f vel = missile.getVelocity();
355 SwarmMember best = null;
356 float maxVelDiff = 0f;
358 while (!picker.isEmpty()) {
359 SwarmMember p = picker.pickAndRemove();
360 float dist = Misc.getDistance(p.loc, sourceSwarm.getAttachedTo().getLocation());
361 if (sourceSwarm.params.generateOffsetAroundAttachedEntityOval) {
362 dist -= Misc.getTargetingRadius(p.loc, sourceSwarm.attachedTo, false) + sourceSwarm.params.maxOffset - range * 0.5f;
363 }
364 float velDiff = Misc.getDistance(p.vel, vel);
365 if (velDiff > maxVelDiff && dist < range) {
366 best = p;
367 maxVelDiff = dist;
368 }
369 }
370 return best;
371 }
372
373 protected SwarmMember pickOuterFragmentWithinRangeClosestTo(float range, Vector2f otherLoc) {
374 SwarmMember best = null;
375 float minDist = Float.MAX_VALUE;
377 while (!picker.isEmpty()) {
378 SwarmMember p = picker.pickAndRemove();
379 float dist = Misc.getDistance(p.loc, sourceSwarm.getAttachedTo().getLocation());
380 if (sourceSwarm.params.generateOffsetAroundAttachedEntityOval) {
381 dist -= Misc.getTargetingRadius(p.loc, sourceSwarm.attachedTo, false) + sourceSwarm.params.maxOffset - range * 0.5f;
382 }
383 if (dist > range) continue;
384 dist = Misc.getDistance(p.loc, otherLoc);
385 if (dist < minDist) {
386 best = p;
387 minDist = dist;
388 }
389 }
390 return best;
391 }
392
394 return true;
395 }
396
397 protected boolean makePrimaryFragmentGlow() {
398 return true;
399 }
400
401 protected float getRangeForNearbyFragments() {
402 return 75f;
403 }
405 return 150f;
406 }
407
409 return 0;
410 }
412 return true;
413 }
414 protected int getNumOtherMembersToAdd() {
415 return 0;
416 }
417
418 protected int getEMPResistance() {
419 return 0;
420 }
421
422 protected FragmentBehaviorOnImpact getOtherFragmentBehaviorOnImpact() {
423 return FragmentBehaviorOnImpact.STOP_AND_FLASH;
424 }
425
426
427 @Override
429 return 1 + getNumOtherMembersToTransfer();
430 }
431
432 protected boolean explodeOnFizzling() {
433 return false;
434 }
435
436 protected String getExplosionSoundId() {
437 return null;
438 }
439
440 protected void swarmAdvance(float amount, MissileAPI missile, RoilingSwarmEffect swarm) {
441
442 }
443
444
445 protected boolean withEMPArc() {
446 return !ship.isFighter();
447 }
448
449 protected Color getEMPFringeColor() {
450 Color c = weapon.getSpec().getGlowColor();
451 //c = Misc.setAlpha(c, 127);
452 //c = Misc.scaleColorOnly(c, 0.75f);
453 return c;
454 }
455
456 protected Color getEMPCoreColor() {
457 return Color.white;
458 }
459
460 protected void spawnEMPArc() {
461
462 Vector2f from = weapon.getFirePoint(0);
463
464 EmpArcParams params = new EmpArcParams();
465 params.segmentLengthMult = 4f;
466
467 params.glowSizeMult = 0.5f;
468 params.brightSpotFadeFraction = 0.33f;
469 params.brightSpotFullFraction = 0.5f;
470 params.movementDurMax = 0.2f;
471 params.flickerRateMult = 0.5f;
472
473 float dist = Misc.getDistance(from, missile.getLocation());
474 float minBright = 100f;
475 if (dist * params.brightSpotFullFraction < minBright) {
476 params.brightSpotFullFraction = minBright / Math.max(minBright, dist);
477 }
478
479 float thickness = 20f;
480
483 missile,
484 thickness, // thickness
487 params
488 );
489 //arc.setCoreWidthOverride(thickness * coreWidthMult);
490 arc.setSingleFlickerMode(true);
492 //arc.setRenderGlowAtStart(false);
493 //arc.setFadedOutAtStart(true);
494 }
495
496}
497
498
499
500
501
502
503
504
static SoundPlayerAPI getSoundPlayer()
Definition Global.java:49
void advance(float amount, CombatEngineAPI engine, WeaponAPI weapon)
void swarmAdvance(float amount, MissileAPI missile, RoilingSwarmEffect swarm)
void reportFragmentHit(MissileAPI missile, SwarmMember p, RoilingSwarmEffect swarm, CombatEntityAPI target)
SwarmMember pickOuterFragmentWithinRangeClosestTo(float range, Vector2f otherLoc)
void swarmCreated(MissileAPI missile, RoilingSwarmEffect missileSwarm, RoilingSwarmEffect sourceSwarm)
void onFire(DamagingProjectileAPI projectile, WeaponAPI weapon, CombatEngineAPI engine)
void transferMembersTo(RoilingSwarmEffect other, float fraction)
WeightedRandomPicker< SwarmMember > getPicker(boolean preferNonFlashing, boolean preferNonPicked, Vector2f towards)
static RoilingSwarmEffect getSwarmFor(CombatEntityAPI entity)
void setBounceDown(boolean bounceDown)
void setBrightness(float brightness)
static float getDistance(SectorEntityToken from, SectorEntityToken to)
Definition Misc.java:599
static float getTargetingRadius(Vector2f from, CombatEntityAPI target, boolean considerShield)
Definition Misc.java:1349
static float getAngleInDegrees(Vector2f v)
Definition Misc.java:1126
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)
void applyDamage(CombatEntityAPI entity, Vector2f point, float damageAmount, DamageType damageType, float empAmount, boolean bypassShields, boolean dealsSoftFlux, Object source, boolean playSound)
void removeEntity(CombatEntityAPI entity)
boolean isMissileAlive(MissileAPI missile)
void setUpdateFromOffsetEveryFrame(boolean updateFromOffsetEveryFrame)
void setEmpResistance(int empResistance)
DamagingProjectileAPI explode()
boolean isWithinArc(Vector2f point)
void setSpawnDebris(boolean spawnDebris)
void setForceDisabled(boolean forceDisabled)
default void showNoFragmentSwarmWarning(WeaponAPI w, ShipAPI ship)