Starsector API
Loading...
Searching...
No Matches
FragmentSwarmHullmod.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.combat.threat;
2
3import java.util.ArrayList;
4import java.util.List;
5
6import java.awt.Color;
7
8import com.fs.starfarer.api.GameState;
9import com.fs.starfarer.api.Global;
10import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType;
11import com.fs.starfarer.api.campaign.CargoStackAPI;
12import com.fs.starfarer.api.campaign.SpecialItemData;
13import com.fs.starfarer.api.combat.BaseHullMod;
14import com.fs.starfarer.api.combat.MutableShipStatsAPI;
15import com.fs.starfarer.api.combat.ShipAPI;
16import com.fs.starfarer.api.combat.ShipAPI.HullSize;
17import com.fs.starfarer.api.combat.WeaponAPI;
18import com.fs.starfarer.api.impl.campaign.ids.Items;
19import com.fs.starfarer.api.impl.campaign.ids.Stats;
20import com.fs.starfarer.api.impl.campaign.ids.Tags;
21import com.fs.starfarer.api.impl.combat.threat.RoilingSwarmEffect.RoilingSwarmParams;
22import com.fs.starfarer.api.loading.HullModSpecAPI;
23import com.fs.starfarer.api.util.ColorShifterUtil;
24import com.fs.starfarer.api.util.Misc;
25
32public class FragmentSwarmHullmod extends BaseHullMod {
33
34 public static String STANDARD_SWARM_EXCHANGE_CLASS = "standard_swarm_exchange_class";
35 public static String STANDARD_SWARM_FLOCKING_CLASS = "standard_swarm_flocking_class";
36 public static String CONSTRUCTION_SWARM_FLOCKING_CLASS = "construction_swarm_flocking_class";
37 public static String RECLAMATION_SWARM_FLOCKING_CLASS = "reclamation_swarm_flocking_class";
38 public static String RECLAMATION_SWARM_EXCHANGE_CLASS = "reclamation_swarm_exchange_class";
39
40 public static boolean SHOW_OVERLAY_ON_THREAT_SHIPS = false;
41
42 public static float SMOD_CR_PENALTY = 0.2f;
43 public static float SMOD_MAINTENANCE_PENALTY = 50f;
44
45 public static Object STATUS_KEY1 = new Object();
46
47
48 @Override
49 public void applyEffectsBeforeShipCreation(HullSize hullSize, MutableShipStatsAPI stats, String id) {
50 boolean sMod = isSMod(stats);
51 if (sMod) {
52 stats.getMaxCombatReadiness().modifyFlat(id, -Math.round(SMOD_CR_PENALTY * 100f) * 0.01f, "Fragment swarm");
54 }
55 }
56
57 @Override
58 public void applyEffectsAfterShipCreation(ShipAPI ship, String id) {
60 ship.setExtraOverlay(Global.getSettings().getSpriteName("misc", "fragment_swarm"));
63 }
64 }
65
66 @Override
67 public void advanceInCombat(ShipAPI ship, float amount) {
68 if (amount <= 0f || ship == null) return;
69
71 if (swarm == null) {
72 swarm = createSwarmFor(ship);
73 }
74
75 if (ship.isFighter()) return;
76
77 boolean playerShip = Global.getCurrentState() == GameState.COMBAT &&
79
80
81 RoilingSwarmParams params = swarm.params;
82 params.baseMembersToMaintain = (int) ship.getMutableStats().getDynamic().getValue(
84 params.memberRespawnRate = getBaseSwarmRespawnRateMult(ship.getHullSize()) *
86
87// if (ship.getHullSpec().getHullId().equals(ThreatHullmod.HIVE_UNIT)) {
88// params.baseMembersToMaintain = SwarmLauncherEffect.FRAGMENT_NUM.get(SwarmLauncherEffect.ATTACK_SWARM_WING);
89// params.baseMembersToMaintain *= 8;
90// params.memberRespawnRate = 15 * ship.getMutableStats().getDynamic().getValue(Stats.FRAGMENT_SWARM_RESPAWN_RATE_MULT);
91// }
92
93 params.maxNumMembersToAlwaysRemoveAbove = (int) (params.baseMembersToMaintain * 1.5f);
94 params.initialMembers = params.baseMembersToMaintain;
95
96
97 if (playerShip) {
98 int active = swarm.getNumActiveMembers();
99
100 int maxRequired = 0;
101 for (WeaponAPI w : ship.getAllWeapons()) {
102 if (w.getEffectPlugin() instanceof FragmentWeapon) {
103 FragmentWeapon fw = (FragmentWeapon) w.getEffectPlugin();
104 maxRequired = Math.max(maxRequired, fw.getNumFragmentsToFire());
105 }
106 }
107
108 boolean debuff = active < maxRequired;
110 Global.getSettings().getSpriteName("ui", "icon_tactical_fragment_swarm"),
112 "FRAGMENTS: " + active,
113 debuff);
114 }
115 }
116
117
120 if (existing != null) return existing;
121
122// if (true) {
123// return SwarmLauncherEffect.createTestDwellerSwarmFor(ship);
124// }
125
126 RoilingSwarmParams params = new RoilingSwarmParams();
127 if (ship.isFighter()) {
128 float radius = 20f;
129 int numMembers = 50;
130
131 String wingId = ship.getWing() == null ? null : ship.getWing().getWingId();
132 if (SwarmLauncherEffect.SWARM_RADIUS.containsKey(wingId)) {
133 radius = SwarmLauncherEffect.SWARM_RADIUS.get(wingId);
134 }
135 if (SwarmLauncherEffect.FRAGMENT_NUM.containsKey(wingId)) {
136 numMembers = SwarmLauncherEffect.FRAGMENT_NUM.get(wingId);
137 }
138
139 params.memberExchangeClass = STANDARD_SWARM_EXCHANGE_CLASS;
141 params.maxSpeed = ship.getMaxSpeedWithoutBoost() +
142 Math.max(ship.getMaxSpeedWithoutBoost() * 0.25f + 50f, 100f);
143
144 params.flashRateMult = 0.25f;
145 params.flashCoreRadiusMult = 0f;
146 params.flashRadius = 120f;
147 params.flashFringeColor = new Color(255,0,0,40);
148 params.flashCoreColor = new Color(255,255,255,127);
149
150 // if this is set to true and the swarm is glowing, missile-fragments pop over the glow and it looks bad
151 //params.renderFlashOnSameLayer = true;
152
153 params.maxOffset = radius;
154 params.initialMembers = numMembers;
155 params.baseMembersToMaintain = params.initialMembers;
156 } else {
157 params.memberExchangeClass = STANDARD_SWARM_EXCHANGE_CLASS;
158 params.maxSpeed = ship.getMaxSpeedWithoutBoost() +
159 Math.max(ship.getMaxSpeedWithoutBoost() * 0.25f + 50f, 100f) +
161
162 params.flashRateMult = 0.25f;
163 params.flashCoreRadiusMult = 0f;
164 params.flashRadius = 120f;
165 params.flashFringeColor = new Color(255,0,0,40);
166 params.flashCoreColor = new Color(255,255,255,127);
167
168 // if this is set to true and the swarm is glowing, missile-fragments pop over the glow and it looks bad
169 //params.renderFlashOnSameLayer = true;
170
171 params.minOffset = 0f;
172 params.maxOffset = Math.min(100f, ship.getCollisionRadius() * 0.5f);
173 params.generateOffsetAroundAttachedEntityOval = true;
174 params.despawnSound = null; // ship explosion does the job instead
175 params.spawnOffsetMult = 0.33f;
176 params.spawnOffsetMultForInitialSpawn = 1f;
177
178 params.baseMembersToMaintain = getBaseSwarmSize(ship.getHullSize());
179 params.memberRespawnRate = getBaseSwarmRespawnRateMult(ship.getHullSize());
180 params.maxNumMembersToAlwaysRemoveAbove = params.baseMembersToMaintain * 2;
181
182 //params.offsetRerollFractionOnMemberRespawn = 0.05f;
183
184 params.initialMembers = 0;
185 params.initialMembers = params.baseMembersToMaintain;
186 params.removeMembersAboveMaintainLevel = false;
187 }
188
189 List<WeaponAPI> glowWeapons = new ArrayList<>();
190 for (WeaponAPI w : ship.getAllWeapons()) {
191 if (w.usesAmmo() && w.getSpec().hasTag(Tags.FRAGMENT_GLOW)) {
192 glowWeapons.add(w);
193 }
194 if (w.getSpec().hasTag(Tags.OVERSEER_CHARGE) ||
195 (ship.isFighter() && w.getSpec().hasTag(Tags.OVERSEER_CHARGE_FIGHTER))) {
196 w.setAmmo(0);
197 }
198 }
199
200// if (ship.hasTag(Tags.FRAGMENT_SWARM_START_WITH_ZERO_FRAGMENTS)) {
201// params.initialMembers = 0;
202// }
203
204 return new RoilingSwarmEffect(ship, params) {
205 protected ColorShifterUtil glowColorShifter = new ColorShifterUtil(new Color(0, 0, 0, 0));
206 protected boolean resetFlash = false;
207
208 @Override
209 public int getNumMembersToMaintain() {
210 if (ship.isFighter()) {
211 return (int)Math.round(((0.2f + 0.8f * ship.getHullLevel()) * super.getNumMembersToMaintain()));
212 }
213 return super.getNumMembersToMaintain();
214 }
215
216 @Override
217 public void advance(float amount) {
218 super.advance(amount);
219
220 glowColorShifter.advance(amount);
221
222 // this is actually QUITE performance-intensive on the rendering, at least doubles the cost per swarm
223 // (comment was from when flashFrequency was *10 with a shorter flashRateMult; *2 is pretty ok -am
225 params.flashFrequency = 4f;
226 params.flashProbability = 1f;
227 resetFlash = true;
228 } else {
229 if (!glowWeapons.isEmpty()) {
230 float ammoFractionTotal = 0f;
231 float totalOP = 0f;
232 for (WeaponAPI w : glowWeapons) {
233 float f = w.getAmmo() / Math.max(1f, w.getMaxAmmo());
234 Color glowColor = w.getSpec().getGlowColor();
235 // if (f > 0) {
236 // glowColorShifter.shift(w, glowColor, 0.5f, 0.5f, 1f);
237 // }
238 glowColorShifter.shift(w, glowColor, 0.5f, 0.5f, 1f);
239 float weight = w.getSpec().getOrdnancePointCost(null);
240 ammoFractionTotal += f * weight;
241 totalOP += weight;
242 }
243
244 float ammoFraction = ammoFractionTotal / Math.max(1f, totalOP);
245 params.flashFrequency = (1f + ammoFraction) * 2f;
246 params.flashFrequency *= Math.max(1f, Math.min(2f, params.baseMembersToMaintain / 50f));
247 params.flashProbability = 1f;
248 if (ammoFraction <= 0f) {
249 params.flashProbability = 0f;
250 }
251 //params.flashFringeColor = new Color(255,0,0,(int)(30f + 30f * ammoFraction));
252 //float glowAlphaBase = 50f;
253 float glowAlphaBase = 30f;
254 if (ship.isFighter()) {
255 glowAlphaBase = 18f;
256 }
257
258 float extraGlow = (totalOP - 10f) / 90f;
259 if (extraGlow < 0) extraGlow = 0;
260 if (extraGlow > 1f) extraGlow = 1f;
261
262 int glowAlpha = (int)(glowAlphaBase + glowAlphaBase * (ammoFraction + extraGlow * 0.5f));
263 if (glowAlpha > 255) glowAlpha = 255;
264 //params.flashFringeColor = Misc.setAlpha(glowColorShifter.getCurr(), glowAlpha);
265 params.flashFringeColor = Misc.setBrightness(glowColorShifter.getCurr(), 255);
266 params.flashFringeColor = Misc.setAlpha(params.flashFringeColor, glowAlpha);
267
268 resetFlash = true;
269 } else {
270 //if (ThreatSwarmAI.isAttackSwarm(ship)) {
271 if (resetFlash) {
272 params.flashProbability = 0f;
273 resetFlash = false;
274 }
275 }
276 }
277
278// int flashing = 0;
279// for (SwarmMember p : members) {
280// if (p.flash != null) {
281// flashing++;
282// }
283// }
284// System.out.println("Flashing: " + flashing + ", total: " + members.size());
285 }
286
287 };
288 }
289
290
291
292
293 public static int getBaseSwarmSize(HullSize size) {
294 switch (size) {
295 case CAPITAL_SHIP: return 100;
296 case CRUISER: return 60;
297 case DESTROYER: return 40;
298 case FRIGATE: return 20;
299 case FIGHTER: return 50;
300 case DEFAULT: return 20;
301 default: return 20;
302 }
303 }
304
305 public static float getBaseSwarmRespawnRateMult(HullSize size) {
306 switch (size) {
307 case CAPITAL_SHIP: return 5f;
308 case CRUISER: return 3f;
309 case DESTROYER: return 2f;
310 case FRIGATE: return 1f;
311 case FIGHTER: return 0f;
312 case DEFAULT: return 0f;
313 default: return 0f;
314 }
315 }
316
317
318 @Override
320 return Global.getSettings().createCargoStack(CargoItemType.SPECIAL,
321 new SpecialItemData(Items.FRAGMENT_FABRICATOR, null), null);
322 }
323
324 public static boolean hasShroudedHullmods(ShipAPI ship) {
325 if (ship == null || ship.getVariant() == null) return false;
326 for (String id : ship.getVariant().getHullMods()) {
328 if (spec != null && spec.hasTag(Tags.SHROUDED)) return true;
329 }
330 return false;
331 }
332
333 public boolean isApplicableToShip(ShipAPI ship) {
334 if (ship != null && ship.getHullSpec().isPhase()) {
335 return false;
336 }
337 if (hasShroudedHullmods(ship)) return false;
338
339 return true;
340 }
341
342 public String getUnapplicableReason(ShipAPI ship) {
343 if (ship != null && ship.getHullSpec().isPhase()) {
344 return "Can not be installed on a phase ship";
345 }
346 return "Incompatible with Shrouded hullmods";
347 }
348
349 public String getDescriptionParam(int index, HullSize hullSize) {
350 if (index == 0) return "" + (int)getBaseSwarmSize(HullSize.FRIGATE);
351 if (index == 1) return "" + (int)getBaseSwarmSize(HullSize.DESTROYER);
352 if (index == 2) return "" + (int)getBaseSwarmSize(HullSize.CRUISER);
353 if (index == 3) return "" + (int)getBaseSwarmSize(HullSize.CAPITAL_SHIP);
354
355 if (index == 4) return "" + (int)getBaseSwarmRespawnRateMult(HullSize.FRIGATE);
356 if (index == 5) return "" + (int)getBaseSwarmRespawnRateMult(HullSize.DESTROYER);
357 if (index == 6) return "" + (int)getBaseSwarmRespawnRateMult(HullSize.CRUISER);
358 if (index == 7) return "" + (int)getBaseSwarmRespawnRateMult(HullSize.CAPITAL_SHIP);
359
360 return null;
361 }
362
363 @Override
364 public String getSModDescriptionParam(int index, HullSize hullSize, ShipAPI ship) {
365 if (index == 0) return "" + (int) Math.round(SMOD_CR_PENALTY * 100f) + "%";
366 if (index == 1) return "" + (int) Math.round(SMOD_MAINTENANCE_PENALTY) + "%";
367 return null;
368 }
369
370 @Override
371 public boolean isSModEffectAPenalty() {
372 return true;
373 }
374}
375
376
377
378
379
380
381
382
383
384
385
static SettingsAPI getSettings()
Definition Global.java:57
static CombatEngineAPI getCombatEngine()
Definition Global.java:69
static GameState getCurrentState()
Definition Global.java:27
boolean isSMod(MutableShipStatsAPI stats)
void modifyFlat(String source, float value)
void modifyPercent(String source, float value)
static final String FRAGMENT_SWARM_RESPAWN_RATE_MULT
Definition Stats.java:151
static final String OVERSEER_CHARGE_FIGHTER
Definition Tags.java:305
String getSModDescriptionParam(int index, HullSize hullSize, ShipAPI ship)
void applyEffectsBeforeShipCreation(HullSize hullSize, MutableShipStatsAPI stats, String id)
static RoilingSwarmEffect getSwarmFor(CombatEntityAPI entity)
void shift(Object source, Color to, float durIn, float durOut, float shift)
static Color setAlpha(Color color, int alpha)
Definition Misc.java:1316
static Color setBrightness(Color color, int brightness)
Definition Misc.java:5196
CargoStackAPI createCargoStack(CargoItemType type, Object data, CargoAPI cargo)
String getSpriteName(String category, String id)
HullModSpecAPI getHullModSpec(String modId)
void maintainStatusForPlayerShip(Object key, String spriteName, String title, String data, boolean isDebuff)
void setExtraOverlayMatchHullColor(boolean extraOverlayMatchHullColor)
MutableShipStatsAPI getMutableStats()
void setExtraOverlay(String spriteName)
void setExtraOverlayShadowOpacity(float extraOverlayOpacity)
List< WeaponAPI > getAllWeapons()