1package com.fs.starfarer.api.impl.combat.threat;
3import java.util.ArrayList;
4import java.util.EnumSet;
5import java.util.LinkedHashMap;
6import java.util.LinkedHashSet;
12import org.lwjgl.util.vector.Vector2f;
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;
30 public static interface SwarmMemberOffsetModifier {
31 void modifyOffset(SwarmMember p);
34 public static class RoilingSwarmParams {
35 public String spriteCat =
"misc";
36 public String spriteKey =
"threat_swarm_pieces";
37 public String despawnSound =
"threat_swarm_destroyed";
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;
49 public String flockingClass =
null;
51 public float baseSpriteSize = 20f;
53 public float baseDur = 100000f;
54 public float durRange = 0f;
55 public float despawnDist = 0f;
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;
66 public float offsetRotationDegreesPerSecond = 0f;
68 public float lateralFrictionFactor = 20f;
69 public float lateralFrictionTurnRateFactor = 0;
73 public float minSpeedForFriction = 25f;
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;
87 public float alphaMult = 1f;
88 public float alphaMultBase = 1f;
89 public float alphaMultFlash = 1f;
90 public Color color = Color.white;
92 public float minFadeoutTime = 1f;
93 public float maxFadeoutTime = 1f;
94 public float minDespawnTime = 2f;
95 public float maxDespawnTime = 1f;
97 public boolean autoscale =
false;
102 public float springStretchMult = 10f;
104 public float swarmLeadsByFractionOfVelocity = 0.03f;
106 public float outspeedAttachedEntityBy = 100f;
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;
117 public SwarmMemberOffsetModifier offsetModifier =
null;
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;
128 public Set<String> tags =
new LinkedHashSet<>();
130 public boolean keepProxBasedScaleForAllMembers =
false;
135 public static class SwarmMember {
138 public Vector2f offset =
new Vector2f();
139 public Vector2f loc =
new Vector2f();
140 public Vector2f vel =
new Vector2f();
142 public float scale = 1f;
143 public float turnRate = 1f;
144 public float angle = 1f;
145 public float recentlyPicked = 0f;
153 public float minScale;
154 public boolean keepScale =
false;
173 angle = (float) Math.random() * 360f;
178 Vector2f spawnOffset =
new Vector2f(offset);
179 spawnOffset.scale(
params.spawnOffsetMult);
180 if (
params.spawnOffsetMult != 0) {
184 Vector2f.add(startingLoc, spawnOffset, loc);
192 dur = params.baseDur + (float) Math.random() *
params.durRange;
195 turnRate = Math.signum((
float) Math.random() - 0.5f) * params.maxTurnRate * (float) Math.random();
197 fader =
new FaderUtil(0f, 0.5f + (
float) Math.random() * 0.5f,
198 params.minFadeoutTime + (
params.maxFadeoutTime -
params.minFadeoutTime) * (
float) Math.random());
201 scaler =
new FaderUtil(0f, 0.5f + (
float) Math.random() * 0.5f, 0.5f + (
float) Math.random() * 0.5f);
211 float angle = (float) Math.random() * 360f;
213 Vector2f from =
new Vector2f(dir);
218 float max = min +
params.maxOffset;
221 float f = min/(Math.max(1f, max));
222 f = Math.max(0.1f, f * 0.75f);
226 for (
int i = 0; i < 10; i++) {
227 float test = (float) Math.sqrt(Math.random());
234 r = f + (1f - f) * (
float) Math.random();
251 if (
params.offsetModifier !=
null) {
252 params.offsetModifier.modifyOffset(
this);
256 public void advance(
float amount, RoilingSwarmParams
params) {
257 loc.x += vel.x * amount;
258 loc.y += vel.y * amount;
260 angle += turnRate * amount;
265 recentlyPicked -= amount;
266 if (recentlyPicked < 0) recentlyPicked = 0f;
275 if (flash ==
null && flashNext !=
null) {
280 if (
params.autoscale && !keepScale) {
286 public void flash() {
294 public void flashNext() {
295 flashNext =
new FaderUtil(0f, 0.25f, 1f);
300 public void setRecentlyPicked(
float pickDuration) {
301 recentlyPicked = Math.max(recentlyPicked, pickDuration);
306 if (
entity ==
null)
return null;
314 @SuppressWarnings(
"unchecked")
316 LinkedHashMap<CombatEntityAPI, RoilingSwarmEffect> map =
319 map =
new LinkedHashMap<>();
330 @SuppressWarnings(
"unchecked")
342 protected List<SwarmMember>
members =
new ArrayList<SwarmMember>();
397 if (
params.flockingClass !=
null) {
400 if (
params.memberExchangeClass !=
null) {
415 return params.visibleRange + extra;
438 for (
int i = 0; i < num; i++) {
443 int num = (int) (
members.size() * fraction);
450 if (num <= 0)
return;
453 picker =
getPicker(
true,
true, point, maxRangeFromPoint);
455 for (
int i = 0; i < num; i++) {
457 if (
pick ==
null)
break;
470 if (!allowFirst && !
members.isEmpty()) {
473 for (
int i = 0; i < num; i++) {
475 if (
pick ==
null)
break;
480 public SwarmMember
pick(
float pickDuration) {
483 pick.setRecentlyPicked(pickDuration);
492 for (SwarmMember p :
members) {
493 if (p.fader.isFadingOut() || p.fader.isFadedOut())
continue;
495 if (preferNonFlashing && p.flash !=
null) w *= 0.001f;
496 if (preferNonPicked && p.recentlyPicked > 0) w *= 0.001f;
502 if (f > 0.9999f) f = 0.9999f;
512 Vector2f point,
float preferMaxRangeFromPoint) {
514 for (SwarmMember p :
members) {
515 if (p.fader.isFadingOut() || p.fader.isFadedOut())
continue;
517 if (preferNonFlashing && p.flash !=
null) w *= 0.001f;
518 if (preferNonPicked && p.recentlyPicked > 0) w *= 0.001f;
521 if (dist > preferMaxRangeFromPoint) {
522 float f = (dist - preferMaxRangeFromPoint) / Math.max(1f, preferMaxRangeFromPoint);
523 if (f > 0.9999f) f = 0.9999f;
526 w *= 0.25f + 0.75f * (1f - dist / Math.max(1f, preferMaxRangeFromPoint));
535 for (SwarmMember p :
members) {
536 if (p.fader.isFadingOut() || p.fader.isFadedOut())
continue;
538 if (preferNonFlashing && p.flash !=
null) w *= 0.001f;
539 if (preferNonPicked && p.recentlyPicked > 0) w *= 0.001f;
546 return getPicker(
false,
false).getItems().size();
551 if (p.flash !=
null) {
559 return params.baseMembersToMaintain;
568 float origSpawnOffsetMult =
params.spawnOffsetMult;
569 if (
params.spawnOffsetMultForInitialSpawn >= 0) {
570 params.spawnOffsetMult =
params.spawnOffsetMultForInitialSpawn;
573 params.spawnOffsetMult = origSpawnOffsetMult;
587 float aSpeed = aVel.length();
588 float leadAmount = aSpeed *
params.swarmLeadsByFractionOfVelocity;
601 List<SwarmMember>
remove =
new ArrayList<>();
603 float maxSpeed =
params.maxSpeed;
604 if (
params.outspeedAttachedEntityBy != 0) {
606 if (minMaxSpeed > maxSpeed) maxSpeed = minMaxSpeed;
612 float maxOffsetForProx =
params.maxOffset;
613 if (
params.generateOffsetAroundAttachedEntityOval) {
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);
634 Vector2f offset =
new Vector2f(p.offset);
639 float prox = offset.length() / maxOffsetForProx;
645 offset.x += facingDir.x * leadAmount;
646 offset.y += facingDir.y * leadAmount;
648 if (!
params.keepProxBasedScaleForAllMembers) {
649 p.scale = params.baseScale + (1f - prox) *
params.scaleRange;
650 if (p.scale > 1f) p.scale = 1f;
653 Vector2f dest =
new Vector2f(aLoc);
654 Vector2f.add(dest, offset, dest);
658 Vector2f perp =
new Vector2f(-dirToDest.y, dirToDest.x);
660 float friction = params.baseFriction + params.frictionRange * prox;
662 float k = params.baseSpringConstant - params.springConstantNegativeRange * prox;
663 float freeLength = params.baseSpringFreeLength + params.springFreeLengthRange * prox;
669 float stretch = dist - freeLength;
671 stretch = (float) (Math.sqrt(Math.abs(stretch *
params.springStretchMult)) * Math.signum(stretch));
673 float forceMag = k * Math.abs(stretch);
674 if (stretch < 0) forceMag = 0;
676 float forceMagReduction = Math.min(Math.abs(forceMag), friction);
677 forceMag -= forceMagReduction;
678 friction -= forceMagReduction;
681 Vector2f force =
new Vector2f(dirToDest);
682 force.scale(forceMag * Math.signum(stretch));
684 Vector2f acc =
new Vector2f(force);
686 Vector2f.add(p.vel, acc, p.vel);
690 float relSpeed = Vector2f.sub(aVel, p.vel,
new Vector2f()).length();
691 if (relSpeed >
params.minSpeedForFriction) {
693 frictionDec.negate();
694 frictionDec.scale(Math.min(friction, p.vel.length()) * amount);
695 Vector2f.add(p.vel, frictionDec, p.vel);
700 float lateralSpeed = Math.abs(Vector2f.dot(p.vel, perp));
701 if (lateralSpeed > 0) {
702 Vector2f frictionDec =
new Vector2f(perp);
703 if (Vector2f.dot(frictionDec, p.vel) > 0) {
704 frictionDec.negate();
706 float lateralFactor =
params.lateralFrictionFactor;
708 float lateralFriction = lateralSpeed * lateralFactor;
709 frictionDec.scale(Math.min(lateralFriction, p.vel.length()) * amount);
710 Vector2f.add(p.vel, frictionDec, p.vel);
715 float speed = p.vel.length();
716 if (speed > maxSpeed) {
717 p.vel.scale(maxSpeed / speed);
722 p.advance(amount,
params);
726 if (!p.fader.isFadingOut() && !p.fader.isFadedOut()) {
728 p.fader.setDurationOut(
params.minDespawnTime +
729 (
params.maxDespawnTime -
params.minDespawnTime) * (
float) Math.random());
735 if (p.fader.isFadedOut()) {
746 if (
params.despawnSound !=
null) {
767 int add = Math.min(
params.numToRespawn, num -
members.size());
770 if (
params.offsetRerollFractionOnMemberRespawn > 0f) {
771 int reroll = Math.round(
params.offsetRerollFractionOnMemberRespawn *
members.size());
772 if (reroll < 1) reroll = 1;
774 for (
int i = 0; i < reroll; i++) {
776 if (
pick ==
null)
break;
781 }
else if (
members.size() > num &&
params.removeMembersAboveMaintainLevel) {
783 }
else if (
params.maxNumMembersToAlwaysRemoveAbove >= 0 &&
785 int extra =
members.size() -
params.maxNumMembersToAlwaysRemoveAbove;
786 int numRemove = (int) Math.min(extra * 0.1f, 5f);
787 if (numRemove < 1) numRemove = 1;
794 params.preFlashDelay -= amount;
795 if (
params.preFlashDelay < 0) params.preFlashDelay = 0;
797 if (
params.flashProbability > 0) {
799 for (SwarmMember p :
members) {
800 if (p.flash ==
null) {
804 for (
int i = 0; i <
params.numToFlash; i++) {
805 if ((
float) Math.random() <
params.flashProbability) {
831 if (
params.memberExchangeClass ==
null ||
params.memberExchangeRange <= 0)
return;
840 if (other ==
this || other.getEntity() ==
null || other.despawning)
continue;
841 if (other.attachedTo ==
null ||
attachedTo ==
null)
continue;
844 if (other.params.memberExchangeClass ==
null ||
845 !other.params.memberExchangeClass.equals(
params.memberExchangeClass)) {
849 if (dist >
params.memberExchangeRange)
continue;
851 swarmPicker.
add(other);
855 if (other ==
null)
return;
857 int num = params.minMembersToExchange +
863 num = Math.min(num, picker.
getItems().size());
864 num = Math.min(num, pickerOther.
getItems().size());
866 for (
int i = 0; i < num; i++) {
869 if (
pick ==
null || otherPick ==
null)
break;
920 Color color =
params.color;
922 if (alphaMult <= 0f)
return;
925 alphaMult *=
params.alphaMult;
951 members.get(0).sprite.bindTexture();
953 for (SwarmMember p :
members) {
954 float size =
params.baseSpriteSize;
955 size *= p.scale * p.fader.getBrightness();
957 float b = p.fader.getBrightness();
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);
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();
974 p.sprite.renderAtCenter(p.loc.x, p.loc.y);
975 p.sprite.setNormalBlend();
985 for (SwarmMember p :
members) {
988 float size = params.flashRadius * (0.5f + 0.5f * glow) * 2f;
989 size *= p.scale * p.fader.getBrightness();
999 float b = p.fader.getBrightness();
1000 if (b > 0 && size > 0) {
1001 glowSprite.
setSize(size, size);
1007 float memberSize =
params.baseSpriteSize;
1008 memberSize *= p.scale;
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);
static SettingsAPI getSettings()
static SoundPlayerAPI getSoundPlayer()
static CombatEngineAPI getCombatEngine()
WeightedRandomPicker< SwarmMember > getPicker(boolean preferNonFlashing, boolean preferNonPicked, Vector2f point, float preferMaxRangeFromPoint)
void despawnMembers(int num, boolean allowFirst)
void render(CombatEngineLayers layer, ViewportAPI viewport)
int getNumActiveMembers()
void init(CombatEntityAPI entity)
void removeMember(SwarmMember sm)
IntervalUtil respawnChecker
static ListMap< RoilingSwarmEffect > getExchangeMap()
void advance(float amount)
boolean shouldDespawnAll()
RoilingSwarmParams params
static ListMap< RoilingSwarmEffect > getStringToSwarmMap(String key)
CombatEntityAPI attachedTo
float getGlowForMember(SwarmMember p)
static LinkedHashMap< CombatEntityAPI, RoilingSwarmEffect > getShipMap()
void transferMembersTo(RoilingSwarmEffect other, int num)
static String KEY_EXCHANGE_MAP
void despawnMembers(int num)
void transferMembersTo(RoilingSwarmEffect other, float fraction)
RoilingSwarmEffect(CombatEntityAPI attachedTo, RoilingSwarmParams params)
RoilingSwarmParams getParams()
float maxDistFromCenterToFragment
EnumSet< CombatEngineLayers > getActiveLayers()
static String KEY_FLOCKING_MAP
WeightedRandomPicker< SwarmMember > getPicker(boolean preferNonFlashing, boolean preferNonPicked)
void exchangeWithNearbySwarms(float amount)
List< SwarmMember > getMembers()
void setForceDespawn(boolean forceDespawn)
List< SwarmMember > members
WeightedRandomPicker< SwarmMember > getPicker(boolean preferNonFlashing, boolean preferNonPicked, Vector2f towards)
void addMember(SwarmMember sm)
RoilingSwarmEffect(CombatEntityAPI attachedTo)
int getNumMembersToMaintain()
IntervalUtil flashChecker
static String KEY_SHIP_MAP
static RoilingSwarmEffect getSwarmFor(CombatEntityAPI entity)
CombatEntityAPI getAttachedTo()
EnumSet< CombatEngineLayers > layers
IntervalUtil transferChecker
static ListMap< RoilingSwarmEffect > getFlockingMap()
SwarmMember pick(float pickDuration)
void transferMembersTo(RoilingSwarmEffect other, int num, Vector2f point, float maxRangeFromPoint)
void advance(float amount)
void setBounceDown(boolean bounceDown)
void setBounce(boolean up, boolean down)
void advance(float amount)
static Vector2f getUnitVectorAtDegreeAngle(float degrees)
static Vector2f rotateAroundOrigin(Vector2f v, float angle)
static float getAngleDiff(float from, float to)
static float getDistance(SectorEntityToken from, SectorEntityToken to)
static Vector2f getUnitVector(Vector2f from, Vector2f to)
static float normalizeAngle(float angleDeg)
static float getTargetingRadius(Vector2f from, CombatEntityAPI target, boolean considerShield)
static Vector2f getPointWithinRadius(Vector2f from, float r)
static Vector2f getPointWithinRadiusUniform(Vector2f from, float r, Random random)
static float getAngleInDegrees(Vector2f v)
static Vector2f normalise(Vector2f v)
SpriteAPI getSprite(String filename)
SoundAPI playSound(String id, float pitch, float volume, Vector2f loc, Vector2f vel)
boolean isShipAlive(ShipAPI ship)
CombatEntityAPI addLayeredRenderingPlugin(CombatLayeredRenderingPlugin plugin)
Map< String, Object > getCustomData()
boolean isEntityInPlay(CombatEntityAPI entity)
boolean isMissileAlive(MissileAPI missile)
float getCollisionRadius()
float getAngularVelocity()
void setSize(float width, float height)
void setTexWidth(float texWidth)
void renderAtCenter(float x, float y)
void setColor(Color color)
void setAlphaMult(float alphaMult)
void setTexHeight(float texHeight)