Starsector API
Loading...
Searching...
No Matches
ConstructionSwarmSystemScript.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 org.lwjgl.util.vector.Vector2f;
7
8import com.fs.starfarer.api.Global;
9import com.fs.starfarer.api.combat.BoundsAPI.SegmentAPI;
10import com.fs.starfarer.api.combat.CombatEngineAPI;
11import com.fs.starfarer.api.combat.CombatFleetManagerAPI;
12import com.fs.starfarer.api.combat.DeployedFleetMemberAPI;
13import com.fs.starfarer.api.combat.MutableShipStatsAPI;
14import com.fs.starfarer.api.combat.ShipAPI;
15import com.fs.starfarer.api.combat.ShipAPI.HullSize;
16import com.fs.starfarer.api.combat.ShipSystemAPI;
17import com.fs.starfarer.api.combat.ShipSystemAPI.SystemState;
18import com.fs.starfarer.api.combat.ShipVariantAPI;
19import com.fs.starfarer.api.impl.campaign.ids.Tags;
20import com.fs.starfarer.api.impl.combat.BaseShipSystemScript;
21import com.fs.starfarer.api.loading.WeaponSlotAPI;
22import com.fs.starfarer.api.util.CountingMap;
23import com.fs.starfarer.api.util.Misc;
24import com.fs.starfarer.api.util.WeightedRandomPicker;
25
27
28
29// public static float MIN_COOLDOWN = 2f;
30// public static float MAX_COOLDOWN = 10f;
31// public static float COOLDOWN_DP_MULT = 0.25f;
32 public static int BASE_FRAGMENTS = 50;
33
34 public static float CONSTRUCTION_SWARM_SPEED_MULT = 0.33f;
35// public static float BASE_CONSTRUCTION_TIME = 2f;
36// public static float CONSTRUCTION_TIME_DP_MULT = 1f;
37 public static float BASE_CONSTRUCTION_TIME = 5f;
38 public static float CONSTRUCTION_TIME_DP_MULT = 1.5f;
39 public static float CONSTRUCTION_TIME_OVERSEER_EXTRA = 13f; // makes it 30 seconds
40
41 public static float NUM_LARGE_AS_FRACTION_OF_DESTROYERS = 0.5f;
42 public static float NUM_DESTROYERS_AS_FRACTION_OF_FRIGATES = 0.6f;
43
44 public static int FAST_CONSTRUCTION_FRIGATES_MAX = 2;
45
46 public static float MIN_CR;
47 public static float MIN_DP;
48 public static int MIN_FRAGMENTS;
49 public static int MAX_FRAGMENTS;
50
51
52
53 public static enum SwarmConstructableType {
54 COMBAT_UNIT,
55 OVERSEER,
56 HIVE,
57 }
58
59 public static class SwarmConstructableVariant {
60 public SwarmConstructableType type;
61 public String variantId;
62 public float cr; // 0 to 1 (so, 0.02 or similar)
63 public float dp;
64 public int fragments;
65 public HullSize size;
66
67 public SwarmConstructableVariant(SwarmConstructableType type, String variantId) {
68 this.type = type;
69 this.variantId = variantId;
70
73 size = v.getHullSize();
74
75 cr = 0.01f;
76 if (v.getHullSize() == HullSize.FRIGATE) {
77 cr = 0.02f;
78 } else if (v.getHullSize() == HullSize.DESTROYER) {
79 cr = 0.04f;
80 } else if (v.getHullSize() == HullSize.CRUISER) {
81 cr = 0.06f;
82 } else if (v.getHullSize() == HullSize.CAPITAL_SHIP) {
83 cr = 0.1f;
84 }
85
86 if (type == SwarmConstructableType.HIVE) {
87 cr += 0.02f;
88 }
89 if (type == SwarmConstructableType.OVERSEER) {
90 cr += 0.01f;
91 }
92 fragments = getFragmentCost(dp, size);
93 }
94 }
95
96 public static List<SwarmConstructableVariant> CONSTRUCTABLE = new ArrayList<>();
97
98 protected static boolean inited = false;
103 public static void init() {
104 if (inited) return;
105 inited = true;
106 CONSTRUCTABLE.add(new SwarmConstructableVariant(SwarmConstructableType.COMBAT_UNIT, "skirmish_unit_Type100"));
107 CONSTRUCTABLE.add(new SwarmConstructableVariant(SwarmConstructableType.COMBAT_UNIT, "skirmish_unit_Type101"));
108 CONSTRUCTABLE.add(new SwarmConstructableVariant(SwarmConstructableType.COMBAT_UNIT, "assault_unit_Type200"));
109 CONSTRUCTABLE.add(new SwarmConstructableVariant(SwarmConstructableType.COMBAT_UNIT, "assault_unit_Type201"));
110 CONSTRUCTABLE.add(new SwarmConstructableVariant(SwarmConstructableType.COMBAT_UNIT, "standoff_unit_Type300"));
111 CONSTRUCTABLE.add(new SwarmConstructableVariant(SwarmConstructableType.COMBAT_UNIT, "standoff_unit_Type301"));
112 CONSTRUCTABLE.add(new SwarmConstructableVariant(SwarmConstructableType.COMBAT_UNIT, "standoff_unit_Type302"));
113
114 CONSTRUCTABLE.add(new SwarmConstructableVariant(SwarmConstructableType.OVERSEER, "overseer_unit_Type250"));
115 CONSTRUCTABLE.add(new SwarmConstructableVariant(SwarmConstructableType.HIVE, "hive_unit_Type350"));
116
117 MIN_CR = 1f;
118 MIN_DP = 100f;
119 MIN_FRAGMENTS = 500;
120
121 MAX_FRAGMENTS = 0;
122
123 for (SwarmConstructableVariant v : CONSTRUCTABLE) {
124 MIN_CR = Math.min(v.cr, MIN_CR);
125 MIN_DP = Math.min(v.dp, MIN_DP);
126 MIN_FRAGMENTS = Math.min(v.fragments, MIN_FRAGMENTS);
127 MAX_FRAGMENTS = Math.max(v.fragments, MAX_FRAGMENTS);
128 }
129 }
130
131
132 public static class SwarmConstructionData {
133 public String variantId;
134 public float constructionTime = 10f;
135 public float preConstructionTravelTime = 3f;
136 }
137
138
140 protected boolean readyToFire = true;
142
143 protected void findSlots(ShipAPI ship) {
144 if (slots != null) return;
146 for (WeaponSlotAPI slot : ship.getHullSpec().getAllWeaponSlotsCopy()) {
147 if (slot.isSystemSlot()) {
148 slots.add(slot);
149 }
150 }
151 }
152
153 public void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel) {
154 ShipAPI ship = null;
155 //boolean player = false;
156 if (stats.getEntity() instanceof ShipAPI) {
157 ship = (ShipAPI) stats.getEntity();
158 //player = ship == Global.getCombatEngine().getPlayerShip();
159 } else {
160 return;
161 }
162
163 init();
164
165 if (state == State.IDLE || state == State.COOLDOWN || effectLevel <= 0f) {
166 readyToFire = true;
167 }
168
169 if (effectLevel == 1 && readyToFire) {
170 readyToFire = false;
171 launchSwarm(ship);
172 }
173 }
174
175
176 protected void launchSwarm(ShipAPI ship) {
177 findSlots(ship);
178
180
182 CombatFleetManagerAPI manager = engine.getFleetManager(ship.getOwner());
183 manager.setSuppressDeploymentMessages(true);
184
185 WeaponSlotAPI slot = slots.pick();
186
187 Vector2f loc = slot.computePosition(ship);
188 float facing = slot.computeMidArcAngle(ship);
189
190 ShipAPI fighter = manager.spawnShipOrWing(wingId, loc, facing, 0f, null);
191 fighter.getWing().setSourceShip(ship);
192
193 manager.setSuppressDeploymentMessages(false);
194
195 fighter.getMutableStats().getMaxSpeed().modifyMult("construction_swarm", CONSTRUCTION_SWARM_SPEED_MULT);
196
197 Vector2f takeoffVel = Misc.getUnitVectorAtDegreeAngle(facing);
198 takeoffVel.scale(fighter.getMaxSpeed() * 1f);
199
200 fighter.setDoNotRender(true);
201 fighter.setExplosionScale(0f);
202 fighter.setHulkChanceOverride(0f);
204 fighter.getArmorGrid().clearComponentMap(); // no damage to weapons/engines
205 Vector2f.add(fighter.getVelocity(), takeoffVel, fighter.getVelocity());
206
208 if (sourceSwarm == null) return;
209
211 swarm.params.flashFringeColor = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR;
212 RoilingSwarmEffect.getFlockingMap().remove(swarm.params.flockingClass, swarm);
213 swarm.params.flockingClass = FragmentSwarmHullmod.CONSTRUCTION_SWARM_FLOCKING_CLASS;
214 RoilingSwarmEffect.getFlockingMap().add(swarm.params.flockingClass, swarm);
215
216
217 SwarmConstructableVariant pick = pickVariant(ship);
218 if (pick == null) return;
219
220 String variantId = pick.variantId;
221// variantId = "standoff_unit_Type300";
222// variantId = "overseer_unit_Type250";
223// variantId = "skirmish_unit_Type100";
224// variantId = "assault_unit_Type200";
225// variantId = "assault_unit_Type201"; // no swarm
226// variantId = "hive_unit_Type350";
227
228 ShipVariantAPI variant = Global.getSettings().getVariant(variantId);
229 if (variant == null) return;
230
231 ship.setCurrentCR(ship.getCurrentCR() - pick.cr);
232
233 float dp = variant.getHullSpec().getSuppliesToRecover();
234
235 int numFragments = pick.fragments;
236 float radiusMult = 1f;
237 float collisionMult = 2f;
238 float hpMult = 1f;
239 float travelTime = 3f;
240
241 if (variant.getHullSize() == HullSize.DESTROYER) {
242 radiusMult = 2f;
243 collisionMult = 4f;
244 hpMult = radiusMult;
245 travelTime = 4f;
246 } else if (variant.getHullSize() == HullSize.CRUISER) {
247 radiusMult = 3.5f;
248 collisionMult = 6f;
249 hpMult = radiusMult;
250 travelTime = 5f;
251 } else if (variant.getHullSize() == HullSize.CAPITAL_SHIP) {
252 radiusMult = 4;
253 collisionMult = 8f;
254 hpMult = radiusMult;
255 travelTime = 6f;
256 }
257
258 for (SegmentAPI s : fighter.getExactBounds().getOrigSegments()) {
259 s.getP1().scale(collisionMult);
260 s.getP2().scale(collisionMult);
261 s.set(s.getP1().x, s.getP1().y, s.getP2().x, s.getP2().y);
262 }
263 fighter.setCollisionRadius(fighter.getCollisionRadius() * collisionMult);
264
265 fighter.setMaxHitpoints(fighter.getMaxHitpoints() * hpMult);
266 fighter.setHitpoints(fighter.getHitpoints() * hpMult);
267
268 swarm.params.maxOffset *= radiusMult;
269// swarm.params.initialMembers *= numMult;
270// swarm.params.baseMembersToMaintain = swarm.params.initialMembers;
271// requiredFragments = swarm.params.initialMembers;
272 swarm.params.initialMembers = numFragments;
273 swarm.params.baseMembersToMaintain = numFragments;
274
275 boolean overseer = variant.getHullSpec().hasTag(Tags.THREAT_OVERSEER);
276
277 SwarmConstructionData data = new SwarmConstructionData();
278 data.variantId = variantId;
279 data.constructionTime = BASE_CONSTRUCTION_TIME + dp * CONSTRUCTION_TIME_DP_MULT;
280 if (overseer) {
281 data.constructionTime += CONSTRUCTION_TIME_OVERSEER_EXTRA;
282 }
283 data.preConstructionTravelTime = travelTime;
284
285 if (fastConstructionLeft > 0) {
286 if (pick.size == HullSize.FRIGATE) {
288 data.constructionTime = 2f;
289 } else {
291 }
292 }
293
294 swarm.custom1 = data;
295
296 int transfer = Math.min(numFragments, sourceSwarm.getNumActiveMembers());
297 if (transfer > 0) {
298 loc = new Vector2f(takeoffVel);
299 loc.scale(0.5f);
300 Vector2f.add(loc, fighter.getLocation(), loc);
301 sourceSwarm.transferMembersTo(swarm, transfer, loc, 100f);
302 }
303
304 int add = numFragments - transfer;
305 if (add > 0) {
306 swarm.addMembers(add);
307 }
308 }
309
310
311 public SwarmConstructableVariant pickVariant(ShipAPI ship) {
312 init();
313
314// if (true) {
315// return new SwarmConstructableVariant(SwarmConstructableType.COMBAT_UNIT, "standoff_unit_Type302");
316// }
317
319 CombatFleetManagerAPI manager = engine.getFleetManager(ship.getOwner());
320 if (manager == null) return null;
321
323 int fragments = swarm == null ? 0 : swarm.getNumActiveMembers();
324
325 int dpLeft = manager.getMaxStrength() - manager.getCurrStrength();
326 float cr = ship.getCurrentCR();
327
328 int overseers = getNumOverseersDeployed(manager);
329 int hives = getNumHivesDeployed(manager);
330 int fabricators = getNumFabricatorsDeployed(manager);
331 float combatWeight = getCombatWeightDeployed(manager);
332
333
334 int wantOverseers = (int) (combatWeight / 8f);
335 if (wantOverseers < 1) wantOverseers = 1;
336
337 combatWeight += Math.max(0, fabricators - 1f) * 16f;
338 int wantHives = (int) (combatWeight / 16f);
339
340 if (wantHives < 1) wantHives = 1;
341 if (wantHives > 2) wantHives = 2;
342
343 wantOverseers -= overseers;
344 wantHives -= hives;
345
346 float frigates = getCombatDeployed(manager, HullSize.FRIGATE);
347 float destroyers = getCombatDeployed(manager, HullSize.DESTROYER);
348 float cruisers = getCombatDeployed(manager, HullSize.CRUISER);
349 float capitals = getCombatDeployed(manager, HullSize.CAPITAL_SHIP);
350 float large = cruisers + capitals;
351
352 if (frigates >= 2) {
354 }
355
356 CountingMap<HullSize> numCombatVariants = new CountingMap<>();
357 for (SwarmConstructableVariant curr : CONSTRUCTABLE) {
358 if (curr.type == SwarmConstructableType.COMBAT_UNIT) {
359 numCombatVariants.add(curr.size);
360 }
361 }
362
368
369 for (SwarmConstructableVariant curr : CONSTRUCTABLE) {
370 if (curr.dp > dpLeft) continue;
371 if (curr.cr > cr) continue;
372 if (curr.fragments > fragments) continue;
373
374 if (curr.type == SwarmConstructableType.HIVE) {
375 hivePicker.add(curr, 1f / curr.dp);
376 } else if (curr.type == SwarmConstructableType.OVERSEER) {
377 overseerPicker.add(curr, 1f / curr.dp);
378 } else {
379 float wMult = 1f / Math.max(1f, numCombatVariants.getCount(curr.size));
380 if (curr.size == HullSize.FRIGATE) {
381 smallPicker.add(curr, 1f / curr.dp * wMult);
382 } else if (curr.size == HullSize.DESTROYER) {
383 mediumPicker.add(curr, 1f / curr.dp * wMult);
384 } else {
385 largePicker.add(curr, 1f / curr.dp * wMult);
386 }
387 }
388 }
389
390 if (frigates <= 1 && !smallPicker.isEmpty()) {
391 return smallPicker.pick();
392 }
393
394 if (wantOverseers > 0 || wantHives > 0) {
395 if (wantOverseers >= wantHives && !overseerPicker.isEmpty()) {
396 return overseerPicker.pick();
397 } else if (!hivePicker.isEmpty()) {
398 return hivePicker.pick();
399 }
400 }
401
402 if (large <= destroyers * NUM_LARGE_AS_FRACTION_OF_DESTROYERS && !largePicker.isEmpty()) {
403 return largePicker.pick();
404 }
405
406 if (destroyers <= frigates * NUM_DESTROYERS_AS_FRACTION_OF_FRIGATES && !mediumPicker.isEmpty()) {
407 return mediumPicker.pick();
408 }
409
410 return smallPicker.pick();
411 }
412
413 public static boolean constructionSwarmWillBuild(ShipAPI ship, String tag, HullSize size) {
415 return false;
416 }
418 if (swarm == null) {
419 return false;
420 }
421
422 if (swarm.custom1 instanceof SwarmConstructionData) {
423 SwarmConstructionData data = (SwarmConstructionData) swarm.custom1;
424 ShipVariantAPI v = Global.getSettings().getVariant(data.variantId);
425 if (v.getHullSpec().hasTag(tag)) {
426 return size == null || v.getHullSize() == size;
427 }
428 }
429 return false;
430 }
431
433 init();
434 int count = 0;
435 for (DeployedFleetMemberAPI dfm : manager.getDeployedCopyDFM()) {
436 ShipAPI ship = dfm.getShip();
437 if (ship == null) continue;
438
440 count++;
441 }
442 }
443 return count;
444 }
445
447 init();
448 int count = 0;
449 for (DeployedFleetMemberAPI dfm : manager.getDeployedCopyDFM()) {
450 ShipAPI ship = dfm.getShip();
451 if (ship == null) continue;
452
454 count++;
455 continue;
456 }
457 if (ship.isFighter()) continue;
458
459
461 count++;
462 }
463 }
464 return count;
465 }
466
467 public static int getNumHivesDeployed(CombatFleetManagerAPI manager) {
468 init();
469 int count = 0;
470 for (DeployedFleetMemberAPI dfm : manager.getDeployedCopyDFM()) {
471 ShipAPI ship = dfm.getShip();
472 if (ship == null) continue;
473
475 count++;
476 continue;
477 }
478 if (ship.isFighter()) continue;
479
480 if (ship.getHullSpec().hasTag(Tags.THREAT_HIVE)) {
481 count++;
482 }
483 }
484 return count;
485 }
486
487 public static float getCombatWeightDeployed(CombatFleetManagerAPI manager) {
488 init();
489 float weight = 0;
490 for (DeployedFleetMemberAPI dfm : manager.getDeployedCopyDFM()) {
491 ShipAPI ship = dfm.getShip();
492 if (ship == null) continue;
493
496 if (swarm != null && swarm.custom1 instanceof SwarmConstructionData) {
497 SwarmConstructionData data = (SwarmConstructionData) swarm.custom1;
498 ShipVariantAPI v = Global.getSettings().getVariant(data.variantId);
500 switch (v.getHullSize()) {
501 case CAPITAL_SHIP: weight += 8; break;
502 case CRUISER: weight += 4; break;
503 case DESTROYER: weight += 2; break;
504 case FRIGATE: weight += 1; break;
505 case FIGHTER: weight += 1; break;
506 }
507 }
508 }
509 continue;
510 }
511
512 if (ship.getHullSpec().hasTag(Tags.THREAT_COMBAT)) {
513 weight += Misc.getShipWeight(ship, false);
514 }
515 }
516 return weight;
517 }
518
519 public static int getCombatDeployed(CombatFleetManagerAPI manager, HullSize size) {
520 init();
521 int count = 0;
522 for (DeployedFleetMemberAPI dfm : manager.getDeployedCopyDFM()) {
523 ShipAPI ship = dfm.getShip();
524 if (ship == null) continue;
525
527 count++;
528 continue;
529 }
530 if (ship.isFighter() || ship.getHullSize() != size) continue;
531
532 if (ship.getHullSpec().hasTag(Tags.THREAT_COMBAT)) {
533 count++;
534 }
535 }
536 return count;
537 }
538
539
540 public static int getFragmentCost(float dp, HullSize size) {
541 float numMult = 1f * dp / 5f;
542 if (size == HullSize.DESTROYER) {
543 numMult = 2f * dp / 20f;
544 } else if (size == HullSize.CRUISER) {
545 numMult = 3f * dp / 20f;
546 } else if (size == HullSize.CAPITAL_SHIP) {
547 numMult = 5f * dp / 40f;
548 }
549 return (int) Math.round(BASE_FRAGMENTS * numMult);
550 }
551
552
553 @Override
554 public String getInfoText(ShipSystemAPI system, ShipAPI ship) {
555 init();
556 if (system.isOutOfAmmo()) return null;
557 if (system.getState() != SystemState.IDLE) return null;
558
559 if (!enoughFragments(system, ship)) {
560 return "LOW FRAGMENTS";
561 }
562 if (!enoughDP(system, ship)) {
563 return "LOW DP";
564 }
565 if (!enoughCR(system, ship)) {
566 return "LOW CR";
567 }
568 return "READY";
569 }
570
571 public boolean enoughCR(ShipSystemAPI system, ShipAPI ship) {
572 return ship.getCurrentCR() >= MIN_CR;
573 }
574 public boolean enoughDP(ShipSystemAPI system, ShipAPI ship) {
576 CombatFleetManagerAPI manager = engine.getFleetManager(ship.getOwner());
577 if (manager == null) return true;
578
579 int dpLeft = manager.getMaxStrength() - manager.getCurrStrength();
580
581 for (DeployedFleetMemberAPI dfm : manager.getDeployedCopyDFM()) {
582 ShipAPI ship2 = dfm.getShip();
583 if (ship2 == null) continue;
584
586 continue;
587 }
589 if (swarm == null) {
590 continue;
591 }
592 if (swarm.custom1 instanceof SwarmConstructionData) {
593 SwarmConstructionData data = (SwarmConstructionData) swarm.custom1;
594 ShipVariantAPI v = Global.getSettings().getVariant(data.variantId);
595 dpLeft -= v.getHullSpec().getSuppliesToRecover();
596 }
597 }
598
599 return dpLeft >= MIN_DP;
600 }
601 public boolean enoughFragments(ShipSystemAPI system, ShipAPI ship) {
603 int active = swarm == null ? 0 : swarm.getNumActiveMembers();
604 int required = MIN_FRAGMENTS;
605 return active >= required;
606 }
607
608 @Override
609 public boolean isUsable(ShipSystemAPI system, ShipAPI ship) {
610 init();
611 return enoughFragments(system, ship) && enoughDP(system, ship) && enoughCR(system, ship);
612 }
613
614}
615
616
617
618
619
620
621
622
static SettingsAPI getSettings()
Definition Global.java:57
static CombatEngineAPI getCombatEngine()
Definition Global.java:69
void modifyMult(String source, float value)
static int getCombatDeployed(CombatFleetManagerAPI manager, HullSize size)
static boolean constructionSwarmWillBuild(ShipAPI ship, String tag, HullSize size)
void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel)
void transferMembersTo(RoilingSwarmEffect other, float fraction)
static RoilingSwarmEffect getSwarmFor(CombatEntityAPI entity)
void add(K key, int quantity)
static float getShipWeight(ShipAPI ship)
Definition Misc.java:6095
static Vector2f getUnitVectorAtDegreeAngle(float degrees)
Definition Misc.java:1196
ShipVariantAPI getVariant(String variantId)
List< SegmentAPI > getOrigSegments()
CombatFleetManagerAPI getFleetManager(FleetSide side)
List< DeployedFleetMemberAPI > getDeployedCopyDFM()
ShipAPI spawnShipOrWing(String specId, Vector2f location, float facing)
void setSuppressDeploymentMessages(boolean suppressDeploymentMessages)
void setSourceShip(ShipAPI sourceShip)
MutableShipStatsAPI getMutableStats()
void setMaxHitpoints(float maxArmor)
void setDoNotRender(boolean doNotRender)
void setImpactVolumeMult(float impactVolumeMult)
void setExplosionScale(float explosionScale)
void setHulkChanceOverride(float hulkChanceOverride)
List< WeaponSlotAPI > getAllWeaponSlotsCopy()
Vector2f computePosition(CombatEntityAPI ship)