001package com.fs.starfarer.api.impl.hullmods;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.Comparator;
006import java.util.LinkedHashSet;
007import java.util.List;
008import java.util.Set;
009
010import java.awt.Color;
011
012import com.fs.starfarer.api.GameState;
013import com.fs.starfarer.api.Global;
014import com.fs.starfarer.api.combat.BaseHullMod;
015import com.fs.starfarer.api.combat.MutableShipStatsAPI;
016import com.fs.starfarer.api.combat.ShipAPI;
017import com.fs.starfarer.api.combat.ShipAPI.HullSize;
018import com.fs.starfarer.api.combat.WeaponAPI;
019import com.fs.starfarer.api.combat.WeaponAPI.WeaponSize;
020import com.fs.starfarer.api.combat.WeaponAPI.WeaponType;
021import com.fs.starfarer.api.impl.campaign.ids.Tags;
022import com.fs.starfarer.api.loading.WeaponSlotAPI;
023import com.fs.starfarer.api.ui.Alignment;
024import com.fs.starfarer.api.ui.TooltipMakerAPI;
025import com.fs.starfarer.api.util.IntervalUtil;
026import com.fs.starfarer.api.util.Misc;
027import com.fs.starfarer.api.util.TimeoutTracker;
028
029public class MissileAutoloader extends BaseHullMod {
030
031        public static class ReloadCapacityData {
032                public HullSize size;
033                public int minW, maxW;
034                public int capacity;
035                public ReloadCapacityData(HullSize size, int minW, int maxW, int capacity) {
036                        this.size = size;
037                        this.minW = minW;
038                        this.maxW = maxW;
039                        this.capacity = capacity;
040                }
041                
042                public String getSizeStr() {
043                        return Misc.getHullSizeStr(size);
044                }
045                
046                public String getWeaponsString() {
047                        if (maxW < 0) return "" + minW + "+";
048                        if (minW != maxW) return "" + minW + "-" + maxW;
049                        return "" + minW;
050                }
051        }
052        
053        public static List<ReloadCapacityData> CAPACITY_DATA = new ArrayList<MissileAutoloader.ReloadCapacityData>();
054        static {
055                CAPACITY_DATA.add(new ReloadCapacityData(HullSize.FRIGATE, 1, 1, 6));
056                CAPACITY_DATA.add(new ReloadCapacityData(HullSize.FRIGATE, 2, -1, 4));
057                
058                CAPACITY_DATA.add(new ReloadCapacityData(HullSize.DESTROYER, 1, 1, 9));
059                CAPACITY_DATA.add(new ReloadCapacityData(HullSize.DESTROYER, 2, -1, 4));
060                
061                CAPACITY_DATA.add(new ReloadCapacityData(HullSize.CRUISER, 1, 2, 15));
062                CAPACITY_DATA.add(new ReloadCapacityData(HullSize.CRUISER, 3, 3, 12));
063                CAPACITY_DATA.add(new ReloadCapacityData(HullSize.CRUISER, 4, -1, 8));
064                
065                CAPACITY_DATA.add(new ReloadCapacityData(HullSize.CAPITAL_SHIP, 1, 3, 24));
066                CAPACITY_DATA.add(new ReloadCapacityData(HullSize.CAPITAL_SHIP, 4, 6, 18));
067                CAPACITY_DATA.add(new ReloadCapacityData(HullSize.CAPITAL_SHIP, 7, -1, 10));
068        }
069        
070        public static float BASIC_COOLDOWN = 5f;
071        public static float SMOD_COOLDOWN = 10f;
072        
073        public static String MA_DATA_KEY = "core_missile_autoloader_data_key";
074        
075        public static class MissileAutoloaderData {
076                public IntervalUtil interval = new IntervalUtil(0.2f, 0.4f);
077                public float opLeft = 0f;
078                public float showExhaustedStatus = 5f;
079                public TimeoutTracker<WeaponAPI> cooldown = new TimeoutTracker<WeaponAPI>();
080        }
081        
082        public void applyEffectsBeforeShipCreation(HullSize hullSize, MutableShipStatsAPI stats, String id) {
083        }
084                
085
086        @Override
087        public void advanceInCombat(ShipAPI ship, float amount) {
088                super.advanceInCombat(ship, amount);
089
090                if (!ship.isAlive()) return;
091                
092                String key = MA_DATA_KEY;
093                ship.getCustomData().get(key);
094                MissileAutoloaderData data = (MissileAutoloaderData) ship.getCustomData().get(key);
095                if (data == null) {
096                        data = new MissileAutoloaderData();
097                        ReloadCapacityData cap = getCapacityData(ship);
098                        //data.opLeft = spec.getCostFor(ship.getHullSize());
099                        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 &&
116                                                         Global.getCombatEngine() != null && Global.getCombatEngine().getPlayerShip() == ship;
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";
179                        Global.getCombatEngine().maintainStatusForPlayerShip(data,
180                                        Global.getSettings().getSpriteName("ui", "icon_tactical_missile_autoloader"),
181                                        spec.getDisplayName(), 
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;
299                tooltip.beginTable(Misc.getBasePlayerColor(), Misc.getDarkPlayerColor(), Misc.getBrightPlayerColor(),
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) {
321                                c = Misc.getHighlightColor();
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                
335                if (Global.CODEX_TOOLTIP_MODE) {
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;
353                tooltip.beginTable(Misc.getBasePlayerColor(), Misc.getDarkPlayerColor(), Misc.getBrightPlayerColor(),
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,
363                                                   Alignment.MID, h, Misc.getRoundedValueOneAfterDecimalIfNotWhole(cost));
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