Starsector API
Loading...
Searching...
No Matches
RoilingSwarmEffect.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.combat.threat;
2
3import java.util.ArrayList;
4import java.util.EnumSet;
5import java.util.LinkedHashMap;
6import java.util.LinkedHashSet;
7import java.util.List;
8import java.util.Set;
9
10import java.awt.Color;
11
12import org.lwjgl.util.vector.Vector2f;
13
14import com.fs.starfarer.api.Global;
15import com.fs.starfarer.api.combat.BaseCombatLayeredRenderingPlugin;
16import com.fs.starfarer.api.combat.CombatEngineLayers;
17import com.fs.starfarer.api.combat.CombatEntityAPI;
18import com.fs.starfarer.api.combat.MissileAPI;
19import com.fs.starfarer.api.combat.ShipAPI;
20import com.fs.starfarer.api.combat.ViewportAPI;
21import com.fs.starfarer.api.graphics.SpriteAPI;
22import com.fs.starfarer.api.util.FaderUtil;
23import com.fs.starfarer.api.util.IntervalUtil;
24import com.fs.starfarer.api.util.ListMap;
25import com.fs.starfarer.api.util.Misc;
26import com.fs.starfarer.api.util.WeightedRandomPicker;
27
29
30 public static interface SwarmMemberOffsetModifier {
31 void modifyOffset(SwarmMember p);
32 }
33
34 public static class RoilingSwarmParams {
35 public String spriteCat = "misc";
36 public String spriteKey = "threat_swarm_pieces";
37 public String despawnSound = "threat_swarm_destroyed";
38
43 public String memberExchangeClass = null;
44 public float memberExchangeRange = 500f;
45 public int minMembersToExchange = 1;
46 public int maxMembersToExchange = 3;
47 public float memberExchangeRate = 0.1f;
48
49 public String flockingClass = null;
50
51 public float baseSpriteSize = 20f;
52
53 public float baseDur = 100000f;
54 public float durRange = 0f;
55 public float despawnDist = 0f;
56
57 public float baseScale = 0.5f;
58 public float scaleRange = 0.5f;
59 public float baseFriction = 100f;
60 public float frictionRange = 500f;
61 public float baseSpringConstant = 50f;
62 public float springConstantNegativeRange = 20f;
63 public float baseSpringFreeLength = 20f;
64 public float springFreeLengthRange = 20f;
65
66 public float offsetRotationDegreesPerSecond = 0f;
67
68 public float lateralFrictionFactor = 20f;
69 public float lateralFrictionTurnRateFactor = 0;
70 //public float lateralFrictionFactor = 0.5f;
71 //public float lateralFrictionTurnRateFactor = 0.2f;
72
73 public float minSpeedForFriction = 25f;
74
75 public float flashRateMult = 1f;
76 public float flashRadius = 100f;
77 public float flashCoreRadiusMult = 1f;
78 public float flashFrequency = 1f;
79 public int numToFlash = 1;
80 public int numToRespawn = 1;
81 public float preFlashDelay = 0f;
82 public float flashProbability = 0f;
83 public boolean renderFlashOnSameLayer = false;
84 public Color flashFringeColor = new Color(255,0,0,255);
85 public Color flashCoreColor = Color.white;
86
87 public float alphaMult = 1f;
88 public float alphaMultBase = 1f;
89 public float alphaMultFlash = 1f;
90 public Color color = Color.white;
91
92 public float minFadeoutTime = 1f;
93 public float maxFadeoutTime = 1f;
94 public float minDespawnTime = 2f;
95 public float maxDespawnTime = 1f;
96
97 public boolean autoscale = false;
98
102 public float springStretchMult = 10f;
103
104 public float swarmLeadsByFractionOfVelocity = 0.03f;
105
106 public float outspeedAttachedEntityBy = 100f;
107
108 public float visibleRange = 500f;
109 public float maxTurnRate = 60f;
110 public float spawnOffsetMult = 0f;
111 public float spawnOffsetMultForInitialSpawn = -1f;
112 public float maxSpeed = 500f;
113 public float minOffset = 0f;
114 public float maxOffset = 20f;
115 public boolean generateOffsetAroundAttachedEntityOval = false;
116
117 public SwarmMemberOffsetModifier offsetModifier = null;
118
119 public boolean withInitialMembers = true;
120 public boolean withRespawn = true;
121 public int initialMembers = 50;
122 public int baseMembersToMaintain = 50;
123 public boolean removeMembersAboveMaintainLevel = true;
124 public int maxNumMembersToAlwaysRemoveAbove = -1;
125 public float memberRespawnRate = 1f;
126 public float offsetRerollFractionOnMemberRespawn = 0f;
127
128 public Set<String> tags = new LinkedHashSet<>();
129
130 public boolean keepProxBasedScaleForAllMembers = false;
131
132 }
133
134
135 public static class SwarmMember {
136 public SpriteAPI sprite;
137
138 public Vector2f offset = new Vector2f();
139 public Vector2f loc = new Vector2f();
140 public Vector2f vel = new Vector2f();
141
142 public float scale = 1f;
143 public float turnRate = 1f;
144 public float angle = 1f;
145 public float recentlyPicked = 0f;
146
147 public float dur;
148 public FaderUtil fader;
149 public FaderUtil flash;
150 public FaderUtil flashNext;
151
152 public FaderUtil scaler;
153 public float minScale;
154 public boolean keepScale = false;
155
156 public SwarmMember(Vector2f startingLoc, RoilingSwarmParams params, CombatEntityAPI attachedTo) {
157 //sprite = Global.getSettings().getSprite("misc", "nebula_particles");
158 //fx_particles2 - swirly
159 // nebula_particles2 - smooth, but 2x2
160 // dust_particles - smooth
161
162 sprite = Global.getSettings().getSprite(params.spriteCat, params.spriteKey);
163 float i = Misc.random.nextInt(4);
164 float j = Misc.random.nextInt(4);
165 sprite.setTexWidth(0.25f);
166 sprite.setTexHeight(0.25f);
167 sprite.setTexX(i * 0.25f);
168 sprite.setTexY(j * 0.25f);
169
170 //sprite.setAdditiveBlend();
171 sprite.setNormalBlend();
172
173 angle = (float) Math.random() * 360f;
174
175
176 rollOffset(params, attachedTo);
177
178 Vector2f spawnOffset = new Vector2f(offset);
179 spawnOffset.scale(params.spawnOffsetMult);
180 if (params.spawnOffsetMult != 0) {
181 spawnOffset = Misc.rotateAroundOrigin(spawnOffset, attachedTo.getFacing());
182 }
183
184 Vector2f.add(startingLoc, spawnOffset, loc);
185
186 vel = Misc.getPointWithinRadius(new Vector2f(), params.maxSpeed * 0.25f);
187
188// params.maxSpeed = 1200;
189// vel = new Vector2f(0, -1400f);
190// Vector2f.add(startingLoc, new Vector2f(0, 500f), loc);
191
192 dur = params.baseDur + (float) Math.random() * params.durRange;
193 scale = 1f;
194
195 turnRate = Math.signum((float) Math.random() - 0.5f) * params.maxTurnRate * (float) Math.random();
196
197 fader = new FaderUtil(0f, 0.5f + (float) Math.random() * 0.5f,
198 params.minFadeoutTime + (params.maxFadeoutTime - params.minFadeoutTime) * (float) Math.random());
199 fader.fadeIn();
200
201 scaler = new FaderUtil(0f, 0.5f + (float) Math.random() * 0.5f, 0.5f + (float) Math.random() * 0.5f);
202 scaler.setBounce(true, true);
203 scaler.fadeIn();
204
205 }
206
207 public void rollOffset(RoilingSwarmParams params, CombatEntityAPI attachedTo) {
208 if (params.generateOffsetAroundAttachedEntityOval && attachedTo instanceof ShipAPI) {
209 ShipAPI ship = (ShipAPI) attachedTo;
210
211 float angle = (float) Math.random() * 360f;
212 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(angle);
213 Vector2f from = new Vector2f(dir);
214 from.scale(ship.getCollisionRadius() + 1000f);
215 Vector2f.add(from, ship.getLocation(), from);
216
217 float min = Misc.getTargetingRadius(from, ship, false);
218 float max = min + params.maxOffset;
219 min += params.minOffset;
220
221 float f = min/(Math.max(1f, max));
222 f = Math.max(0.1f, f * 0.75f);
223
224 // there's definitely a smarter way than this to get a uniform distribution -am
225 float r = -1f;
226 for (int i = 0; i < 10; i++) {
227 float test = (float) Math.sqrt(Math.random());
228 if (test >= f) {
229 r = test;
230 break;
231 }
232 }
233 if (r < 0f) {
234 r = f + (1f - f) * (float) Math.random();
235 }
236
237 //dir.scale(min + (max - min) * r);
238 //r = f;
239 dir.scale(max * r);
240 offset = dir;
241 offset = Misc.rotateAroundOrigin(offset, -attachedTo.getFacing());
242// if (params.spawnOffsetMultForInitialSpawn != params.spawnOffsetMult) {
243// offset = Misc.rotateAroundOrigin(offset, (float) Math.random() * 360f);
244//
245// }
246 //offset = new Vector2f(200f, 0f);
247 } else {
248 offset = Misc.getPointWithinRadiusUniform(new Vector2f(), params.minOffset, params.maxOffset, Misc.random);
249 }
250
251 if (params.offsetModifier != null) {
252 params.offsetModifier.modifyOffset(this);
253 }
254 }
255
256 public void advance(float amount, RoilingSwarmParams params) {
257 loc.x += vel.x * amount;
258 loc.y += vel.y * amount;
259
260 angle += turnRate * amount;
261
262 dur -= amount;
263 if (dur <= 0) fader.fadeOut();
264
265 recentlyPicked -= amount;
266 if (recentlyPicked < 0) recentlyPicked = 0f;
267
268 fader.advance(amount);
269 if (flash != null) {
270 flash.advance(amount * params.flashRateMult);
271 if (flash.isFadedOut()) {
272 flash = null;
273 }
274 }
275 if (flash == null && flashNext != null) {
276 flash = flashNext;
277 flashNext = null;
278 }
279
280 if (params.autoscale && !keepScale) {
281 scaler.advance(amount * 0.5f);
282 scale = minScale + (1f - minScale) * scaler.getBrightness() * scaler.getBrightness();
283 }
284 }
285
286 public void flash() {
287 if (flash == null) {
288 flash = new FaderUtil(0f, 0.25f, 1f);
289 flash.setBounceDown(true);
290 flash.fadeIn();
291 }
292 }
293
294 public void flashNext() {
295 flashNext = new FaderUtil(0f, 0.25f, 1f);
296 flashNext.setBounceDown(true);
297 flashNext.fadeIn();
298 }
299
300 public void setRecentlyPicked(float pickDuration) {
301 recentlyPicked = Math.max(recentlyPicked, pickDuration);
302 }
303 }
304
306 if (entity == null) return null;
307 return getShipMap().get(entity);
308 }
309
310 public static String KEY_SHIP_MAP = "RoilingSwarmEffect_shipMap_key";
311 public static String KEY_FLOCKING_MAP = "RoilingSwarmEffect_flockingMap_key";
312 public static String KEY_EXCHANGE_MAP = "RoilingSwarmEffect_exchangeMap_key";
313
314 @SuppressWarnings("unchecked")
315 public static LinkedHashMap<CombatEntityAPI, RoilingSwarmEffect> getShipMap() {
316 LinkedHashMap<CombatEntityAPI, RoilingSwarmEffect> map =
317 (LinkedHashMap<CombatEntityAPI, RoilingSwarmEffect>) Global.getCombatEngine().getCustomData().get(KEY_SHIP_MAP);
318 if (map == null) {
319 map = new LinkedHashMap<>();
321 }
322 return map;
323 }
330 @SuppressWarnings("unchecked")
331 public static ListMap<RoilingSwarmEffect> getStringToSwarmMap(String key) {
333 (ListMap<RoilingSwarmEffect>) Global.getCombatEngine().getCustomData().get(key);
334 if (map == null) {
335 map = new ListMap<>();
336 Global.getCombatEngine().getCustomData().put(key, map);
337 }
338 return map;
339 }
340
341 protected RoilingSwarmParams params;
342 protected List<SwarmMember> members = new ArrayList<SwarmMember>();
343
345 protected float elapsed = 0f;
349 protected boolean spawnedInitial = false;
350 protected boolean despawning = false;
351 protected boolean forceDespawn = false;
352 protected float sinceExchange = 0f;
353 protected float maxDistFromCenterToFragment = 0f;
354
355 public Object custom1;
356 public Object custom2;
357 public Object custom3;
358
359
361 this(attachedTo, new RoilingSwarmParams());
362 }
363
365 //System.out.println("Creating swarm for " + attachedTo);
368
369 this.attachedTo = attachedTo;
370 this.params = params;
371
372 // these values kinda work, too - a bit tigher
373 //params.maxOffset = 20;
374 //params.baseSpringFreeLength = 10;
375
376 //params.initialMembers = 1000;
377 //params.frictionRange = 0f;
378// params.lateralFrictionFactor = 1f;
379// params.baseSpringFreeLength = 0f;
380// params.springFreeLengthRange = 40f;
381
382// params.frictionRange = 100f;
383// params.baseFriction = 50f;
384// params.lateralFrictionFactor = 1f;
385
386// params.memberExchangeClass = "attack_swarm";
387
388// params.baseDur = 5f;
389// params.durRange = 10f;
390// params.memberRespawnRate = 10f;
391
392 flashChecker = new IntervalUtil(0.5f, 1.5f);
393 respawnChecker = new IntervalUtil(0.5f, 1.5f);
394 transferChecker = new IntervalUtil(0.2f, 1.8f);
395
396 getShipMap().put(attachedTo, this);
397 if (params.flockingClass != null) {
398 getFlockingMap().add(params.flockingClass, this);
399 }
400 if (params.memberExchangeClass != null) {
401 getExchangeMap().add(params.memberExchangeClass, this);
402 }
403 }
404
406 super.init(entity);
407 }
408
409 public float getRenderRadius() {
410 float extra = 0f;
411 if (sinceExchange < 3f) {
412 extra = 500f - sinceExchange * 100f;
413 }
414 extra = Math.max(extra, maxDistFromCenterToFragment);
415 return params.visibleRange + extra;
416 }
417
418
419 protected EnumSet<CombatEngineLayers> layers = EnumSet.of(CombatEngineLayers.FIGHTERS_LAYER,
421 @Override
422 public EnumSet<CombatEngineLayers> getActiveLayers() {
423 return layers;
424 }
425
426 public SwarmMember addMember() {
427 SwarmMember sm = new SwarmMember(attachedTo.getLocation(), params, attachedTo);
428 addMember(sm);
429 return sm;
430 }
431 public void addMember(SwarmMember sm) {
432 members.add(sm);
433 }
434 public void removeMember(SwarmMember sm) {
435 members.remove(sm);
436 }
437 public void addMembers(int num) {
438 for (int i = 0; i < num; i++) {
439 addMember();
440 }
441 }
442 public void transferMembersTo(RoilingSwarmEffect other, float fraction) {
443 int num = (int) (members.size() * fraction);
444 transferMembersTo(other, num);
445 }
446 public void transferMembersTo(RoilingSwarmEffect other, int num) {
447 transferMembersTo(other, num, null, 0f);
448 }
449 public void transferMembersTo(RoilingSwarmEffect other, int num, Vector2f point, float maxRangeFromPoint) {
450 if (num <= 0) return;
451 WeightedRandomPicker<SwarmMember> picker = getPicker(true, true);
452 if (point != null) {
453 picker = getPicker(true, true, point, maxRangeFromPoint);
454 }
455 for (int i = 0; i < num; i++) {
456 SwarmMember pick = picker.pickAndRemove();
457 if (pick == null) break;
458
460 other.addMember(pick);
461 pick.rollOffset(other.params, other.attachedTo);
462 }
463 }
464
465 public void despawnMembers(int num) {
466 despawnMembers(num, true);
467 }
468 public void despawnMembers(int num, boolean allowFirst) {
469 WeightedRandomPicker<SwarmMember> picker = getPicker(false, false);
470 if (!allowFirst && !members.isEmpty()) {
471 picker.remove(members.get(0));
472 }
473 for (int i = 0; i < num; i++) {
474 SwarmMember pick = picker.pickAndRemove();
475 if (pick == null) break;
476 pick.fader.fadeOut();
477 }
478 }
479
480 public SwarmMember pick(float pickDuration) {
481 SwarmMember pick = getPicker(true, true).pick();
482 if (pick != null) {
483 pick.setRecentlyPicked(pickDuration);
484 }
485 return pick;
486 }
487
488 public WeightedRandomPicker<SwarmMember> getPicker(boolean preferNonFlashing, boolean preferNonPicked,
489 Vector2f towards) {
491 float angle = Misc.getAngleInDegrees(attachedTo.getLocation(), towards);
492 for (SwarmMember p : members) {
493 if (p.fader.isFadingOut() || p.fader.isFadedOut()) continue;
494 float w = 1000f;
495 if (preferNonFlashing && p.flash != null) w *= 0.001f;
496 if (preferNonPicked && p.recentlyPicked > 0) w *= 0.001f;
497
498 float curr = Misc.getAngleInDegrees(attachedTo.getLocation(), p.loc);
499 float diff = Misc.getAngleDiff(angle, curr);
500 if (diff > 90f) {
501 float f = Misc.normalizeAngle(diff - 90f) / 90f;
502 if (f > 0.9999f) f = 0.9999f;
503 w *= 1f - f;
504 w *= 0.05f;
505 }
506
507 picker.add(p, w);
508 }
509 return picker;
510 }
511 public WeightedRandomPicker<SwarmMember> getPicker(boolean preferNonFlashing, boolean preferNonPicked,
512 Vector2f point, float preferMaxRangeFromPoint) {
514 for (SwarmMember p : members) {
515 if (p.fader.isFadingOut() || p.fader.isFadedOut()) continue;
516 float w = 1000f;
517 if (preferNonFlashing && p.flash != null) w *= 0.001f;
518 if (preferNonPicked && p.recentlyPicked > 0) w *= 0.001f;
519
520 float dist = Misc.getDistance(point, p.loc);
521 if (dist > preferMaxRangeFromPoint) {
522 float f = (dist - preferMaxRangeFromPoint) / Math.max(1f, preferMaxRangeFromPoint);
523 if (f > 0.9999f) f = 0.9999f;
524 w *= 1f - f;
525 } else {
526 w *= 0.25f + 0.75f * (1f - dist / Math.max(1f, preferMaxRangeFromPoint));
527 }
528
529 picker.add(p, w);
530 }
531 return picker;
532 }
533 public WeightedRandomPicker<SwarmMember> getPicker(boolean preferNonFlashing, boolean preferNonPicked) {
535 for (SwarmMember p : members) {
536 if (p.fader.isFadingOut() || p.fader.isFadedOut()) continue;
537 float w = 1000f;
538 if (preferNonFlashing && p.flash != null) w *= 0.001f;
539 if (preferNonPicked && p.recentlyPicked > 0) w *= 0.001f;
540 picker.add(p, w);
541 }
542 return picker;
543 }
544
545 public int getNumActiveMembers() {
546 return getPicker(false, false).getItems().size();
547 }
548
549 public float getGlowForMember(SwarmMember p) {
550 float glow = 0f;
551 if (p.flash != null) {
552 glow = p.flash.getBrightness();
553 glow *= glow;
554 }
555 return glow;
556 }
557
559 return params.baseMembersToMaintain;
560 }
561
562 public void advance(float amount) {
563 //if (true) return;
564
565 if (Global.getCombatEngine().isPaused() || entity == null || isExpired()) return;
566
567 if (!spawnedInitial && params.withInitialMembers) {
568 float origSpawnOffsetMult = params.spawnOffsetMult;
569 if (params.spawnOffsetMultForInitialSpawn >= 0) {
570 params.spawnOffsetMult = params.spawnOffsetMultForInitialSpawn;
571 }
572 addMembers(params.initialMembers - getNumActiveMembers());
573 params.spawnOffsetMult = origSpawnOffsetMult;
574 spawnedInitial = true;
575 }
576
577// attachedTo.setCollisionClass(CollisionClass.SHIP);
578// ((ShipAPI)attachedTo).getMutableStats().getHullDamageTakenMult().modifyMult("efwefwefwe", 0f);
579
580 //System.out.println("Swarm members: " + members.size());
581
583
584 elapsed += amount;
585
586 Vector2f aVel = attachedTo.getVelocity();
587 float aSpeed = aVel.length();
588 float leadAmount = aSpeed * params.swarmLeadsByFractionOfVelocity;
589
590 Vector2f facingDir = Misc.getUnitVectorAtDegreeAngle(attachedTo.getFacing());
591 if (attachedTo.getVelocity().length() > 1f) {
592 facingDir = Misc.normalise(new Vector2f(attachedTo.getVelocity()));
593 }
594
595 Vector2f aLoc = new Vector2f(attachedTo.getLocation());
596// if (params.generateOffsetAroundAttachedEntityOval && attachedTo instanceof ShipAPI) {
597// ShipAPI ship = (ShipAPI) attachedTo;
598// aLoc = new Vector2f(ship.getShieldCenterEvenIfNoShield());
599// }
600
601 List<SwarmMember> remove = new ArrayList<>();
602
603 float maxSpeed = params.maxSpeed;
604 if (params.outspeedAttachedEntityBy != 0) {
605 float minMaxSpeed = attachedTo.getVelocity().length() + params.outspeedAttachedEntityBy;
606 if (minMaxSpeed > maxSpeed) maxSpeed = minMaxSpeed;
607 }
608
609 // springs! (sort of, sqrt instead of linear) and friction
610 boolean despawnAll = shouldDespawnAll();
611
612 float maxOffsetForProx = params.maxOffset;
613 if (params.generateOffsetAroundAttachedEntityOval) {
614 maxOffsetForProx += attachedTo.getCollisionRadius() * 0.75f;
615 }
616
617
618// int flashing = 0;
619// for (SwarmMember p : members) {
620// if (p.flash != null) flashing++;
621// }
622// System.out.println("Flashing: " + flashing + " / " + members.size());
623
624 float maxDistSq = 0f;
626 for (SwarmMember p : members) {
627 float distSq = (aLoc.x - p.loc.x) * (aLoc.x - p.loc.x) + (aLoc.y - p.loc.y) * (aLoc.y - p.loc.y);
628 maxDistSq = Math.max(maxDistSq, distSq);
629 if (params.despawnDist > 0 && params.despawnDist * params.despawnDist < distSq) {
630 p.fader.fadeOut();
631 }
632
633 if (!despawnAll) {
634 Vector2f offset = new Vector2f(p.offset);
635 //offset.y *= p.offsetDrift;
636 //offset.y = p.offsetDrift * params.maxOffset;
637 //offset = Misc.rotateAroundOrigin(offset, attachedTo.getFacing() + elapsed * 5f);
638
639 float prox = offset.length() / maxOffsetForProx;
640 prox = 1f - prox;
641
642
643 offset = Misc.rotateAroundOrigin(offset, attachedTo.getFacing() + elapsed * params.offsetRotationDegreesPerSecond);
644 //offset = Misc.rotateAroundOrigin(offset, attachedTo.getFacing());
645 offset.x += facingDir.x * leadAmount;
646 offset.y += facingDir.y * leadAmount;
647
648 if (!params.keepProxBasedScaleForAllMembers) {
649 p.scale = params.baseScale + (1f - prox) * params.scaleRange;
650 if (p.scale > 1f) p.scale = 1f;
651 }
652
653 Vector2f dest = new Vector2f(aLoc);
654 Vector2f.add(dest, offset, dest);
655 float dist = Misc.getDistance(p.loc, dest);
656
657 Vector2f dirToDest = Misc.getUnitVector(p.loc, dest);
658 Vector2f perp = new Vector2f(-dirToDest.y, dirToDest.x);
659
660 float friction = params.baseFriction + params.frictionRange * prox;
661
662 float k = params.baseSpringConstant - params.springConstantNegativeRange * prox;
663 float freeLength = params.baseSpringFreeLength + params.springFreeLengthRange * prox;
664
665 // if (proj == Global.getCombatEngine().getPlayerShip()) {
666 // System.out.println("32ferfwefw");
667 // }
668
669 float stretch = dist - freeLength;
670
671 stretch = (float) (Math.sqrt(Math.abs(stretch * params.springStretchMult)) * Math.signum(stretch));
672
673 float forceMag = k * Math.abs(stretch);
674 if (stretch < 0) forceMag = 0; // one-way spring, only pulls
675
676 float forceMagReduction = Math.min(Math.abs(forceMag), friction);
677 forceMag -= forceMagReduction;
678 friction -= forceMagReduction;
679
680
681 Vector2f force = new Vector2f(dirToDest);
682 force.scale(forceMag * Math.signum(stretch));
683
684 Vector2f acc = new Vector2f(force);
685 acc.scale(amount);
686 Vector2f.add(p.vel, acc, p.vel);
687
688 // leftover friction - apply against current velocity
689 if (friction > 0) {
690 float relSpeed = Vector2f.sub(aVel, p.vel, new Vector2f()).length();
691 if (relSpeed > params.minSpeedForFriction) {
692 Vector2f frictionDec = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(p.vel));
693 frictionDec.negate();
694 frictionDec.scale(Math.min(friction, p.vel.length()) * amount);
695 Vector2f.add(p.vel, frictionDec, p.vel);
696 }
697 }
698
699 // lateral friction to damp out any orbiting behavior fast
700 float lateralSpeed = Math.abs(Vector2f.dot(p.vel, perp));
701 if (lateralSpeed > 0) {// && lateralSpeed > params.minSpeedForFriction) {
702 Vector2f frictionDec = new Vector2f(perp);
703 if (Vector2f.dot(frictionDec, p.vel) > 0) {
704 frictionDec.negate();
705 }
706 float lateralFactor = params.lateralFrictionFactor;
707 lateralFactor += Math.min(Math.abs(attachedTo.getAngularVelocity()), 100f) * params.lateralFrictionTurnRateFactor;
708 float lateralFriction = lateralSpeed * lateralFactor;
709 frictionDec.scale(Math.min(lateralFriction, p.vel.length()) * amount);
710 Vector2f.add(p.vel, frictionDec, p.vel);
711 }
712
713
714
715 float speed = p.vel.length();
716 if (speed > maxSpeed) {
717 p.vel.scale(maxSpeed / speed);
718 }
719
720 }
721
722 p.advance(amount, params);
723 //p.loc.set(dest);
724
725 if (despawnAll) {
726 if (!p.fader.isFadingOut() && !p.fader.isFadedOut()) {
727 //p.fader.setDurationOut(2f + (float) Math.random() * 1f);
728 p.fader.setDurationOut(params.minDespawnTime +
729 (params.maxDespawnTime - params.minDespawnTime) * (float) Math.random());
730 p.fader.fadeOut();
731 }
732 }
733
734
735 if (p.fader.isFadedOut()) {
736 remove.add(p);
737 }
738 }
739
740 maxDistFromCenterToFragment = (float) Math.sqrt(maxDistSq);
741
742 members.removeAll(remove);
743
744 if (despawnAll) {
745 if (!despawning) {
746 if (params.despawnSound != null) {
747 Global.getSoundPlayer().playSound(params.despawnSound, 1f, 1f, entity.getLocation(), aVel);
748 despawning = true;
749 }
750 }
751 }
752
753 if (isExpired()) {
754// getShipMap().remove(attachedTo);
755// getFlockingMap().remove(params.flockingClass, this);
756// getExchangeMap().remove(params.memberExchangeClass, this);
757 } else if (!despawnAll && !despawning){
759 }
760
761
762 if (!despawnAll) {
763 respawnChecker.advance(amount * params.memberRespawnRate);
764 if (respawnChecker.intervalElapsed() && params.withRespawn) {
765 int num = getNumMembersToMaintain();
766 if (members.size() < num) {
767 int add = Math.min(params.numToRespawn, num - members.size());
768 addMembers(add);
769
770 if (params.offsetRerollFractionOnMemberRespawn > 0f) {
771 int reroll = Math.round(params.offsetRerollFractionOnMemberRespawn * members.size());
772 if (reroll < 1) reroll = 1;
773 WeightedRandomPicker<SwarmMember> picker = getPicker(true, false);
774 for (int i = 0; i < reroll; i++) {
775 SwarmMember pick = picker.pickAndRemove();
776 if (pick == null) break;
777 pick.rollOffset(params, attachedTo);
778 }
779 }
780
781 } else if (members.size() > num && params.removeMembersAboveMaintainLevel) {
783 } else if (params.maxNumMembersToAlwaysRemoveAbove >= 0 &&
784 members.size() > params.maxNumMembersToAlwaysRemoveAbove) {
785 int extra = members.size() - params.maxNumMembersToAlwaysRemoveAbove;
786 int numRemove = (int) Math.min(extra * 0.1f, 5f);
787 if (numRemove < 1) numRemove = 1;
788 despawnMembers(numRemove);
789 }
790 }
791
792
793 flashChecker.advance(amount * params.flashFrequency);
794 params.preFlashDelay -= amount;
795 if (params.preFlashDelay < 0) params.preFlashDelay = 0;
796 if (flashChecker.intervalElapsed() && params.preFlashDelay <= 0) {
797 if (params.flashProbability > 0) {
799 for (SwarmMember p : members) {
800 if (p.flash == null) {
801 notFlashing.add(p);
802 }
803 }
804 for (int i = 0; i < params.numToFlash; i++) {
805 if ((float) Math.random() < params.flashProbability) {
806 SwarmMember pick = notFlashing.pickAndRemove();
807 if (pick != null) pick.flash();
808 }
809 }
810 }
811 }
812 }
813
814 sinceExchange += amount;
815// if (proj.didDamage()) {
816// if (!resetTrailSpeed) {
817// for (ParticleData p : particles) {
818// Vector2f.add(p.vel, projVel, p.vel);
819// }
820// projVel.scale(0f);
821// resetTrailSpeed = true;
822// }
823// for (ParticleData p : particles) {
824// float dist = p.offset.length();
825// p.vel.scale(Math.min(1f, dist / 100f));
826// }
827// }
828 }
829
830 public void exchangeWithNearbySwarms(float amount) {
831 if (params.memberExchangeClass == null || params.memberExchangeRange <= 0) return;
832
833 transferChecker.advance(amount * params.memberExchangeRate);
834 if (!transferChecker.intervalElapsed()) return;
835
836
838
839 for (RoilingSwarmEffect other : getExchangeMap().getList(params.memberExchangeClass)) {
840 if (other == this || other.getEntity() == null || other.despawning) continue;
841 if (other.attachedTo == null || attachedTo == null) continue;
842 if (other.attachedTo.getOwner() != attachedTo.getOwner()) continue;
843
844 if (other.params.memberExchangeClass == null ||
845 !other.params.memberExchangeClass.equals(params.memberExchangeClass)) {
846 continue;
847 }
848 float dist = Misc.getDistance(entity.getLocation(), other.getEntity().getLocation());
849 if (dist > params.memberExchangeRange) continue;
850
851 swarmPicker.add(other);
852 }
853
854 RoilingSwarmEffect other = swarmPicker.pick();
855 if (other == null) return;
856
857 int num = params.minMembersToExchange +
858 Misc.random.nextInt(params.maxMembersToExchange - params.minMembersToExchange + 1);
859
860 WeightedRandomPicker<SwarmMember> picker = getPicker(true, true);
861 WeightedRandomPicker<SwarmMember> pickerOther = other.getPicker(true, true);
862
863 num = Math.min(num, picker.getItems().size());
864 num = Math.min(num, pickerOther.getItems().size());
865
866 for (int i = 0; i < num; i++) {
867 SwarmMember pick = picker.pickAndRemove();
868 SwarmMember otherPick = pickerOther.pickAndRemove();
869 if (pick == null || otherPick == null) break;
870
872 other.addMember(pick);
873 pick.rollOffset(other.params, other.attachedTo);
874
875 other.removeMember(otherPick);
876 addMember(otherPick);
877 otherPick.rollOffset(params, attachedTo);
878
879 sinceExchange = 0f;
880 }
881
882 }
883
884
885 public boolean shouldDespawnAll() {
886 if (forceDespawn) return true;
887
888// if ((float) Math.random() > 0.9995f && !params.generateOffsetAroundAttachedEntityOval) {
889// forceDespawn = true;
890// return true;
891// }
892
893 if (attachedTo instanceof ShipAPI) {
894 ShipAPI ship = (ShipAPI) attachedTo;
895 return !Global.getCombatEngine().isShipAlive(ship);
896 }
897 if (attachedTo instanceof MissileAPI) {
898 MissileAPI missile = (MissileAPI) attachedTo;
899 return !Global.getCombatEngine().isMissileAlive(missile);
900 }
901
903 }
904
905 public boolean isExpired() {
906 boolean expired = shouldDespawnAll() && members.isEmpty();
907 if (expired) {
908 //getFlockingMap().getList(FragmentSwarmHullmod.STANDARD_SWARM_FLOCKING_CLASS).get(0)
909 getShipMap().remove(attachedTo);
910 getFlockingMap().remove(params.flockingClass, this);
911 getExchangeMap().remove(params.memberExchangeClass, this);
912 }
913 return expired;
914 }
915
917 //if (true) return;
918
919 //Color color = Color.white;
920 Color color = params.color;
921 float alphaMult = viewport.getAlphaMult();
922 if (alphaMult <= 0f) return;
923
924 //alphaMult = 0.1f;
925 alphaMult *= params.alphaMult;
926
928// float zoom = viewport.getViewMult();
929// //System.out.println("Zoom: " + zoom);
930// if (zoom >= 3f) {
931// GL11.glDisable(GL11.GL_TEXTURE_2D);
932// if (!members.isEmpty()) {
933// Color c = members.get(0).sprite.getAverageBrightColor();
934// c = Misc.interpolateColor(c, members.get(0).sprite.getAverageColor(), 0.9f);
935// //Misc.setColor(c, alphaMult);
936// GL11.glEnable(GL11.GL_POINT_SMOOTH);
937// GL11.glPointSize(params.baseSpriteSize / zoom * 0.5f);
938// GL11.glBegin(GL11.GL_POINTS);
939// for (SwarmMember p : members) {
942//
943// float b = p.fader.getBrightness();
944// Misc.setColor(c, alphaMult * b);
945// GL11.glVertex2f(p.loc.x, p.loc.y);
946// }
947// GL11.glEnd();
948// }
949// } else {
950 if (!members.isEmpty()) {
951 members.get(0).sprite.bindTexture();
952 }
953 for (SwarmMember p : members) {
954 float size = params.baseSpriteSize;
955 size *= p.scale * p.fader.getBrightness();
956
957 float b = p.fader.getBrightness();
958 //b *= 0.67f;
959 //b *= 0.5f;
960 //b *= 0.1f;
961
962 p.sprite.setAngle(p.angle);
963 p.sprite.setSize(size, size);
964 p.sprite.setAlphaMult(alphaMult * b * params.alphaMultBase);
965 p.sprite.setColor(color);
966 p.sprite.renderAtCenterNoBind(p.loc.x, p.loc.y);
967
968 float glow = getGlowForMember(p);
969 if (glow > 0 && params.flashCoreRadiusMult <= 0f) {
970 p.sprite.setAlphaMult(alphaMult * b * glow * params.alphaMultFlash);
971 p.sprite.setColor(params.flashCoreColor);
972 p.sprite.setAdditiveBlend();
973 //p.sprite.setNormalBlend();
974 p.sprite.renderAtCenter(p.loc.x, p.loc.y);
975 p.sprite.setNormalBlend();
976 }
977 }
978// }
979 }
980
981 if ((layer == CombatEngineLayers.ABOVE_PARTICLES_LOWER && !params.renderFlashOnSameLayer) ||
982 (layer == CombatEngineLayers.FIGHTERS_LAYER && params.renderFlashOnSameLayer)) {
983 SpriteAPI glowSprite = Global.getSettings().getSprite("misc", "threat_swarm_glow");
984 glowSprite.setAdditiveBlend();
985 for (SwarmMember p : members) {
986 float glow = getGlowForMember(p);
987 if (glow > 0f) {
988 float size = params.flashRadius * (0.5f + 0.5f * glow) * 2f;
989 size *= p.scale * p.fader.getBrightness();
990
991// float f = p.offset.length() / 150f;
992// if (f > 1f) f = 1f;
993// //f = 1 - Math.min(f * 2f, 1f);
994// f = 1 - f * 0.5f;
995// //f = 0f;
996// Color color2 = Misc.interpolateColor(params.flashFringeColor, Misc.setBrightness(
997// new Color(7, 163, 169), 255), f);
998
999 float b = p.fader.getBrightness();
1000 if (b > 0 && size > 0) {
1001 glowSprite.setSize(size, size);
1002 glowSprite.setAlphaMult(alphaMult * b * glow * 0.5f * params.alphaMultFlash);
1003 glowSprite.setColor(params.flashFringeColor);
1004 glowSprite.renderAtCenter(p.loc.x, p.loc.y);
1005 }
1006
1007 float memberSize = params.baseSpriteSize;
1008 memberSize *= p.scale;
1009 memberSize *= 2f;
1010 memberSize *= params.flashCoreRadiusMult;
1011 if (b > 0 && memberSize > 0) {
1012 glowSprite.setSize(memberSize, memberSize);
1013 glowSprite.setAlphaMult(alphaMult * b * p.fader.getBrightness() * glow * 0.5f * params.alphaMultFlash);
1014 glowSprite.setColor(params.flashCoreColor);
1015 glowSprite.renderAtCenter(p.loc.x, p.loc.y);
1016 }
1017 }
1018 }
1019 }
1020 }
1021
1022 public RoilingSwarmParams getParams() {
1023 return params;
1024 }
1025 public List<SwarmMember> getMembers() {
1026 return members;
1027 }
1029 return attachedTo;
1030 }
1031 public boolean isDespawning() {
1032 return despawning;
1033 }
1034 public boolean isForceDespawn() {
1035 return forceDespawn;
1036 }
1037 public void setForceDespawn(boolean forceDespawn) {
1038 this.forceDespawn = forceDespawn;
1039 }
1040
1041
1042
1043}
1044
1045
1046
1047
static SettingsAPI getSettings()
Definition Global.java:57
static SoundPlayerAPI getSoundPlayer()
Definition Global.java:49
static CombatEngineAPI getCombatEngine()
Definition Global.java:69
WeightedRandomPicker< SwarmMember > getPicker(boolean preferNonFlashing, boolean preferNonPicked, Vector2f point, float preferMaxRangeFromPoint)
void render(CombatEngineLayers layer, ViewportAPI viewport)
static ListMap< RoilingSwarmEffect > getStringToSwarmMap(String key)
static LinkedHashMap< CombatEntityAPI, RoilingSwarmEffect > getShipMap()
void transferMembersTo(RoilingSwarmEffect other, float fraction)
RoilingSwarmEffect(CombatEntityAPI attachedTo, RoilingSwarmParams params)
WeightedRandomPicker< SwarmMember > getPicker(boolean preferNonFlashing, boolean preferNonPicked)
WeightedRandomPicker< SwarmMember > getPicker(boolean preferNonFlashing, boolean preferNonPicked, Vector2f towards)
static RoilingSwarmEffect getSwarmFor(CombatEntityAPI entity)
void transferMembersTo(RoilingSwarmEffect other, int num, Vector2f point, float maxRangeFromPoint)
void setBounceDown(boolean bounceDown)
void setBounce(boolean up, boolean down)
static Vector2f getUnitVectorAtDegreeAngle(float degrees)
Definition Misc.java:1196
static Vector2f rotateAroundOrigin(Vector2f v, float angle)
Definition Misc.java:1205
static float getAngleDiff(float from, float to)
Definition Misc.java:1716
static float getDistance(SectorEntityToken from, SectorEntityToken to)
Definition Misc.java:599
static Vector2f getUnitVector(Vector2f from, Vector2f to)
Definition Misc.java:1191
static float normalizeAngle(float angleDeg)
Definition Misc.java:1142
static float getTargetingRadius(Vector2f from, CombatEntityAPI target, boolean considerShield)
Definition Misc.java:1349
static Vector2f getPointWithinRadius(Vector2f from, float r)
Definition Misc.java:711
static Vector2f getPointWithinRadiusUniform(Vector2f from, float r, Random random)
Definition Misc.java:722
static float getAngleInDegrees(Vector2f v)
Definition Misc.java:1126
static Vector2f normalise(Vector2f v)
Definition Misc.java:1134
SpriteAPI getSprite(String filename)
SoundAPI playSound(String id, float pitch, float volume, Vector2f loc, Vector2f vel)
CombatEntityAPI addLayeredRenderingPlugin(CombatLayeredRenderingPlugin plugin)
boolean isEntityInPlay(CombatEntityAPI entity)
boolean isMissileAlive(MissileAPI missile)
void setSize(float width, float height)
void renderAtCenter(float x, float y)