Starsector API
Loading...
Searching...
No Matches
MissileAutoloader.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.hullmods;
2
3import java.util.ArrayList;
4import java.util.Collections;
5import java.util.Comparator;
6import java.util.LinkedHashSet;
7import java.util.List;
8import java.util.Set;
9
10import java.awt.Color;
11
12import com.fs.starfarer.api.GameState;
13import com.fs.starfarer.api.Global;
14import com.fs.starfarer.api.combat.BaseHullMod;
15import com.fs.starfarer.api.combat.MutableShipStatsAPI;
16import com.fs.starfarer.api.combat.ShipAPI;
17import com.fs.starfarer.api.combat.ShipAPI.HullSize;
18import com.fs.starfarer.api.combat.WeaponAPI;
19import com.fs.starfarer.api.combat.WeaponAPI.WeaponSize;
20import com.fs.starfarer.api.combat.WeaponAPI.WeaponType;
21import com.fs.starfarer.api.impl.campaign.ids.Tags;
22import com.fs.starfarer.api.loading.WeaponSlotAPI;
23import com.fs.starfarer.api.ui.Alignment;
24import com.fs.starfarer.api.ui.TooltipMakerAPI;
25import com.fs.starfarer.api.util.IntervalUtil;
26import com.fs.starfarer.api.util.Misc;
27import com.fs.starfarer.api.util.TimeoutTracker;
28
29public class MissileAutoloader extends BaseHullMod {
30
31 public static class ReloadCapacityData {
32 public HullSize size;
33 public int minW, maxW;
34 public int capacity;
35 public ReloadCapacityData(HullSize size, int minW, int maxW, int capacity) {
36 this.size = size;
37 this.minW = minW;
38 this.maxW = maxW;
39 this.capacity = capacity;
40 }
41
42 public String getSizeStr() {
43 return Misc.getHullSizeStr(size);
44 }
45
46 public String getWeaponsString() {
47 if (maxW < 0) return "" + minW + "+";
48 if (minW != maxW) return "" + minW + "-" + maxW;
49 return "" + minW;
50 }
51 }
52
53 public static List<ReloadCapacityData> CAPACITY_DATA = new ArrayList<MissileAutoloader.ReloadCapacityData>();
54 static {
55 CAPACITY_DATA.add(new ReloadCapacityData(HullSize.FRIGATE, 1, 1, 6));
56 CAPACITY_DATA.add(new ReloadCapacityData(HullSize.FRIGATE, 2, -1, 4));
57
58 CAPACITY_DATA.add(new ReloadCapacityData(HullSize.DESTROYER, 1, 1, 9));
59 CAPACITY_DATA.add(new ReloadCapacityData(HullSize.DESTROYER, 2, -1, 4));
60
61 CAPACITY_DATA.add(new ReloadCapacityData(HullSize.CRUISER, 1, 2, 15));
62 CAPACITY_DATA.add(new ReloadCapacityData(HullSize.CRUISER, 3, 3, 12));
63 CAPACITY_DATA.add(new ReloadCapacityData(HullSize.CRUISER, 4, -1, 8));
64
65 CAPACITY_DATA.add(new ReloadCapacityData(HullSize.CAPITAL_SHIP, 1, 3, 24));
66 CAPACITY_DATA.add(new ReloadCapacityData(HullSize.CAPITAL_SHIP, 4, 6, 18));
67 CAPACITY_DATA.add(new ReloadCapacityData(HullSize.CAPITAL_SHIP, 7, -1, 10));
68 }
69
70 public static float BASIC_COOLDOWN = 5f;
71 public static float SMOD_COOLDOWN = 10f;
72
73 public static String MA_DATA_KEY = "core_missile_autoloader_data_key";
74
75 public static class MissileAutoloaderData {
76 public IntervalUtil interval = new IntervalUtil(0.2f, 0.4f);
77 public float opLeft = 0f;
78 public float showExhaustedStatus = 5f;
80 }
81
82 public void applyEffectsBeforeShipCreation(HullSize hullSize, MutableShipStatsAPI stats, String id) {
83 }
84
85
86 @Override
87 public void advanceInCombat(ShipAPI ship, float amount) {
88 super.advanceInCombat(ship, amount);
89
90 if (!ship.isAlive()) return;
91
92 String key = MA_DATA_KEY;
93 ship.getCustomData().get(key);
94 MissileAutoloaderData data = (MissileAutoloaderData) ship.getCustomData().get(key);
95 if (data == null) {
96 data = new MissileAutoloaderData();
97 ReloadCapacityData cap = getCapacityData(ship);
98 //data.opLeft = spec.getCostFor(ship.getHullSize());
99 if (cap != null) {
100 data.opLeft = cap.capacity;
101 } else {
102 data.showExhaustedStatus = 0;
103 }
104 ship.setCustomData(key, data);
105 }
106
107 if (data.opLeft <= 0.05f) {
108 data.opLeft = 0f;
109 data.showExhaustedStatus -= amount;
110 if (data.showExhaustedStatus <= 0) {
111 return;
112 }
113 }
114
115 boolean playerShip = Global.getCurrentState() == GameState.COMBAT &&
117
118 float mult = ship.getMutableStats().getMissileRoFMult().getModifiedValue();
119 data.cooldown.advance(amount * mult);
120 for (WeaponAPI w : data.cooldown.getItems()) {
121 w.setRemainingCooldownTo(w.getCooldown());
122 }
123
124 data.interval.advance(amount);
125 if (data.interval.intervalElapsed()) {
126 boolean playSound = false;
127 for (WeaponAPI w : ship.getAllWeapons()) {
128 if (!isAffected(w)) continue;
129 if (data.cooldown.contains(w)) continue;
130
131 if (w.usesAmmo() && w.getAmmo() <= 0) {
132 float reloadSize = w.getSpec().getMaxAmmo();
133 float reloadCost = getReloadCost(w, ship);
134 float salvoSize = w.getSpec().getBurstSize();
135 if (salvoSize < 1) salvoSize = 1;
136 if (reloadCost > data.opLeft) {
137 float f = data.opLeft / reloadCost;
138 if (f <= 0f) continue;
139
140 reloadSize *= f;
141 reloadSize /= salvoSize;
142 reloadSize = (float) Math.ceil(reloadSize);
143 reloadSize *= salvoSize;
144 reloadSize = (int) Math.round(reloadSize);
145 }
146
147 playSound = true;
148
149 w.setAmmo((int) reloadSize);
150 boolean sMod = isSMod(ship);
151 //if (COOLDOWN > 0) {
152 if (sMod) {
153 if (SMOD_COOLDOWN > 0) {
154 data.cooldown.set(w, SMOD_COOLDOWN);
155 }
156 } else {
157 if (BASIC_COOLDOWN > 0) {
158 data.cooldown.set(w, BASIC_COOLDOWN);
159 }
160 }
161
162 data.opLeft -= reloadCost;
163 //data.opLeft = Math.round(data.opLeft);
164
165 if (data.opLeft < 0) data.opLeft = 0;
166 if (data.opLeft <= 0) break;
167 }
168 }
169
170 playSound = false; // better without the sound I think
171 if (playerShip && playSound) {
172 Global.getSoundPlayer().playSound("missile_weapon_reloaded", 1f, 1f, ship.getLocation(), ship.getVelocity());
173 }
174 }
175
176 if (playerShip) {
177 String status = "" + Misc.getRoundedValueOneAfterDecimalIfNotWhole(data.opLeft) + " CAPACITY REMAINING";
178 if (data.opLeft <= 0) status = "CAPACITY EXHAUSTED";
180 Global.getSettings().getSpriteName("ui", "icon_tactical_missile_autoloader"),
182 status, data.opLeft <= 0);
183
184 }
185 }
186
187 public static ReloadCapacityData getCapacityData(ShipAPI ship) {
188 if (ship == null) return null;
189 int count = 0;
190// for (WeaponAPI w : ship.getAllWeapons()) {
191// if (!isAffected(w)) continue;
192// if (w.getSlot().getSlotSize() != WeaponSize.SMALL || w.getSlot().getWeaponType() != WeaponType.MISSILE) {
193// count++;
194// }
195// }
196 for (WeaponSlotAPI slot : ship.getHullSpec().getAllWeaponSlotsCopy()) {
197 if (slot.getSlotSize() == WeaponSize.SMALL &&
198 slot.getWeaponType() == WeaponType.MISSILE) {
199 count++;
200 }
201 }
202
203 for (ReloadCapacityData data : CAPACITY_DATA) {
204 if (data.size == ship.getHullSize()) {
205 if (count >= data.minW && count <= data.maxW) return data;
206 if (count >= data.minW && data.maxW < 0) return data;
207 }
208 }
209 return null;
210 }
211
212 public static boolean isAffected(WeaponAPI w) {
213 if (w == null) return false;
214 if (w.getType() != WeaponType.MISSILE) return false;
215 if (w.getSize() != WeaponSize.SMALL) return false;
216
217 if (w.getSlot().getWeaponType() != WeaponType.MISSILE) return false;
218 if (w.getSlot().getSlotSize() != WeaponSize.SMALL) return false;
219
220 if (w.getSpec().hasTag(Tags.NO_RELOAD)) return false;
221 if (!w.usesAmmo() || w.getAmmoPerSecond() > 0) return false;
222 if (w.isDecorative()) return false;
223 if (w.getSlot() != null && w.getSlot().isSystemSlot()) return false;
224 return true;
225 }
226
227 public static float getReloadCost(WeaponAPI w, ShipAPI ship) {
228 if (w.getSpec().hasTag(Tags.RELOAD_1PT)) return 1f;
229 if (w.getSpec().hasTag(Tags.RELOAD_1_AND_A_HALF_PT)) return 1.5f;
230 if (w.getSpec().hasTag(Tags.RELOAD_2PT)) return 2f;
231 if (w.getSpec().hasTag(Tags.RELOAD_3PT)) return 3f;
232 if (w.getSpec().hasTag(Tags.RELOAD_4PT)) return 4f;
233 if (w.getSpec().hasTag(Tags.RELOAD_5PT)) return 5f;
234 if (w.getSpec().hasTag(Tags.RELOAD_6PT)) return 6f;
235
236 int op = (int) Math.round(w.getSpec().getOrdnancePointCost(null, null));
237 if (op == 1) return 1f;
238 if (op == 2 || op == 3) return 2f;
239 if (op == 4) return 3f;
240 if (op == 5 || op == 6) return 4f;
241 if (op == 7 || op == 8) return 5f;
242 return 6f;
243 }
244
245 @Override
246 public boolean shouldAddDescriptionToTooltip(HullSize hullSize, ShipAPI ship, boolean isForModSpec) {
247 return false;
248 }
249
250 @Override
251 public void addPostDescriptionSection(TooltipMakerAPI tooltip, HullSize hullSize, final ShipAPI ship, float width, boolean isForModSpec) {
252 float pad = 3f;
253 float opad = 10f;
254 Color h = Misc.getHighlightColor();
255 Color bad = Misc.getNegativeHighlightColor();
256
257
258 tooltip.addPara("A combat-rated autoloader that provides a limited number of reloads, out of a shared reload capacity, to "
259 + "missile weapons installed in small missile mounts.", opad, h, "missile weapons installed in small missile mounts");
260// tooltip.addPara("Does not affect weapons that do not use ammo or already regenerate it, or weapons that are "
261// + "mounted in any other type of weapon slot."
262// + " The number of missiles reloaded is not affected by skills or hullmods that "
263// + "increase missile weapon ammo capacity.", opad);
264 tooltip.addPara("Does not affect weapons that do not use ammo or regenerate it, or are mounted in any other type of slot."
265 + " Reload size is not affected by skills or hullmods that "
266 + "increase missile ammo capacity.", opad);
267
268// tooltip.addPara("A combat-rated autoloader capable of reloading small missile weapons "
269// + "installed in small missile mounts"
270// + " a limited number of times. Does not affect weapons that do not use ammo or regenerate it."
271// + " The number of missiles reloaded is not affected by skills or hullmods that "
272// + "increase missile weapon ammo capacity.", opad, h, "small missile weapons installed in small missile mounts");
273
274 tooltip.addSectionHeading("Reload capacity", Alignment.MID, opad);
275 tooltip.addPara("Determined by ship size and number of small missile "
276 + "slots, both filled and empty. "
277 + "Having fewer of these simplifies the task and "
278 + "increases the number of possible reloads.", opad);
279
280 if (isForModSpec || (ship == null && !Global.CODEX_TOOLTIP_MODE)) return;
281
282 tooltip.setBgAlpha(0.9f);
283
284 List<WeaponAPI> weapons = new ArrayList<WeaponAPI>();
285 Set<String> seen = new LinkedHashSet<String>();
286 if (ship != null) {
287 for (WeaponAPI w : ship.getAllWeapons()) {
288 if (!isAffected(w)) continue;
289 String id = w.getId();
290 if (seen.contains(id)) continue;
291 seen.add(id);
292 weapons.add(w);
293 }
294 }
295
296 float numW = 130f;
297 float reloadW = 130f;
298 float sizeW = width - numW - reloadW - 10f;
300 20f, true, true,
301 new Object [] {"Ship size", sizeW, "Small missiles", numW, "Reload capacity", reloadW});
302
303 ReloadCapacityData cap = getCapacityData(ship);
304
305 List<ReloadCapacityData> sortedCap = new ArrayList<ReloadCapacityData>(CAPACITY_DATA);
306 Collections.sort(sortedCap, new Comparator<ReloadCapacityData>() {
307 public int compare(ReloadCapacityData o1, ReloadCapacityData o2) {
308 //return (int) Math.signum(o1.capacity - o2.capacity);
309 if (o1.size != o2.size) {
310 return (int) Math.signum(o1.size.ordinal() - o2.size.ordinal());
311 }
312 return (int) Math.signum(o1.capacity - o2.capacity);
313 }
314 });
315 //sortedCap = new ArrayList<ReloadCapacityData>(CAPACITY_DATA);
316
317 HullSize prev = HullSize.FRIGATE;
318 for (ReloadCapacityData curr : sortedCap) {
319 Color c = Misc.getGrayColor();
320 if (cap == curr || Global.CODEX_TOOLTIP_MODE) {
322 }
323 if (curr.size != hullSize && !Global.CODEX_TOOLTIP_MODE) continue;
324// if (prev != curr.size) {
325// tooltip.addRow("", "", "");
326// }
327 tooltip.addRow(Alignment.MID, c, curr.getSizeStr(),
328 Alignment.MID, c, curr.getWeaponsString(),
329 Alignment.MID, c, "" + curr.capacity);
330 prev = curr.size;
331 }
332 tooltip.addTable("", 0, opad);
333
334
336 tooltip.addSpacer(5f);
337 return;
338 }
339
340 Collections.sort(weapons, new Comparator<WeaponAPI>() {
341 public int compare(WeaponAPI o1, WeaponAPI o2) {
342 float c1 = getReloadCost(o1, ship);
343 float c2 = getReloadCost(o2, ship);
344 return (int) Math.signum(c1 - c2);
345 }
346 });
347
348
349 tooltip.addSectionHeading("Reload cost", Alignment.MID, opad + 5f);
350
351 float costW = 100f;
352 float nameW = width - costW - 5f;
354 20f, true, true,
355 new Object [] {"Affected weapon", nameW, "Reload cost", costW});
356 int max = 10;
357 int count = 0;
358 for (WeaponAPI w : weapons) {
359 count++;
360 float cost = getReloadCost(w, ship);
361 String name = tooltip.shortenString(w.getDisplayName(), nameW - 20f);
362 tooltip.addRow(Alignment.LMID, Misc.getTextColor(), name,
364 if (count >= max) break;
365 }
366 tooltip.addTable("No affected weapons mounted", weapons.size() - max, opad);
367
368 //tooltip.addPara("Weapons are reloaded, out of the shared pool of reload capacity, when they run out of ammo.", opad);
369 tooltip.addPara("A partial reload is possible when running out of capacity.", opad);
370// tooltip.addPara("A partial reload is possible when running out of capacity "
371// + "and the number of missiles loaded will be rounded up to the "
372// + "nearest multiple of the weapon's salvo size.", opad);
373 if (BASIC_COOLDOWN > 0) {
374 tooltip.addPara("After a reload, the weapon requires an extra %s seconds,"
375 + " in addition to its normal cooldown, before it can fire again.", opad,
376 h, "" + (int) BASIC_COOLDOWN);
377 }
378 }
379
380
381 @Override
382 public boolean isApplicableToShip(ShipAPI ship) {
383 return getCapacityData(ship) != null;
384 }
385
386
387 @Override
388 public String getUnapplicableReason(ShipAPI ship) {
389 return "Ship does not have any small missile slots";
390 }
391
392 @Override
393 public boolean isSModEffectAPenalty() {
394 return true;
395 }
396
397 @Override
398 public String getSModDescriptionParam(int index, HullSize hullSize, ShipAPI ship) {
399 if (index == 0) return "" + (int) SMOD_COOLDOWN;
400 return null;
401 }
402
403
404}
405
406
407
408
409
410
411
412
413
414
415
static SettingsAPI getSettings()
Definition Global.java:57
static boolean CODEX_TOOLTIP_MODE
Definition Global.java:15
static SoundPlayerAPI getSoundPlayer()
Definition Global.java:49
static CombatEngineAPI getCombatEngine()
Definition Global.java:69
static GameState getCurrentState()
Definition Global.java:27
boolean isSMod(MutableShipStatsAPI stats)
static final String RELOAD_1_AND_A_HALF_PT
Definition Tags.java:489
void applyEffectsBeforeShipCreation(HullSize hullSize, MutableShipStatsAPI stats, String id)
boolean shouldAddDescriptionToTooltip(HullSize hullSize, ShipAPI ship, boolean isForModSpec)
String getSModDescriptionParam(int index, HullSize hullSize, ShipAPI ship)
void addPostDescriptionSection(TooltipMakerAPI tooltip, HullSize hullSize, final ShipAPI ship, float width, boolean isForModSpec)
static float getReloadCost(WeaponAPI w, ShipAPI ship)
static ReloadCapacityData getCapacityData(ShipAPI ship)
static Color getTextColor()
Definition Misc.java:839
static Color getNegativeHighlightColor()
Definition Misc.java:802
static String getHullSizeStr(HullSize size)
Definition Misc.java:6665
static Color getBasePlayerColor()
Definition Misc.java:833
static Color getGrayColor()
Definition Misc.java:826
static Color getBrightPlayerColor()
Definition Misc.java:830
static Color getHighlightColor()
Definition Misc.java:792
static Color getDarkPlayerColor()
Definition Misc.java:836
static String getRoundedValueOneAfterDecimalIfNotWhole(float value)
Definition Misc.java:683
String getSpriteName(String category, String id)
SoundAPI playSound(String id, float pitch, float volume, Vector2f loc, Vector2f vel)
void maintainStatusForPlayerShip(Object key, String spriteName, String title, String data, boolean isDebuff)
void setCustomData(String key, Object data)
MutableShipStatsAPI getMutableStats()
List< WeaponAPI > getAllWeapons()
List< WeaponSlotAPI > getAllWeaponSlotsCopy()
float getOrdnancePointCost(MutableCharacterStatsAPI stats)
void addTable(String emptyText, int andMore, float pad)
String shortenString(String in, float maxWidth)
UIPanelAPI beginTable(FactionAPI faction, float itemHeight, Object ... columns)
LabelAPI addPara(String format, float pad, Color hl, String... highlights)
UIComponentAPI addSpacer(float height)
LabelAPI addSectionHeading(String str, Alignment align, float pad)