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