001package com.fs.starfarer.api.plugins.impl;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.Comparator;
006import java.util.HashMap;
007import java.util.HashSet;
008import java.util.Iterator;
009import java.util.LinkedHashMap;
010import java.util.LinkedHashSet;
011import java.util.List;
012import java.util.Map;
013import java.util.Random;
014import java.util.Set;
015
016import com.fs.starfarer.api.Global;
017import com.fs.starfarer.api.campaign.CampaignFleetAPI;
018import com.fs.starfarer.api.campaign.FactionAPI;
019import com.fs.starfarer.api.characters.MutableCharacterStatsAPI;
020import com.fs.starfarer.api.characters.MutableCharacterStatsAPI.SkillLevelAPI;
021import com.fs.starfarer.api.characters.OfficerDataAPI;
022import com.fs.starfarer.api.characters.PersonAPI;
023import com.fs.starfarer.api.characters.SkillSpecAPI;
024import com.fs.starfarer.api.combat.ShieldAPI.ShieldType;
025import com.fs.starfarer.api.combat.ShipAPI;
026import com.fs.starfarer.api.combat.ShipAPI.HullSize;
027import com.fs.starfarer.api.combat.ShipHullSpecAPI;
028import com.fs.starfarer.api.combat.ShipVariantAPI;
029import com.fs.starfarer.api.combat.WeaponAPI.AIHints;
030import com.fs.starfarer.api.combat.WeaponAPI.WeaponType;
031import com.fs.starfarer.api.fleet.FleetMemberAPI;
032import com.fs.starfarer.api.impl.campaign.DModManager;
033import com.fs.starfarer.api.impl.campaign.HullModItemManager;
034import com.fs.starfarer.api.impl.campaign.fleets.DefaultFleetInflater;
035import com.fs.starfarer.api.impl.campaign.ids.Factions;
036import com.fs.starfarer.api.impl.campaign.ids.HullMods;
037import com.fs.starfarer.api.impl.campaign.ids.Skills;
038import com.fs.starfarer.api.impl.campaign.ids.Tags;
039import com.fs.starfarer.api.impl.campaign.tutorial.TutorialMissionIntel;
040import com.fs.starfarer.api.loading.FighterWingSpecAPI;
041import com.fs.starfarer.api.loading.HullModSpecAPI;
042import com.fs.starfarer.api.loading.VariantSource;
043import com.fs.starfarer.api.loading.WeaponGroupSpec;
044import com.fs.starfarer.api.loading.WeaponSlotAPI;
045import com.fs.starfarer.api.loading.WeaponSpecAPI;
046import com.fs.starfarer.api.util.Misc;
047import com.fs.starfarer.api.util.WeightedRandomPicker;
048
049public class CoreAutofitPlugin extends BaseAutofitPlugin {
050
051        public static float RANDOMIZE_CHANCE = 0.5f;
052        
053        public static int PRIORITY = 1000;
054        
055        public static String BUY_FROM_MARKET = new String("buy_from_market");
056        public static String USE_FROM_CARGO = new String("use_from_cargo");
057        public static String USE_FROM_STORAGE = new String("use_from_storage");
058        public static String BUY_FROM_BLACK_MARKET = new String("black_market");
059        //public static String USE_BETTER = new String("use_better");
060        public static String UPGRADE = new String("upgrade");
061        public static String ALWAYS_REINFORCED_HULL = new String("always_reinforced_hull");
062        public static String ALWAYS_BLAST_DOORS = new String("always_blast_doors");
063        public static String STRIP = new String("strip");
064        public static String RANDOMIZE = new String("randomize");
065        //public static String USE_OTHER = new String("use_other");
066
067        
068        public static String LR = "LR";
069        public static String SR = "SR";
070        
071        public static String KINETIC = "kinetic";
072        public static String HE = "he";
073        public static String ENERGY = "energy";
074        public static String PD = "pd";
075        public static String BEAM = "beam";
076        
077        public static String STRIKE = "strike";
078        public static String MISSILE = "missile";
079        public static String UTILITY = "utility";
080        public static String ROCKET = "rocket";
081        
082        public static String INTERCEPTOR = "interceptor";
083        public static String BOMBER = "bomber";
084        public static String FIGHTER = "fighter";
085        public static String SUPPORT = "support";
086        
087        
088        protected static Map<String, Category> reusableCategories = null; 
089        
090        public static class Category {
091                public String base;
092                public Set<String> tags = new HashSet<String>();
093                
094                public List<String> fallback = new ArrayList<String>();
095                
096                public Category(String base, Map<String, Category> categories) {
097                        this.base = base;
098                        
099                        categories.put(base, this);
100                        for (int i = 0; i < 100; i++) {
101                                String id = base + i;
102                                tags.add(id);
103                                categories.put(id, this);
104                        }
105                }
106                
107                public void addFallback(String ... categories) {
108                        for (String catId : categories) {
109                                fallback.add(catId);
110                        }
111                }
112        }
113        
114        protected List<AutofitOption> options = new ArrayList<AutofitOption>();
115        
116        protected Map<String, Category> categories = new LinkedHashMap<String, Category>();
117        
118        protected Map<WeaponSpecAPI, List<String>> altWeaponCats = new LinkedHashMap<WeaponSpecAPI, List<String>>();
119        protected Map<FighterWingSpecAPI, List<String>> altFighterCats = new LinkedHashMap<FighterWingSpecAPI, List<String>>();
120        
121        protected boolean debug = false;
122        protected PersonAPI fleetCommander;
123        protected MutableCharacterStatsAPI stats;
124        
125        protected Random random;
126        
127        protected boolean randomize = false;
128        protected long weaponFilterSeed = 0;
129        protected String emptyWingTarget = null;
130        
131        public Random getRandom() {
132                return random;
133        }
134
135        public void setRandom(Random random) {
136                this.random = random;
137        }
138
139        public boolean isChecked(String id) {
140                for (AutofitOption option : options) {
141                        if (option.id.equals(id)) return option.checked;
142                }
143                return false;
144        }
145        
146        public void setChecked(String id, boolean checked) {
147                for (AutofitOption option : options) {
148                        if (option.id.equals(id)) {
149                                option.checked = checked;
150                                return;
151                        }
152                }
153        }
154        
155        public CoreAutofitPlugin(PersonAPI fleetCommander) {
156                this.fleetCommander = fleetCommander;
157                if (fleetCommander != null) stats = fleetCommander.getStats();
158                options.add(new AutofitOption(USE_FROM_CARGO, "Use ordnance from cargo", true, 
159                                "Use weapons and fighter LPCs from your fleet's cargo holds."));
160                options.add(new AutofitOption(USE_FROM_STORAGE, "Use ordnance from storage", true, 
161                                "Use weapons and fighter LPCs from your local storage facilities."));
162                options.add(new AutofitOption(BUY_FROM_MARKET, "Buy ordnance from market", true, 
163                                "Buy weapons and fighter LPCs from market, if docked at one.\n\n" +
164                                "Ordnance from your cargo will be preferred if that option is checked and if the alternatives are of equal quality."));
165                options.add(new AutofitOption(BUY_FROM_BLACK_MARKET, "Allow black market purchases", true, 
166                                "Buy weapons and fighter LPCs from the black market.\n\n" +
167                                "Non-black-market options will be preferred if the alternatives are of equal quality."));
168                options.add(new AutofitOption(UPGRADE, "Upgrade weapons using extra OP", false, 
169                                "Use weapons better than the ones specified in the goal variant, if there are ordnance points left to mount them.\n\n" +
170                                "Will add flux vents and capacitors up to the number specified in the goal variant first, " +
171                                "then upgrade weapons, and then add more vents and some common hullmods.\n\n" +
172                                "Leaving some unspent ordnance points in a goal variant can help take advantage of this option."));
173                options.add(new AutofitOption(STRIP, "Strip before autofitting", true, 
174                                "Remove everything possible prior to autofitting; generally results in a better fit.\n\n" +
175                                "However, refitting outside of port reduces a ship's combat readiness, and this option tends to lead to more changes and more readiness lost."));
176                options.add(new AutofitOption(ALWAYS_REINFORCED_HULL, "Always add \"Reinforced Bulkheads\"", false, 
177                                "Prioritizes installing the \"Reinforced Bulkheads\" hullmod, which increases hull integrity and " +
178                                "makes a ship virtually certain to be recoverable if lost in battle.\n\n" +
179                                "\"Reinforced Bulkheads\" may still be added if this option isn't checked, provided there are enough ordnance points."));
180                options.add(new AutofitOption(ALWAYS_BLAST_DOORS, "Always add \"Blast Doors\"", false, 
181                                "Prioritizes installing the \"Blast Doors\" hullmod, which increases hull integrity and " +
182                                "greatly reduces crew losses suffered due to hull damage.\n\n" +
183                                "\"Blast Doors\" may still be added if this option isn't checked, provided there are enough ordnance points."));
184                options.add(new AutofitOption(RANDOMIZE, "Randomize weapons and hullmods", false, 
185                                "Makes the loadout only loosely based on the goal variant."));
186                
187                
188                //reusableCategories = null;
189                if (reusableCategories != null) {
190                        categories = reusableCategories;
191                } else {
192                        new Category(KINETIC, categories).addFallback(KINETIC, ENERGY, HE, BEAM, PD, ROCKET, MISSILE, UTILITY, STRIKE);
193                        new Category(HE, categories).addFallback(HE, ENERGY, KINETIC, BEAM, PD, ROCKET, MISSILE, UTILITY, STRIKE);
194                        new Category(ENERGY, categories).addFallback(ENERGY, KINETIC, HE, BEAM, PD, ROCKET, MISSILE, UTILITY, STRIKE);
195                        new Category(PD, categories).addFallback(PD, BEAM, HE, KINETIC, UTILITY, ROCKET, MISSILE, STRIKE);
196                        new Category(BEAM, categories).addFallback(BEAM, ENERGY, HE, KINETIC, ROCKET, MISSILE, UTILITY, STRIKE);
197                        
198                        new Category(STRIKE, categories).addFallback(STRIKE, MISSILE, ROCKET, HE, ENERGY, KINETIC, UTILITY, BEAM, PD);
199                        new Category(MISSILE, categories).addFallback(MISSILE, STRIKE, ROCKET, HE, ENERGY, KINETIC, UTILITY, BEAM, PD);
200                        new Category(UTILITY, categories).addFallback(UTILITY, MISSILE, ROCKET, STRIKE, HE, KINETIC, ENERGY, BEAM, PD);
201                        new Category(ROCKET, categories).addFallback(ROCKET, UTILITY, MISSILE, STRIKE, HE, ENERGY, KINETIC, BEAM, PD);
202                        
203                        new Category(INTERCEPTOR, categories).addFallback(INTERCEPTOR, FIGHTER, SUPPORT, BOMBER);
204                        new Category(BOMBER, categories).addFallback(BOMBER, FIGHTER, INTERCEPTOR, SUPPORT);
205                        new Category(FIGHTER, categories).addFallback(FIGHTER, INTERCEPTOR, BOMBER, SUPPORT);
206                        new Category(SUPPORT, categories).addFallback(SUPPORT, INTERCEPTOR, FIGHTER, BOMBER);
207                        
208                        reusableCategories = categories;
209                }
210                
211                
212                //RANDOMIZE_CHANCE = 0.5f;
213                //RANDOMIZE_CHANCE = 1f;
214                
215                //if (random == null) random = new Random();
216        }
217        
218        
219        protected void stripWeapons(ShipVariantAPI current, AutofitPluginDelegate delegate) {
220                for (String id : current.getFittedWeaponSlots()) {
221                        WeaponSlotAPI slot = current.getSlot(id);
222                        if (slot.isDecorative() || slot.isBuiltIn() || slot.isHidden() ||
223                                        slot.isSystemSlot() || slot.isStationModule()) continue;
224                        clearWeaponSlot(slot, delegate, current);
225                }
226        }
227        
228        protected void stripFighters(ShipVariantAPI current, AutofitPluginDelegate delegate) {
229                int numBays = 20; // well above whatever it might actually be
230                for (int i = 0; i < numBays; i++) {
231                        if (current.getWingId(i) != null) {
232                                clearFighterSlot(i, delegate, current);
233                        }
234                }
235        }
236        
237//      protected Map<WeaponSlotAPI, AvailableWeapon> fittedWeapons = new HashMap<WeaponSlotAPI, AvailableWeapon>();
238//      protected Map<Integer, AvailableFighter> fittedFighters = new HashMap<Integer, AvailableFighter>();
239        protected Map<String, AvailableWeapon> fittedWeapons = new HashMap<String, AvailableWeapon>();
240        protected Map<String, AvailableFighter> fittedFighters = new HashMap<String, AvailableFighter>();
241        
242        
243        public int getCreditCost() {
244                int cost = 0;
245                for (AvailableWeapon w : fittedWeapons.values()) {
246                        cost += w.getPrice();
247                }
248                for (AvailableFighter w : fittedFighters.values()) {
249                        cost += w.getPrice();
250                }
251                return cost;
252        }
253        
254        protected Set<String> availableMods;
255        protected Set<String> slotsToSkip = new HashSet<String>();
256        protected Set<Integer> baysToSkip = new HashSet<Integer>();
257        protected boolean fittingModule = false;
258        protected int missilesWithAmmoOnCurrent = 0;
259        public void doFit(ShipVariantAPI current, ShipVariantAPI target, int maxSMods, AutofitPluginDelegate delegate) {
260                
261                
262//              if (stats == null) {
263//                      stats = Global.getFactory().createPerson().getStats();
264//                      stats.getShipOrdnancePointBonus().modifyPercent("test", 10f);
265//                      stats.getMaxVentsBonus().modifyPercent("test", 20f);
266//                      stats.getMaxCapacitorsBonus().modifyPercent("test", 20f);
267//              }
268                boolean player = fleetCommander != null && fleetCommander.isPlayer();
269                
270                if (!fittingModule) {
271                        fittedWeapons.clear();
272                        fittedFighters.clear();
273                        
274                        randomize = isChecked(RANDOMIZE);
275                        
276                        availableMods = new LinkedHashSet<String>(delegate.getAvailableHullmods());
277                }
278                
279//              if (fittingModule && current.getHullSpec().getHullId().equals("module_hightech_hangar")) {
280//                      System.out.println("wfweffewfew");
281//              }
282                
283                current.setMayAutoAssignWeapons(false);
284                current.getStationModules().putAll(target.getStationModules());
285
286                int index = 0;
287                for (String slotId : current.getStationModules().keySet()) {
288                        ShipVariantAPI moduleCurrent = current.getModuleVariant(slotId);
289                        boolean forceClone = false;
290                        if (moduleCurrent == null) {
291                                // when the target variant is not stock and has custom variants for the modules, grab them
292                                forceClone = true;
293                                moduleCurrent = target.getModuleVariant(slotId);
294                                //continue;
295                        }
296                        if (moduleCurrent == null) {
297                                String variantId = current.getHullVariantId();
298                                throw new RuntimeException("Module variant for slotId [" + slotId + "] not found for " +
299                                                                                  "variantId [" + variantId + "] of hull [" + current.getHullSpec().getHullId() + "]");
300                                //continue;
301                        }
302                        if (moduleCurrent.isStockVariant() || forceClone) {
303                                moduleCurrent = moduleCurrent.clone();
304                                moduleCurrent.setSource(VariantSource.REFIT);
305                                if (!forceClone) {
306                                        moduleCurrent.setHullVariantId(moduleCurrent.getHullVariantId() + "_" + index);
307                                }
308                        }
309                        index++;
310                        
311//                              String variantId = current.getStationModules().get(slotId);
312//                              ShipVariantAPI moduleTarget = Global.getSettings().getVariant(variantId);
313                        ShipVariantAPI moduleTarget = target.getModuleVariant(slotId);
314                        if (moduleTarget == null) continue;
315                        
316                        fittingModule = true;
317                        doFit(moduleCurrent, moduleTarget, 0, delegate);
318                        fittingModule = false;
319                        
320                        current.setModuleVariant(slotId, moduleCurrent);
321                }
322                current.setSource(VariantSource.REFIT);
323                
324                weaponFilterSeed = random.nextLong();
325                
326                emptyWingTarget = null;
327                if (delegate.getAvailableFighters().size() > 0) {
328                        emptyWingTarget = delegate.getAvailableFighters().get(random.nextInt(delegate.getAvailableFighters().size())).getId();
329                }
330                
331                altWeaponCats.clear();
332                altFighterCats.clear();
333                
334                slotsToSkip.clear();
335                baysToSkip.clear();
336                
337                missilesWithAmmoOnCurrent = 0;
338                
339                boolean strip = isChecked(STRIP);
340                if (strip) {
341                        stripWeapons(current, delegate);
342                        stripFighters(current, delegate);
343                        
344                        current.setNumFluxCapacitors(0);
345                        current.setNumFluxVents(0);
346                        if (delegate.isPlayerCampaignRefit()) {
347                                for (String modId : current.getNonBuiltInHullmods()) {
348                                        boolean canRemove = delegate.canAddRemoveHullmodInPlayerCampaignRefit(modId);
349                                        if (canRemove) {
350                                                current.removeMod(modId);
351                                        }
352                                }       
353                        } else {
354                                current.clearHullMods();
355                        }
356                        if (!fittingModule) {
357                                delegate.syncUIWithVariant(current);
358                        }
359                } else {
360                        slotsToSkip.addAll(current.getFittedWeaponSlots()); 
361                        for (int i = 0; i < 20; i++) {
362                                String wingId = current.getWingId(i);
363                                if (wingId != null && !wingId.isEmpty()) {
364                                        baysToSkip.add(i);
365                                }
366                        }
367                }
368                
369                //boolean randomize = isChecked(RANDOMIZE);
370                
371                
372                boolean reinforcedHull = isChecked(ALWAYS_REINFORCED_HULL);
373                boolean blastDoors = isChecked(ALWAYS_BLAST_DOORS);
374                
375                if (reinforcedHull) {
376                        addHullmods(current, delegate, HullMods.REINFORCEDHULL);
377                }
378                if (blastDoors) {
379                        addHullmods(current, delegate, HullMods.BLAST_DOORS);
380                }
381
382                List<String> targetMods = new ArrayList<String>();
383                for (String id : target.getSMods()) {
384                        if (target.getSModdedBuiltIns().contains(id)) continue;
385                        targetMods.add(id);
386                }
387                for (String id : target.getNonBuiltInHullmods()) {
388                        //if (HullMods.FLUX_DISTRIBUTOR.equals(id) || HullMods.FLUX_COIL.equals(id)) continue;
389                        targetMods.add(id);
390                }
391                if (!targetMods.isEmpty()) {
392                        addHullmods(current, delegate, targetMods.toArray(new String[0]));
393                }
394                
395                int addedRandomHullmodPts = 0;
396                if (randomize) {
397                        addedRandomHullmodPts = addRandomizedHullmodsPre(current, delegate);
398                }
399                
400                
401                fitFighters(current, target, false, delegate);
402                fitWeapons(current, target, false, delegate);
403
404                if (current.hasHullMod(HullMods.FRAGILE_SUBSYSTEMS) && 
405                                (current.getHullSize() == HullSize.FRIGATE || current.getHullSize() == HullSize.DESTROYER)) {
406                        addHullmods(current, delegate, HullMods.HARDENED_SUBSYSTEMS);
407                }
408                
409                
410                float addedMax = current.getHullSpec().getOrdnancePoints(stats) * 0.1f;
411                if (randomize && addedRandomHullmodPts <= addedMax) {
412                        addRandomizedHullmodsPost(current, delegate);
413                }
414                
415                float ventsCapsFraction = 1f;
416                boolean upgrade = isChecked(UPGRADE);
417                if (upgrade) {
418                        ventsCapsFraction = 0.5f;
419                        //ventsCapsFraction = 0f;
420                }
421                
422                addVentsAndCaps(current, target, ventsCapsFraction);
423
424                
425                // now that we're at the target level of vents and caps
426                // see if we can upgrade some weapons
427                if (upgrade) {
428                        fitFighters(current, target, true, delegate);
429                        fitWeapons(current, target, true, delegate);
430                        addVentsAndCaps(current, target, 1f - ventsCapsFraction);
431                }
432                
433//              float dissipation = current.getHullSpec().getFluxDissipation() + current.getNumFluxVents() * 10f;
434//              float generation = 0f;
435//              for (String slotId : current.getFittedWeaponSlots()) {
436//                      WeaponSpecAPI spec = current.getWeaponSpec(slotId);
437//                      generation += spec.getDerivedStats().getSustainedFluxPerSecond();
438//              }
439                
440                addExtraVentsAndCaps(current, target);
441                addHullmods(current, delegate, HullMods.REINFORCEDHULL, HullMods.BLAST_DOORS, HullMods.HARDENED_SUBSYSTEMS);
442                addModsWithSpareOPIfAny(current, target, false, delegate);
443                
444                //maxSMods = 2;
445                if (maxSMods > 0) {
446                        int added = convertToSMods(current, maxSMods);
447                        addExtraVents(current);
448                        addExtraCaps(current);
449                        //addHullmods(current, delegate, HullMods.FLUX_DISTRIBUTOR, HullMods.FLUX_COIL);
450                        if (!current.hasHullMod(HullMods.FLUX_DISTRIBUTOR)) {
451                                addDistributor(current, delegate);
452                        }
453                        if (!current.hasHullMod(HullMods.FLUX_COIL)) {
454                                addCoil(current, delegate);
455                        }
456                        //addModsWithSpareOPIfAny(current, target, true, delegate);
457                        //addHullmods(current, delegate, HullMods.FLUX_DISTRIBUTOR, HullMods.FLUX_COIL);
458                        if (current.getHullSize() == HullSize.FRIGATE || current.hasHullMod(HullMods.SAFETYOVERRIDES)) {
459                                addHullmods(current, delegate, HullMods.HARDENED_SUBSYSTEMS, HullMods.REINFORCEDHULL, HullMods.BLAST_DOORS);
460                        } else {
461                                addHullmods(current, delegate, HullMods.REINFORCEDHULL, HullMods.BLAST_DOORS, HullMods.HARDENED_SUBSYSTEMS);
462                        }
463                        int remaining = maxSMods - added;
464                        if (remaining > 0) {
465                                List<String> mods = new ArrayList<String>();
466                                mods.add(HullMods.FLUX_DISTRIBUTOR);
467                                mods.add(HullMods.FLUX_COIL);
468                                if (current.getHullSize() == HullSize.FRIGATE || current.hasHullMod(HullMods.SAFETYOVERRIDES)) {
469                                        mods.add(HullMods.HARDENED_SUBSYSTEMS);
470                                        mods.add(HullMods.REINFORCEDHULL);
471                                } else {
472                                        mods.add(HullMods.REINFORCEDHULL);
473                                        mods.add(HullMods.HARDENED_SUBSYSTEMS);
474                                }
475                                mods.add(HullMods.BLAST_DOORS);
476                                Iterator<String> iter = mods.iterator();
477                                while (iter.hasNext()) {
478                                        String modId = iter.next();
479                                        if (current.getPermaMods().contains(modId)) {
480                                                iter.remove();
481                                        }
482                                }
483//                              while (!mods.isEmpty() && current.hasHullMod(mods.get(0))) {
484//                                      mods.remove(0);
485//                              }
486                                for (int i = 0; i < remaining && !mods.isEmpty(); i++) {
487                                        current.setNumFluxCapacitors(0);
488                                        current.setNumFluxVents(0);
489                                        String modId = mods.get(Math.min(i, mods.size() - 1));
490                                        addHullmods(current, delegate, modId);
491                                        convertToSMods(current, 1);
492//                                      addExtraVents(current);
493//                                      addExtraCaps(current);
494                                }
495                        }
496                }
497                
498                
499                if (current.getHullSpec().isPhase()) {
500                        addExtraCaps(current);
501                } else {
502                        addExtraVents(current);
503                }
504                
505                addHullmods(current, delegate, HullMods.ARMOREDWEAPONS);
506                int opCost = current.computeOPCost(stats);
507                int opMax = current.getHullSpec().getOrdnancePoints(stats);
508                int opLeft = opMax - opCost;
509                if (opLeft > 0) {
510                        addRandomizedHullmodsPost(current, delegate);
511                }
512                
513                if (current.getHullSpec().isPhase()) {
514                        addExtraVents(current);
515                } else {
516                        addExtraCaps(current);
517                }
518                
519                
520                current.setVariantDisplayName(target.getDisplayName());
521                
522                current.getWeaponGroups().clear();
523                for (WeaponGroupSpec group : target.getWeaponGroups()) {
524                        WeaponGroupSpec copy = new WeaponGroupSpec(group.getType());
525                        copy.setAutofireOnByDefault(group.isAutofireOnByDefault());
526                        for (String slotId : group.getSlots()) {
527                                if (current.getWeaponId(slotId) != null) {
528                                        copy.addSlot(slotId);
529                                }
530                        }
531                        if (!copy.getSlots().isEmpty()) {
532                                current.addWeaponGroup(copy);
533                        }
534                }
535                
536                if (player) {
537                        if (current.getWeaponGroups().isEmpty() || randomize || current.hasUnassignedWeapons()) {
538                                current.setMayAutoAssignWeapons(true);
539                                current.autoGenerateWeaponGroups();
540                        }
541                        //current.assignUnassignedWeapons();                    
542                } else {
543                        current.getWeaponGroups().clear(); // will get auto-assigned when deployed in combat; until then don't care
544                }
545                
546                if (!fittingModule) {
547                        delegate.syncUIWithVariant(current);
548                }
549        }
550        
551        protected int convertToSMods(ShipVariantAPI current, int num) {
552                if (num <= 0) return 0;
553                
554                List<HullModSpecAPI> mods = new ArrayList<HullModSpecAPI>(); 
555                for (String id : current.getHullMods()) {
556                        if (current.getPermaMods().contains(id)) continue;
557                        if (current.getHullSpec().getBuiltInMods().contains(id)) continue;
558                        HullModSpecAPI mod = DModManager.getMod(id);
559                        if (mod.hasTag(Tags.HULLMOD_NO_BUILD_IN)) continue;
560                        mods.add(mod);
561                }
562                
563                final HullSize size = current.getHullSize();
564                Collections.sort(mods, new Comparator<HullModSpecAPI>() {
565                        public int compare(HullModSpecAPI o1, HullModSpecAPI o2) {
566                                return Misc.getOPCost(o2, size) - Misc.getOPCost(o1, size);
567                        }
568                });
569                
570                int count = 0;
571                for (int i = 0; i < num && i < mods.size(); i++) {
572                        String id = mods.get(i).getId();
573                        current.addPermaMod(id, true);
574                        count++;
575                }
576                return count;
577        }
578        
579        protected void addModsWithSpareOPIfAny(ShipVariantAPI current, ShipVariantAPI target, boolean sModMode, AutofitPluginDelegate delegate) {
580                int opCost = current.computeOPCost(stats);
581                int opMax = current.getHullSpec().getOrdnancePoints(stats);
582                int opLeft = opMax - opCost;
583        
584                if (opLeft <= 0) return;
585                
586                float total = target.getNumFluxVents() + target.getNumFluxCapacitors();
587                float ventsFraction = 1f;
588                if (total > 0) {
589                        ventsFraction = target.getNumFluxVents() / total;
590                }
591                
592                if (sModMode) {
593                        if (ventsFraction >= 0.5f) {
594                                addDistributorRemoveVentsIfNeeded(current, delegate);
595                                addCoilRemoveCapsIfNeeded(current, delegate);
596                        } else {
597                                addCoil(current, delegate);
598                                addCoilRemoveCapsIfNeeded(current, delegate);
599                        }
600                } else {
601                        if (ventsFraction >= 0.5f) {
602                                addDistributor(current, delegate);
603                                addCoil(current, delegate);
604                        } else {
605                                addCoil(current, delegate);
606                                addDistributor(current, delegate);
607                        }
608                }
609        }
610        
611        protected void addCoil(ShipVariantAPI current, AutofitPluginDelegate delegate) {
612                int opCost = current.computeOPCost(stats);
613                int opMax = current.getHullSpec().getOrdnancePoints(stats);
614                int opLeft = opMax - opCost;
615        
616                if (opLeft <= 0) return;
617                
618                int vents = current.getNumFluxVents();
619                
620                HullModSpecAPI coil = Misc.getMod(HullMods.FLUX_COIL);
621                int cost = coil.getCostFor(current.getHullSize());
622                
623                if (cost < opLeft + vents * 0.3f) {
624                        int remove = cost - opLeft;
625                        if (remove > 0) {
626                                opLeft -= addVents(-remove, current, 1000);
627                        }
628                        opLeft -= addModIfPossible(HullMods.FLUX_COIL, delegate, current, opLeft);
629                }
630        }
631        
632        protected void addCoilRemoveCapsIfNeeded(ShipVariantAPI current, AutofitPluginDelegate delegate) {
633                int opCost = current.computeOPCost(stats);
634                int opMax = current.getHullSpec().getOrdnancePoints(stats);
635                int opLeft = opMax - opCost;
636                
637                if (opLeft <= 0) return;
638                
639                int caps = current.getNumFluxCapacitors();
640                
641                HullModSpecAPI coil = Misc.getMod(HullMods.FLUX_COIL);
642                int cost = coil.getCostFor(current.getHullSize());
643                
644                if (cost < opLeft + caps * 0.3f) {
645                        int remove = cost - opLeft;
646                        if (remove > 0) {
647                                opLeft -= addCapacitors(-remove, current, 1000);
648                        }
649                        opLeft -= addModIfPossible(HullMods.FLUX_COIL, delegate, current, opLeft);
650                }
651        }
652        
653        protected void addDistributor(ShipVariantAPI current, AutofitPluginDelegate delegate) {
654                int opCost = current.computeOPCost(stats);
655                int opMax = current.getHullSpec().getOrdnancePoints(stats);
656                int opLeft = opMax - opCost;
657                
658                if (opLeft <= 0) return;
659                
660                int caps = current.getNumFluxCapacitors();
661                
662                HullModSpecAPI distributor = Misc.getMod(HullMods.FLUX_DISTRIBUTOR);
663                int cost = distributor.getCostFor(current.getHullSize());
664                
665                if (cost <= opLeft + caps * 0.3f) {
666                        int remove = cost - opLeft;
667                        if (remove > 0) {
668                                opLeft -= addCapacitors(-remove, current, 1000);
669                        }
670                        opLeft -= addModIfPossible(HullMods.FLUX_DISTRIBUTOR, delegate, current, opLeft);
671                }
672        }
673        
674        protected void addDistributorRemoveVentsIfNeeded(ShipVariantAPI current, AutofitPluginDelegate delegate) {
675                int opCost = current.computeOPCost(stats);
676                int opMax = current.getHullSpec().getOrdnancePoints(stats);
677                int opLeft = opMax - opCost;
678                
679                if (opLeft <= 0) return;
680                
681                int vents = current.getNumFluxVents();
682                
683                HullModSpecAPI distributor = Misc.getMod(HullMods.FLUX_DISTRIBUTOR);
684                int cost = distributor.getCostFor(current.getHullSize());
685                
686                if (cost <= opLeft + vents * 0.3f) {
687                        int remove = cost - opLeft;
688                        if (remove > 0) {
689                                opLeft -= addVents(-remove, current, 1000);
690                        }
691                        opLeft -= addModIfPossible(HullMods.FLUX_DISTRIBUTOR, delegate, current, opLeft);
692                }
693        }
694
695        
696        
697        protected List<AvailableWeapon> getWeapons(AutofitPluginDelegate delegate) {
698                boolean buy = isChecked(BUY_FROM_MARKET);
699                boolean storage = isChecked(USE_FROM_STORAGE);
700                boolean useCargo = isChecked(USE_FROM_CARGO);
701                boolean useBlack = isChecked(BUY_FROM_BLACK_MARKET);
702                
703                List<AvailableWeapon> weapons = new ArrayList<AvailableWeapon>(delegate.getAvailableWeapons());
704                
705                Iterator<AvailableWeapon> iter = weapons.iterator();
706                while (iter.hasNext()) {
707                        AvailableWeapon w = iter.next();
708                        if ((!buy && w.getPrice() > 0) ||
709                                (!storage && w.getPrice() <= 0 && w.getSubmarket() != null) ||
710                                (!useCargo && w.getSubmarket() == null) ||
711                                (!useBlack && w.getSubmarket() != null && w.getSubmarket().getPlugin().isBlackMarket())) {
712                                iter.remove();
713                        }
714                }
715                return weapons;
716        }
717        
718        protected List<AvailableFighter> getFighters(AutofitPluginDelegate delegate) {
719                boolean buy = isChecked(BUY_FROM_MARKET);
720                boolean storage = isChecked(USE_FROM_STORAGE);
721                boolean useCargo = isChecked(USE_FROM_CARGO);
722                boolean useBlack = isChecked(BUY_FROM_BLACK_MARKET);
723                
724                boolean automated = Misc.isAutomated(delegate.getShip());
725                List<AvailableFighter> fighters = new ArrayList<AvailableFighter>(delegate.getAvailableFighters());
726                Iterator<AvailableFighter> iter = fighters.iterator();
727                while (iter.hasNext()) {
728                        AvailableFighter f = iter.next();
729                        if ((!buy && f.getPrice() > 0) ||
730                                (automated && !f.getWingSpec().hasTag(Tags.AUTOMATED_FIGHTER)) ||
731                                (!storage && f.getPrice() <= 0 && f.getSubmarket() != null) ||
732                                (!useCargo && f.getSubmarket() == null) ||
733                                (!useBlack && f.getSubmarket() != null && f.getSubmarket().getPlugin().isBlackMarket())) {
734                                iter.remove();
735                        }
736                }
737                return fighters;
738        }
739        
740        public int addHullmods(ShipVariantAPI current, AutofitPluginDelegate delegate, String ... mods) {
741                if (fittingModule) return 0;
742                
743                int opCost = current.computeOPCost(stats);
744                int opMax = current.getHullSpec().getOrdnancePoints(stats);
745                int opLeft = opMax - opCost;
746                
747                int addedTotal = 0;
748                for (String mod : mods) {
749                        if (current.hasHullMod(mod)) continue;
750//                      if (mod.equals(HullMods.INTEGRATED_TARGETING_UNIT)) {
751//                              System.out.println("wefwefwefe");
752//                      }
753                        if (!availableMods.contains(mod)) {
754                                if (mod.equals(HullMods.INTEGRATED_TARGETING_UNIT) && 
755                                                current.getHullSize().ordinal() >= HullSize.CRUISER.ordinal()) {
756                                        mod = HullMods.DEDICATED_TARGETING_CORE;
757                                } else {
758                                        continue;
759                                }
760                        }
761                        
762                        if (mod.equals(HullMods.DEDICATED_TARGETING_CORE) && 
763                                        availableMods.contains(HullMods.INTEGRATED_TARGETING_UNIT)) {
764                                mod = HullMods.INTEGRATED_TARGETING_UNIT;
765                        }
766                        
767                        HullModSpecAPI modSpec = Misc.getMod(mod);
768                        
769                        if (mod.equals(HullMods.INTEGRATED_TARGETING_UNIT) && 
770                                        current.hasHullMod(HullMods.DEDICATED_TARGETING_CORE)) {
771                                current.removeMod(HullMods.DEDICATED_TARGETING_CORE);
772                                HullModSpecAPI dtc = Misc.getMod(HullMods.DEDICATED_TARGETING_CORE);
773                                int cost = dtc.getCostFor(current.getHullSize());;
774                                addedTotal -= cost;
775                                opLeft += cost;
776                        }
777                        
778                        
779                        if (current.hasHullMod(HullMods.ADVANCED_TARGETING_CORE) || current.hasHullMod(HullMods.DISTRIBUTED_FIRE_CONTROL)) {
780                                if (mod.equals(HullMods.INTEGRATED_TARGETING_UNIT)) {
781                                        continue;
782                                }
783                                if (mod.equals(HullMods.DEDICATED_TARGETING_CORE)) {
784                                        continue;
785                                }
786                        }
787                        
788                        if (current.getHullSpec().isPhase()) {
789                                if (modSpec.hasTag(HullMods.TAG_NON_PHASE)) {
790                                        continue;
791                                }
792                        }
793                        if (!current.getHullSpec().isPhase()) {
794                                if (modSpec.hasTag(HullMods.TAG_PHASE)) {
795                                        continue;
796                                }
797                        }
798                        
799                        int cost = addModIfPossible(modSpec, delegate, current, opLeft);;
800                        //int cost = addModIfPossible(mod, delegate, current, opLeft);
801                        
802                        opLeft -= cost;
803                        addedTotal += cost;
804                }
805                return addedTotal;
806        }
807        
808        public int addModIfPossible(String id, AutofitPluginDelegate delegate, ShipVariantAPI current, int opLeft) {
809                if (current.hasHullMod(id)) return 0;
810                if (delegate.isPlayerCampaignRefit() && !delegate.canAddRemoveHullmodInPlayerCampaignRefit(id)) return 0;
811                
812                HullModSpecAPI mod = Misc.getMod(id);
813                return addModIfPossible(mod, delegate, current, opLeft);
814        }
815        
816        public int addModIfPossible(HullModSpecAPI mod, AutofitPluginDelegate delegate, ShipVariantAPI current, int opLeft) {
817                if (mod == null) return 0;
818                
819                if (current.hasHullMod(mod.getId())) return 0;
820                if (delegate.isPlayerCampaignRefit() && !delegate.canAddRemoveHullmodInPlayerCampaignRefit(mod.getId())) return 0;
821                
822                
823                int cost = mod.getCostFor(current.getHullSize());
824                if (cost > opLeft) return 0;
825
826                ShipAPI ship = delegate.getShip();
827                ShipVariantAPI orig = null;
828                // why is this commented out? It fixes an issue with logistics hullmods not being properly applied
829                // if the current variant already has some
830                // but probably? causes some other issues
831                // possibly: it was not setting the orig variant back when returning 0; this is now fixed
832                if (ship != null) {
833                        orig = ship.getVariant();
834                        ship.setVariantForHullmodCheckOnly(current);
835                }
836                if (ship != null && mod.getEffect() != null && ship.getVariant() != null && !mod.getEffect().isApplicableToShip(ship)
837                                && !ship.getVariant().hasHullMod(mod.getId())) {
838                        if (orig != null) {
839                                ship.setVariantForHullmodCheckOnly(orig);
840                        }
841                        return 0;
842                }
843                
844                boolean hasItemIfAny = HullModItemManager.getInstance().isRequiredItemAvailable(mod.getId(), 
845                                                                delegate.getFleetMember(), current, delegate.getMarket());
846                if (!hasItemIfAny) {
847                        if (orig != null) {
848                                ship.setVariantForHullmodCheckOnly(orig);
849                        }
850                        return 0;
851                }
852                
853                
854                if (orig != null && ship != null) {
855                        ship.setVariantForHullmodCheckOnly(orig);
856                }
857                
858                current.addMod(mod.getId());
859                
860                if (ship != null && mod.getId() != null && mod.getEffect() != null) {
861                        if (!mod.hasTag(Tags.DO_NOT_APPLY_HULLMOD_DURING_AUTOFIT)) {
862                                mod.getEffect().applyEffectsBeforeShipCreation(ship.getHullSize(), ship.getMutableStats(), mod.getId());
863                                mod.getEffect().applyEffectsAfterShipCreation(ship, mod.getId());
864                        }
865                }
866                return cost;
867        }
868        
869        
870        
871        public void addVentsAndCaps(ShipVariantAPI current, ShipVariantAPI target, float fraction) {
872                if (fraction < 0) return;
873                
874                int opCost = current.computeOPCost(stats);
875                int opMax = current.getHullSpec().getOrdnancePoints(stats);
876                int opLeft = opMax - opCost;
877                
878                int maxVents = getMaxVents(current.getHullSize());
879                int maxCapacitors = getMaxCaps(current.getHullSize());
880                
881                int add = Math.max((int)Math.ceil(target.getNumFluxVents() * fraction) - current.getNumFluxVents(), 0);
882                if (add > opLeft) add = opLeft;
883                opLeft -= addVents(add, current, maxVents);
884                
885                add = Math.max((int)Math.ceil(target.getNumFluxCapacitors() * fraction) - current.getNumFluxCapacitors(), 0);
886                if (add > opLeft) add = opLeft;
887                opLeft -= addCapacitors(add, current, maxCapacitors);
888        }
889        
890        public void addExtraVents(ShipVariantAPI current) {
891                int opCost = current.computeOPCost(stats);
892                int opMax = current.getHullSpec().getOrdnancePoints(stats);
893                int opLeft = opMax - opCost;
894                
895                if (opLeft > 0) {
896                        int maxVents = getMaxVents(current.getHullSize());
897                        opLeft -= addVents((int) opLeft, current, maxVents);
898                }
899        }
900        
901        public void addExtraCaps(ShipVariantAPI current) {
902                int opCost = current.computeOPCost(stats);
903                int opMax = current.getHullSpec().getOrdnancePoints(stats);
904                int opLeft = opMax - opCost;
905                
906                if (opLeft > 0) {
907                        int maxCaps = getMaxCaps(current.getHullSize());
908                        opLeft -= addCapacitors((int) opLeft, current, maxCaps);
909                }
910        }
911        
912        public void addExtraVentsAndCaps(ShipVariantAPI current, ShipVariantAPI target) {
913                int opCost = current.computeOPCost(stats);
914                int opMax = current.getHullSpec().getOrdnancePoints(stats);
915                int opLeft = opMax - opCost;
916                
917                int maxVents = getMaxVents(current.getHullSize());
918                int maxCapacitors = getMaxCaps(current.getHullSize());
919                if (opLeft > 0) {
920                        
921                        float total = current.getNumFluxVents() + current.getNumFluxCapacitors();
922                        float ventsFraction = 1f;
923                        if (total > 0) {
924                                ventsFraction = current.getNumFluxVents() / total;
925                        }
926                        
927                        int add = (int) (opLeft * ventsFraction);
928                        opLeft -= addVents(add, current, maxVents);
929                        add = opLeft;
930                        opLeft -= addCapacitors(add, current, maxCapacitors);
931                        
932                        add = opLeft;
933                        opLeft -= addVents(add, current, maxVents);
934                
935                        // if we ended up with more capacitors than desired, move some of them to vents
936                        if (target != null) {
937                                float targetVents = target.getNumFluxVents();
938                                float targetCaps = target.getNumFluxCapacitors();
939                                
940                                if (targetVents > targetCaps || targetVents >= maxVents) {
941                                        float currVents = current.getNumFluxVents();
942                                        float currCaps = current.getNumFluxCapacitors();
943                                        float currTotal = currVents + currCaps;
944                                        
945                                        int currVentsDesired = (int) (currVents + currCaps * 0.5f);
946                                        if (currVentsDesired > maxVents) currVentsDesired = maxVents;
947                                        int currCapsDesired = (int) (currTotal - currVentsDesired);
948                                        if (currCapsDesired > maxCapacitors) currCapsDesired = maxCapacitors;
949                                        current.setNumFluxVents(currVentsDesired);
950                                        current.setNumFluxCapacitors(currCapsDesired);
951                                }
952                                
953        //                      if (targetVents > 0 && currVents + currCaps > 0) {
954        //                              float ratioTarget = targetVents / (targetVents + targetCaps);
955        //                              float ratioCurr = currVents / (currVents + currCaps);
956        //                              if (ratioTarget > ratioCurr) {
957        //                                      float currTotal = currVents + currCaps;
958        //                                      int currVentsDesired = (int) (ratioTarget * currTotal);
959        //                                      if (currVentsDesired > maxVents) currVentsDesired = maxVents;
960        //                                      int currCapsDesired = (int) (currTotal - currVents);
961        //                                      if (currCapsDesired > maxCapacitors) currCapsDesired = maxCapacitors;
962        //                                      current.setNumFluxVents(currVentsDesired);
963        //                                      current.setNumFluxCapacitors(currCapsDesired);
964        //                              }
965        //                      }
966                        }
967                }
968                
969        }
970        
971        public int getMaxVents(HullSize size) {
972                int maxVents = getBaseMax(size);
973                if (stats != null) {
974                        maxVents = (int) stats.getMaxVentsBonus().computeEffective(maxVents);
975                }
976                return maxVents;
977        }
978        
979        public int getMaxCaps(HullSize size) {
980                int maxCapacitors = getBaseMax(size);
981                if (stats != null) {
982                        maxCapacitors = (int) stats.getMaxCapacitorsBonus().computeEffective(maxCapacitors);
983                }
984                return maxCapacitors;
985        }
986        
987        public static int getBaseMax(HullSize size) {
988                int max = 100;
989                switch (size) {
990                case CAPITAL_SHIP: max = 50; break;
991                case CRUISER: max = 30; break;
992                case DESTROYER: max = 20; break;
993                case FRIGATE: max = 10; break;
994                case FIGHTER: max = 5; break;
995                }
996                return max;
997        }
998        
999        public int addVents(int add, ShipVariantAPI current, int max) {
1000                int target = current.getNumFluxVents() + add;
1001                if (target > max) target = max;
1002                if (target < 0) target = 0;
1003                int actual = target - current.getNumFluxVents();
1004                current.setNumFluxVents(target);
1005                return actual;
1006        }
1007        
1008        public int addCapacitors(int add, ShipVariantAPI current, int max) {
1009                int target = current.getNumFluxCapacitors() + add;
1010                if (target > max) target = max;
1011                if (target < 0) target = 0;
1012                int actual = target - current.getNumFluxCapacitors();
1013                current.setNumFluxCapacitors(target);
1014                return actual;
1015        }
1016
1017        public void clearWeaponSlot(WeaponSlotAPI slot, AutofitPluginDelegate delegate, ShipVariantAPI variant) {
1018                fittedWeapons.remove(variant.getHullVariantId() + "_" + slot.getId());
1019                delegate.clearWeaponSlot(slot, variant);
1020        }
1021        
1022        public void clearFighterSlot(int index, AutofitPluginDelegate delegate, ShipVariantAPI variant) {
1023                fittedFighters.remove(variant.getHullVariantId() + "_" + index);
1024                delegate.clearFighterSlot(index, variant);
1025        }
1026
1027        public void fitWeapons(ShipVariantAPI current, ShipVariantAPI target, boolean upgradeMode, AutofitPluginDelegate delegate) {
1028                
1029                //upgradeMode = false;
1030                //boolean upgradeWhenNothingMatchingInPrimary = isChecked(UPGRADE);
1031                
1032                //boolean randomize = isChecked(RANDOMIZE);
1033                
1034                Set<String> alreadyUsed = new HashSet<String>();
1035                for (WeaponSlotAPI slot : getWeaponSlotsInPriorityOrder(current, target, upgradeMode)) {
1036                        if (slotsToSkip.contains(slot.getId())) continue;
1037                        
1038//                      if (slot.getId().equals("WS 004")) {
1039//                              System.out.println("wefwefwef");
1040//                      }
1041                        
1042                        float opCost = current.computeOPCost(stats);
1043                        float opMax = current.getHullSpec().getOrdnancePoints(stats);
1044                        float opLeft = opMax - opCost;
1045                        
1046                        float levelToBeat = -1;
1047                        if (upgradeMode) {
1048                                WeaponSpecAPI curr = current.getWeaponSpec(slot.getId());
1049                                if (curr != null) {
1050                                        float cost = curr.getOrdnancePointCost(stats, current.getStatsForOpCosts());
1051                                        opLeft += cost;
1052                                        
1053                                        for (String tag : curr.getTags()) {
1054                                                levelToBeat = Math.max(levelToBeat, getLevel(tag));
1055                                        }
1056                                        if (delegate.isPriority(curr)) {
1057                                                levelToBeat += PRIORITY;
1058                                        }
1059                                }
1060                        }
1061                        
1062                        WeaponSpecAPI desired = target.getWeaponSpec(slot.getId());
1063                        // shouldn't happen since it should be filtered out by getWeaponSlotsInPriorityOrder()
1064                        if (desired == null) continue;
1065                        
1066                        List<AvailableWeapon> weapons = getWeapons(delegate);
1067                        List<AvailableWeapon> possible = getPossibleWeapons(slot, desired, current, opLeft, weapons);
1068                        if (possible.isEmpty()) continue;
1069                        
1070//                      for (AvailableWeapon w : possible) {
1071//                              if (w.getSpec().getWeaponId().equals("harpoonpod")) {
1072//                                      System.out.println("wefwef");
1073//                              }
1074//                      }
1075                        
1076                        
1077                        List<String> categories = desired.getAutofitCategoriesInPriorityOrder(); 
1078                        List<String> alternate = altWeaponCats.get(desired);
1079                        RANDOMIZE_CHANCE = 1f;
1080                        if (false && randomize && (alternate != null || random.nextFloat() < RANDOMIZE_CHANCE)) {
1081                                if (alternate == null) {
1082                                        alternate = new ArrayList<String>();
1083                                        for (String cat : categories) {
1084                                                Category category = this.categories.get(cat);
1085                                                if (category == null) {
1086                                                        //System.out.println("ewfwefew");
1087                                                        continue;
1088                                                }
1089                                                if (!category.fallback.isEmpty()) {
1090                                                        int index = random.nextInt(category.fallback.size()/2) + 1;
1091                                                        //int index = random.nextInt(category.fallback.size());
1092                                                        if (index != 0) {
1093                                                                alternate.add(category.fallback.get(index));
1094                                                        }
1095                                                }
1096                                        }
1097                                        altWeaponCats.put(desired, alternate);
1098                                }
1099                                if (!alternate.isEmpty()) {
1100                                        categories = alternate;
1101                                }
1102                        } else if (randomize) {
1103                                altWeaponCats.put(desired, new ArrayList<String>());
1104                        }
1105                        
1106                        
1107                        AvailableWeapon pick = null;
1108                        for (String catId : categories) {
1109                                pick = getBestMatch(desired, upgradeMode, catId, alreadyUsed, possible, slot, delegate);
1110                                if (pick != null) {
1111                                        break;
1112                                }
1113                                if (upgradeMode) break; // don't pick from secondary categories when upgrading
1114                        }
1115                        
1116                        if (pick == null && !upgradeMode) {
1117                                OUTER: for (String catId : categories) {
1118                                        Category cat = this.categories.get(catId);
1119                                        if (cat == null) continue;
1120                                        
1121                                        for (String fallbackCatId : cat.fallback) {
1122                                                pick = getBestMatch(desired, true, fallbackCatId, alreadyUsed, possible, delegate);
1123                                                if (pick != null) {
1124                                                        break OUTER;
1125                                                }
1126                                        }
1127                                }
1128                        }
1129                        
1130                        if (pick != null) {
1131                                if (upgradeMode) {
1132                                        float pickLevel = -1;
1133                                        if (!categories.isEmpty()) {
1134                                                Category cat = this.categories.get(categories.get(0));
1135                                                if (cat != null) {
1136                                                        String tag = getCategoryTag(cat, pick.getSpec().getTags());
1137                                                        pickLevel = getLevel(tag);
1138                                                        if (delegate.isPriority(pick.getSpec())) {
1139                                                                pickLevel += PRIORITY;
1140                                                        }
1141                                                }
1142                                        }
1143                                        if (pickLevel <= levelToBeat) continue;
1144                                }
1145                                
1146                                alreadyUsed.add(pick.getId());
1147                                
1148                                clearWeaponSlot(slot, delegate, current);
1149                                delegate.fitWeaponInSlot(slot, pick, current);
1150                                fittedWeapons.put(current.getHullVariantId() + "_" + slot.getId(), pick);
1151                                
1152                                if (pick.getSpec().getType() == WeaponType.MISSILE && pick.getSpec().usesAmmo()) {
1153                                        missilesWithAmmoOnCurrent++;
1154                                }
1155                        }
1156                }
1157                
1158        }
1159        
1160        
1161        public void fitFighters(ShipVariantAPI current, ShipVariantAPI target, boolean upgradeMode, AutofitPluginDelegate delegate) {
1162                
1163                //boolean randomize = isChecked(RANDOMIZE);
1164                
1165                int numBays = Global.getSettings().computeNumFighterBays(current);
1166                
1167                Set<String> alreadyUsed = new HashSet<String>();
1168                
1169                for (int i = 0; i < numBays; i++) {
1170                        if (baysToSkip.contains(i)) continue;
1171                        
1172                        float opCost = current.computeOPCost(stats);
1173                        float opMax = current.getHullSpec().getOrdnancePoints(stats);
1174                        float opLeft = opMax - opCost;
1175                        
1176                        float levelToBeat = -1;
1177                        if (upgradeMode) {
1178                                FighterWingSpecAPI curr = current.getWing(i);
1179                                if (curr != null) {
1180                                        float cost = curr.getOpCost(current.getStatsForOpCosts());
1181                                        opLeft += cost;
1182                                        
1183                                        for (String tag : curr.getTags()) {
1184                                                levelToBeat = Math.max(levelToBeat, getLevel(tag));
1185                                        }
1186                                        if (delegate.isPriority(curr)) {
1187                                                levelToBeat += PRIORITY;
1188                                        }
1189                                }
1190                        } else {
1191                                if (current.getWingId(i) != null) {
1192                                        continue;
1193                                }
1194                        }
1195                        
1196                        List<AvailableFighter> fighters = getFighters(delegate);
1197                        List<AvailableFighter> possible = getPossibleFighters(current, opLeft, fighters);
1198                        if (possible.isEmpty()) continue;
1199                        
1200                        String desiredWingId = target.getWingId(i);
1201                        if (desiredWingId == null || desiredWingId.isEmpty()) {
1202                                if (randomize) {
1203                                        desiredWingId = emptyWingTarget;
1204                                } else {
1205                                        continue;
1206                                }
1207                        }
1208                        
1209                        FighterWingSpecAPI desired = Global.getSettings().getFighterWingSpec(desiredWingId);
1210                        if (desired == null) continue;
1211                        
1212                        //List<String> categories = getCategoriesInPriorityOrder(desired.getTags()); 
1213                        List<String> categories = desired.getAutofitCategoriesInPriorityOrder();
1214                        
1215                        List<String> alternate = altFighterCats.get(desired);
1216                        if (randomize && (alternate != null || random.nextFloat() < RANDOMIZE_CHANCE)) {
1217                                if (alternate == null) {
1218                                        alternate = new ArrayList<String>();
1219                                        for (String cat : categories) {
1220                                                Category category = this.categories.get(cat);
1221                                                if (category == null) {
1222                                                        //System.out.println("ewfwefew");
1223                                                        continue;
1224                                                }
1225                                                if (!category.fallback.isEmpty()) {
1226                                                        int index = random.nextInt(category.fallback.size() - 1) + 1;
1227                                                        if (index != 0) {
1228                                                                alternate.add(category.fallback.get(index));
1229                                                        }
1230                                                }
1231                                        }
1232                                        altFighterCats.put(desired, alternate);
1233                                }
1234                                if (!alternate.isEmpty()) {
1235                                        categories = alternate;
1236                                }
1237                        } else if (randomize) {
1238                                altFighterCats.put(desired, new ArrayList<String>());
1239                        }
1240                        
1241                        
1242                        AvailableFighter pick = null;
1243                        for (String catId : categories) {
1244                                pick = getBestMatch(desired, upgradeMode, catId, alreadyUsed, possible, delegate);
1245                                if (pick != null) {
1246                                        break;
1247                                }
1248                                if (upgradeMode) break; // don't pick from secondary categories when upgrading
1249                        }
1250                        
1251                        if (pick == null && !upgradeMode) {
1252                                OUTER: for (String catId : categories) {
1253                                        Category cat = this.categories.get(catId);
1254                                        if (cat == null) continue;
1255                                        
1256                                        for (String fallbackCatId : cat.fallback) {
1257                                                pick = getBestMatch(desired, true, fallbackCatId, alreadyUsed, possible, delegate);
1258                                                if (pick != null) {
1259                                                        break OUTER;
1260                                                }
1261                                        }
1262                                }
1263                        }
1264                        
1265                        if (pick != null) {
1266                                if (upgradeMode) {
1267                                        float pickLevel = -1;
1268                                        if (!categories.isEmpty()) {
1269                                                Category cat = this.categories.get(categories.get(0));
1270                                                if (cat != null) {
1271                                                        String tag = getCategoryTag(cat, pick.getWingSpec().getTags());
1272                                                        pickLevel = getLevel(tag);
1273                                                        if (delegate.isPriority(pick.getWingSpec())) {
1274                                                                pickLevel += PRIORITY;
1275                                                        }
1276                                                }
1277                                        }
1278                                        if (pickLevel <= levelToBeat) continue;
1279                                }
1280                                
1281                                alreadyUsed.add(pick.getId());
1282                                
1283                                clearFighterSlot(i, delegate, current);
1284                                delegate.fitFighterInSlot(i, pick, current);
1285                                fittedFighters.put(current.getHullVariantId() + "_" + i, pick);
1286                        }
1287                }
1288                
1289        }
1290        
1291        
1292        
1293        
1294        public AvailableWeapon getBestMatch(WeaponSpecAPI desired, boolean useBetter,
1295                        String catId, Set<String> alreadyUsed, List<AvailableWeapon> possible,
1296                        AutofitPluginDelegate delegate) {
1297                return getBestMatch(desired, useBetter, catId, alreadyUsed, possible, null, delegate);
1298        }
1299        
1300        public AvailableWeapon getBestMatch(WeaponSpecAPI desired, boolean useBetter,
1301                                                                                String catId, Set<String> alreadyUsed, List<AvailableWeapon> possible,
1302                                                                                WeaponSlotAPI slot,
1303                                                                                AutofitPluginDelegate delegate) {
1304                //AvailableWeapon best = null;
1305                float bestScore = -1f;
1306                boolean bestIsPriority = false;
1307                int bestSize = -1;
1308                
1309                Category cat = categories.get(catId);
1310                if (cat == null) return null;
1311                
1312                String desiredTag = getCategoryTag(cat, desired.getTags());
1313                float desiredLevel = getLevel(desiredTag);
1314                
1315                if (desiredTag == null) {
1316                        // fallback to categories that aren't in the tags of the desired weapon
1317//                      for (String tag : desired.getTags()) {
1318//                              desiredLevel = Math.max(desiredLevel, getLevel(tag));
1319//                      }
1320                        desiredLevel = 10000f;
1321                }
1322                
1323                boolean longRange = desired.hasTag(LR);
1324                boolean shortRange = desired.hasTag(SR);
1325                boolean midRange = !longRange && !shortRange;
1326                boolean desiredPD = desired.getAIHints().contains(AIHints.PD);
1327                
1328                WeightedRandomPicker<AvailableWeapon> best = new WeightedRandomPicker<AvailableWeapon>(random);
1329                
1330                
1331//              boolean randomize = isChecked(RANDOMIZE);
1332//              if (randomize) {
1333//                      shortRange = true;
1334//                      longRange = false;
1335//                      midRange = !longRange && !shortRange;
1336//                      desiredPD = true;
1337//              }
1338                
1339                int iter = 0;
1340                for (AvailableWeapon w : possible) {
1341                        iter++;
1342                        WeaponSpecAPI spec = w.getSpec();
1343                        String catTag = getCategoryTag(cat, spec.getTags());
1344                        if (catTag == null) continue; // not in this category
1345                        
1346//                      if (desired.getWeaponId().equals("autopulse") && spec.getWeaponId().contains("phase")) {
1347//                              System.out.println("wefwefwe");
1348//                      }
1349                        
1350                        boolean currLongRange = spec.hasTag(LR);
1351                        boolean currShortRange = spec.hasTag(SR);
1352                        boolean currMidRange = !currLongRange && !currShortRange;
1353                        
1354                        // don't fit short-range weapons instead of long-range ones unless it's PD 
1355                        if (!desiredPD && currShortRange && (midRange || longRange)) continue;
1356                        //if (currMidRange && longRange) continue;
1357                        
1358                        boolean isPrimaryCategory = cat.base.equals(spec.getAutofitCategory());
1359                        boolean currIsPriority = isPrimaryCategory && delegate.isPriority(spec);
1360                        int currSize = spec.getSize().ordinal();
1361                        boolean betterDueToPriority = currSize >= bestSize && currIsPriority && !bestIsPriority;
1362                        boolean worseDueToPriority = currSize <= bestSize && !currIsPriority && bestIsPriority;
1363                        
1364                        if (worseDueToPriority) continue;
1365                        
1366                        float level = getLevel(catTag);
1367                        //if (randomize) level += random.nextInt(20);
1368                        if (!randomize && !useBetter && !betterDueToPriority && level > desiredLevel) continue;
1369                        int rMag = 0;
1370                        if (randomize && desired.getSize() == spec.getSize()) {
1371                                rMag = 20;
1372                        } else if (desired.getSize() == spec.getSize()) {
1373                                //if (delegate.getFaction() != null && delegate.getFaction().getDoctrine().getAutofitRandomizeProbability() > 0) {
1374                                if (delegate.isAllowSlightRandomization()) {
1375                                        rMag = 4;
1376                                }
1377                        }
1378                        if (rMag > 0) {
1379                                boolean symmetric = random.nextFloat() < 0.75f;
1380                                if (slot != null && symmetric) {
1381                                        long seed = (Math.abs((int)(slot.getLocation().x/2f)) * 723489413945245311L) ^ 1181783497276652981L;
1382                                        Random r = new Random((seed + weaponFilterSeed) * iter);
1383                                        level += r.nextInt(rMag);
1384                                } else {
1385                                        level += random.nextInt(rMag);
1386                                }
1387                        }
1388                        
1389                        
1390                        float score = level;
1391//                      if (delegate.isPriority(spec)) {
1392//                              score += PRIORITY;
1393//                      }
1394                        if ((score > bestScore || betterDueToPriority)) {
1395                                //best = w;
1396                                best.clear();
1397                                best.add(w);
1398                                bestScore = score;
1399                                bestSize = currSize;
1400                                bestIsPriority = currIsPriority;
1401                        } else if (score == bestScore) {
1402                                best.add(w);
1403                        }
1404                }
1405//              if (desired.getWeaponId().equals("autopulse")) {
1406//                      System.out.println("wefwefwe");
1407//              }
1408                
1409                
1410                // if the best-match tier includes the weapon specified in the target variant, use that
1411                // prefer one we already have to buying
1412                List<AvailableWeapon> allMatches = new ArrayList<AvailableWeapon>();
1413                List<AvailableWeapon> freeMatches = new ArrayList<AvailableWeapon>();
1414                for (AvailableWeapon w : best.getItems()) {
1415                        if (desired.getWeaponId().equals(w.getId())) {
1416                                allMatches.add(w);
1417                                if (w.getPrice() <= 0) {
1418                                        freeMatches.add(w);
1419                                }
1420                        }
1421                }
1422                if (!freeMatches.isEmpty()) return freeMatches.get(0);
1423                if (!allMatches.isEmpty()) return allMatches.get(0);
1424                
1425                // if the best-match tier includes a weapon that we already own, filter out all non-free ones
1426                boolean hasFree = false;
1427                boolean hasNonBlackMarket = false;
1428                for (AvailableWeapon w : best.getItems()) {
1429                        if (w.getPrice() <= 0) {
1430                                hasFree = true;
1431                        }
1432                        if (w.getSubmarket() == null || !w.getSubmarket().getPlugin().isBlackMarket()) {
1433                                hasNonBlackMarket = true;
1434                        }
1435                }
1436                if (hasFree) {
1437                        for (AvailableWeapon w : new ArrayList<AvailableWeapon>(best.getItems())) {
1438                                if (w.getPrice() > 0) {
1439                                        best.remove(w); 
1440                                }
1441                        }
1442                } else if (hasNonBlackMarket) {
1443                        for (AvailableWeapon w : new ArrayList<AvailableWeapon>(best.getItems())) {
1444                                if (w.getSubmarket() != null && w.getSubmarket().getPlugin().isBlackMarket()) {
1445                                        best.remove(w); 
1446                                }
1447                        }
1448                }
1449                
1450                // if the best-match tier includes a weapon we used already, use that
1451                if (!alreadyUsed.isEmpty()) {
1452                        for (AvailableWeapon w : best.getItems()) {
1453                                if (alreadyUsed.contains(w.getId())) return w;
1454                        }
1455                }
1456                
1457                if (best.isEmpty()) return null;
1458                
1459                //return best.getItems().get(0);
1460                return best.pick();
1461        }
1462        
1463
1464        public AvailableFighter getBestMatch(FighterWingSpecAPI desired, boolean useBetter,
1465                                                                                String catId, Set<String> alreadyUsed, List<AvailableFighter> possible,
1466                                                                                AutofitPluginDelegate delegate) {
1467                float bestScore = -1f;
1468                boolean bestIsPriority = false;
1469                
1470                Category cat = categories.get(catId);
1471                if (cat == null) return null;
1472
1473                String desiredTag = getCategoryTag(cat, desired.getTags());
1474                float desiredLevel = getLevel(desiredTag);
1475
1476                WeightedRandomPicker<AvailableFighter> best = new WeightedRandomPicker<AvailableFighter>(random);
1477
1478                for (AvailableFighter f : possible) {
1479                        FighterWingSpecAPI spec = f.getWingSpec();
1480                        String catTag = getCategoryTag(cat, spec.getTags());
1481                        if (catTag == null) continue; // not in this category
1482
1483                        boolean isPrimaryCategory = cat.base.equals(spec.getAutofitCategory());
1484                        boolean currIsPriority = isPrimaryCategory && delegate.isPriority(spec);
1485                        boolean betterDueToPriority = currIsPriority && !bestIsPriority;
1486                        boolean worseDueToPriority = !currIsPriority && bestIsPriority;
1487
1488                        if (worseDueToPriority) continue;
1489                        
1490                        float level = getLevel(catTag);
1491                        if (!randomize && !useBetter && !betterDueToPriority && level > desiredLevel) continue;
1492                        //if (randomize) level += random.nextInt(20);
1493                        
1494                        int rMag = 0;
1495                        if (randomize) {
1496                                rMag = 20;
1497                        } else {
1498                                if (delegate.isAllowSlightRandomization()) {
1499                                        rMag = 2;
1500                                }
1501                        }
1502                        if (rMag > 0) {
1503                                level += random.nextInt(rMag);
1504                        }
1505
1506                        float score = level;
1507//                      if (delegate.isPriority(spec)) {
1508//                              score += PRIORITY;
1509//                      }
1510                        if (score > bestScore || betterDueToPriority) {
1511                                best.clear();
1512                                best.add(f);
1513                                bestScore = score;
1514                                bestScore = score;
1515                                bestIsPriority = currIsPriority;
1516                        } else if (score == bestScore) {
1517                                best.add(f);
1518                        }
1519                }
1520
1521
1522                // if the best-match tier includes the fighter specified in the target variant, use that
1523                List<AvailableFighter> allMatches = new ArrayList<AvailableFighter>();
1524                List<AvailableFighter> freeMatches = new ArrayList<AvailableFighter>();
1525                for (AvailableFighter f : best.getItems()) {
1526                        if (desired.getId().equals(f.getId())) {
1527                                allMatches.add(f);
1528                                if (f.getPrice() <= 0) {
1529                                        freeMatches.add(f);
1530                                }
1531                        }
1532                }
1533                if (!freeMatches.isEmpty()) return freeMatches.get(0);
1534                if (!allMatches.isEmpty()) return allMatches.get(0);
1535
1536                // if the best-match tier includes a fighter that we already own, filter out all non-free ones
1537                // prefer one we already have to buying
1538                boolean hasFree = false;
1539                boolean hasNonBlackMarket = false;
1540                for (AvailableFighter f : best.getItems()) {
1541                        if (f.getPrice() <= 0) {
1542                                hasFree = true;
1543                        }
1544                        if (f.getSubmarket() == null || !f.getSubmarket().getPlugin().isBlackMarket()) {
1545                                hasNonBlackMarket = true;
1546                        }
1547                }
1548                if (hasFree) {
1549                        for (AvailableFighter f : new ArrayList<AvailableFighter>(best.getItems())) {
1550                                if (f.getPrice() > 0) {
1551                                        best.remove(f); 
1552                                }
1553                        }
1554                } else if (hasNonBlackMarket) {
1555                        for (AvailableFighter f : new ArrayList<AvailableFighter>(best.getItems())) {
1556                                if (f.getSubmarket() != null && f.getSubmarket().getPlugin().isBlackMarket()) {
1557                                        best.remove(f); 
1558                                }
1559                        }
1560                }
1561
1562                
1563                // if the best-match tier includes a fighter we used already, use that
1564                if (!alreadyUsed.isEmpty()) {
1565                        for (AvailableFighter f : best.getItems()) {
1566                                if (alreadyUsed.contains(f.getId())) return f;
1567                        }
1568                }
1569
1570                if (best.isEmpty()) return null;
1571
1572                //return best.getItems().get(0);
1573                return best.pick();
1574        }
1575        
1576        public String getCategoryTag(Category cat, Set<String> tags) {
1577                String catTag = null;
1578                for (String tag : tags) {
1579                        if (cat.tags.contains(tag)) {
1580                                catTag = tag;
1581                                break;
1582                        }
1583                }
1584                return catTag;
1585        }
1586        
1587
1588        protected static transient Map<String, Integer> tagLevels = new HashMap<String, Integer>();
1589
1590        
1591        public float getLevel(String tag) {
1592                Integer result = tagLevels.get(tag);
1593                if (result != null) return result;
1594                Category cat = categories.get(tag);
1595                if (cat == null) {
1596                        tagLevels.put(tag, -1);
1597                        return -1f;
1598                }
1599                try {
1600                        result = (int) Float.parseFloat(tag.replaceAll(cat.base, ""));
1601                        tagLevels.put(tag, result);
1602                        return result;
1603                } catch (Throwable t) {
1604                        tagLevels.put(tag, -1);
1605                        return -1f;
1606                }
1607        }
1608        
1609//      public List<String> getCategoriesInPriorityOrder(Set<String> tags) {
1610////            final Map<String, Float> levels = new HashMap<String, Float>();
1611//              List<String> result = new ArrayList<String>();
1612//              result.addAll(tags);
1613////            for (String tag : tags) {
1614////                    float level = getLevel(tag);
1615////                    if (level < 0) continue;
1616////                    levels.put(tag, level);
1617////                    result.add(tag);
1618////            }
1619//              
1620//              Collections.sort(result, new Comparator<String>() {
1621//                      public int compare(String o1, String o2) {
1622//                              //return (int)Math.signum(levels.get(o2) - levels.get(o1));
1623//                              return (int)Math.signum(getLevel(o2) - getLevel(o1));
1624//                      }
1625//              });
1626//              
1627//              return result;
1628//      }
1629        
1630        
1631        public List<WeaponSlotAPI> getWeaponSlotsInPriorityOrder(ShipVariantAPI current, ShipVariantAPI target, boolean upgradeMode) {
1632                List<WeaponSlotAPI> result = new ArrayList<WeaponSlotAPI>();
1633
1634                for (WeaponSlotAPI slot : current.getHullSpec().getAllWeaponSlotsCopy()) {
1635                        if (slot.isBuiltIn() || slot.isDecorative()) continue;
1636                        if (target.getWeaponId(slot.getId()) == null) continue;
1637                        if (!upgradeMode && current.getWeaponId(slot.getId()) != null) continue;
1638                        result.add(slot);
1639                }
1640                
1641                Collections.sort(result, new Comparator<WeaponSlotAPI>() {
1642                        public int compare(WeaponSlotAPI w1, WeaponSlotAPI w2) {
1643                                float s1 = getSlotPriorityScore(w1);
1644                                float s2 = getSlotPriorityScore(w2);
1645                                return (int) Math.signum(s2 - s1);
1646                        }
1647                });
1648                
1649                return result;
1650        }
1651
1652        public float getSlotPriorityScore(WeaponSlotAPI slot) {
1653                float score = 0;
1654                
1655                switch (slot.getSlotSize()) {
1656                case LARGE: score = 10000; break;
1657                case MEDIUM: score = 5000; break;
1658                case SMALL: score = 2500; break;
1659                }
1660                float angleDiff = Misc.getAngleDiff(slot.getAngle(), 0);
1661                boolean front = Misc.isInArc(slot.getAngle(), slot.getArc(), 0);
1662                if (front) {
1663                        //score += 10f;
1664                        score += 180f - angleDiff;
1665                }
1666                
1667                return score;
1668        }
1669        
1670        
1671        
1672        public List<AvailableWeapon> getPossibleWeapons(WeaponSlotAPI slot, WeaponSpecAPI desired, ShipVariantAPI current, float opLeft, List<AvailableWeapon> weapons) {
1673                List<AvailableWeapon> result = new ArrayList<AvailableWeapon>();
1674                
1675                for (AvailableWeapon w : weapons) {
1676                        if (w.getQuantity() <= 0) continue;
1677                        
1678                        WeaponSpecAPI spec = w.getSpec();
1679                        //float cost = spec.getOrdnancePointCost(stats, current.getStatsForOpCosts());
1680                        float cost = w.getOPCost(stats, current.getStatsForOpCosts());
1681                        if (cost > opLeft) continue;
1682                        if (!slot.weaponFits(spec)) continue;
1683                        
1684                        if (spec != desired && 
1685                                        (spec.getType() == WeaponType.MISSILE || spec.getAIHints().contains(AIHints.STRIKE))) {
1686                                boolean guided = spec.getAIHints().contains(AIHints.DO_NOT_AIM);
1687                                if (!guided) {
1688                                        boolean guidedPoor = spec.getAIHints().contains(AIHints.GUIDED_POOR);
1689                                        float angleDiff = Misc.getDistanceFromArc(slot.getAngle(), slot.getArc(), 0);
1690                                        if (angleDiff > 45 || (!guidedPoor && angleDiff > 20)) continue;
1691                                }
1692                        }
1693                        
1694                        result.add(w);
1695                }
1696                
1697                if (randomize && false) {
1698                        Random filterRandom = new Random(weaponFilterSeed);
1699                        int num = Math.max(1, result.size() / 3 * 2);
1700                        Set<Integer> picks = DefaultFleetInflater.makePicks(num, result.size(), filterRandom);
1701                        List<AvailableWeapon> filtered = new ArrayList<AvailableWeapon>();
1702                        for (Integer pick : picks) {
1703                                filtered.add(result.get(pick));
1704                        }
1705                        result = filtered;
1706                }
1707                
1708                if (TutorialMissionIntel.isTutorialInProgress() &&
1709                                current.getHullSpec() != null && current.getHullSpec().hasTag(Factions.DERELICT)) {
1710                        List<AvailableWeapon> remove = new ArrayList<AvailableWeapon>();
1711                        for (AvailableWeapon w : result) {
1712                                if (w.getId().equals("heatseeker")) {
1713                                        remove.add(w);
1714                                }
1715                        }
1716                        result.removeAll(remove);
1717                }
1718                
1719                return result;
1720        }
1721        
1722        public List<AvailableFighter> getPossibleFighters(ShipVariantAPI current, float opLeft, List<AvailableFighter> fighters) {
1723                List<AvailableFighter> result = new ArrayList<AvailableFighter>();
1724                
1725                for (AvailableFighter f : fighters) {
1726                        if (f.getQuantity() <= 0) continue;
1727                        
1728                        FighterWingSpecAPI spec = f.getWingSpec();
1729                        float cost = spec.getOpCost(current.getStatsForOpCosts());
1730                        if (cost > opLeft) continue;
1731                        
1732                        result.add(f);
1733                }
1734                
1735                if (randomize) {
1736                        Random filterRandom = new Random(weaponFilterSeed);
1737                        int num = Math.max(1, result.size() / 3 * 2);
1738                        Set<Integer> picks = DefaultFleetInflater.makePicks(num, result.size(), filterRandom);
1739                        List<AvailableFighter> filtered = new ArrayList<AvailableFighter>();
1740                        for (Integer pick : picks) {
1741                                filtered.add(result.get(pick));
1742                        }
1743                        result = filtered;
1744                }
1745                
1746                return result;
1747        }
1748        
1749        
1750        public List<AutofitOption> getOptions() {
1751                return options;
1752        }
1753
1754        public float getRating(ShipVariantAPI current, ShipVariantAPI target, AutofitPluginDelegate delegate) {
1755                return 0;
1756        }
1757
1758        @Override
1759        public void doQuickAction(ShipVariantAPI current, AutofitPluginDelegate delegate) {
1760//              if (!fittingModule) {
1761//                      availableMods = new LinkedHashSet<String>(delegate.getAvailableHullmods());
1762//              }
1763//              
1764//              int index = 0;
1765//              for (String slotId : current.getStationModules().keySet()) {
1766//                      ShipVariantAPI moduleCurrent = current.getModuleVariant(slotId);
1767//                      if (moduleCurrent == null) continue;
1768//                      if (moduleCurrent.isStockVariant()) {
1769//                              moduleCurrent = moduleCurrent.clone();
1770//                              moduleCurrent.setSource(VariantSource.REFIT);
1771//                              //moduleCurrent.setHullVariantId(Misc.genUID());
1772//                              moduleCurrent.setHullVariantId(moduleCurrent.getHullVariantId() + "_" + index);
1773//                      }
1774//                      index++;
1775//                      
1776//                      fittingModule = true;
1777//                      doQuickAction(moduleCurrent, delegate);
1778//                      fittingModule = false;
1779//                      
1780//                      current.setModuleVariant(slotId, moduleCurrent);
1781//                      current.setSource(VariantSource.REFIT);
1782//              }
1783                availableMods = new LinkedHashSet<String>(delegate.getAvailableHullmods());
1784                
1785                if (current.getHullSize().ordinal() >= HullSize.DESTROYER.ordinal() && !current.isCivilian()) {
1786                        addHullmods(current, delegate, HullMods.INTEGRATED_TARGETING_UNIT);
1787                }
1788                
1789                //addHullmods(current, delegate, HullMods.REINFORCEDHULL);
1790                addExtraVentsAndCaps(current, null);
1791//              addExtraVents(current);
1792//              addExtraCaps(current);
1793                addDistributor(current, delegate);
1794                addDistributorRemoveVentsIfNeeded(current, delegate);
1795                addCoilRemoveCapsIfNeeded(current, delegate);
1796                //addModsWithSpareOPIfAny(current, current, false, delegate);
1797                addHullmods(current, delegate, HullMods.REINFORCEDHULL, HullMods.BLAST_DOORS, HullMods.HARDENED_SUBSYSTEMS);
1798                
1799                if (!fittingModule) {
1800                        delegate.syncUIWithVariant(current);
1801                }
1802        }
1803
1804        @Override
1805        public String getQuickActionText() {
1806                return "Spend free OP";
1807        }
1808        
1809        public String getQuickActionTooltip() {
1810                return "Spend any unused ordnance points on flux vents, capacitors, and essential hullmods.\n\n" +
1811                                //"Will not make any changes to weapon loadout or changes that would reduce the combat readiness of the ship, and will not spend any credits.";
1812                                "Will not make any changes to weapon loadout, will not affect ship modules (if any), and will not spend any credits.";
1813        }
1814        
1815        public boolean isQuickActionEnabled(ShipVariantAPI currentVariant) {
1816                int unusedOpTotal = 0;
1817                for (String slotId : currentVariant.getStationModules().keySet()) {
1818                        ShipVariantAPI moduleCurrent = currentVariant.getModuleVariant(slotId);
1819                        if (moduleCurrent == null) continue;
1820                        unusedOpTotal += moduleCurrent.getUnusedOP(stats);
1821                }
1822                unusedOpTotal += currentVariant.getUnusedOP(stats);
1823                return unusedOpTotal > 0;
1824                
1825                //return currentVariant.getUnusedOP(stats) > 0;
1826        }
1827        
1828        
1829        public static class AutoAssignScore {
1830                public float [] score;
1831                public FleetMemberAPI member;
1832                public PersonAPI officer;
1833        }
1834        
1835        
1836        @Override
1837        public void autoAssignOfficers(CampaignFleetAPI fleet) {
1838                List<FleetMemberAPI> members = new ArrayList<FleetMemberAPI>();
1839                for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
1840                        if (member.isMothballed()) {
1841                                continue;
1842                        }
1843                        if (!member.getCaptain().isDefault()) {
1844                                continue;
1845                        }
1846                        if (fleet.isPlayerFleet() && Misc.isAutomated(member)) continue;
1847                        members.add(member);
1848                }
1849                
1850                List<OfficerDataAPI> officers = new ArrayList<OfficerDataAPI>();
1851                int max = (int) fleet.getCommander().getStats().getOfficerNumber().getModifiedValue();
1852                int count = 0;
1853                for (OfficerDataAPI officer : fleet.getFleetData().getOfficersCopy()) {
1854                        boolean merc = Misc.isMercenary(officer.getPerson());
1855                        if (!merc) {
1856                                count++;
1857                        }
1858                        if (count > max && !merc) continue;
1859                        
1860                        boolean found = false;
1861                        for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
1862                                if (member.getCaptain() == officer.getPerson()) {
1863                                        found = true;
1864                                        break;
1865                                }
1866                        }
1867                        if (!found) {
1868                                officers.add(officer);
1869                        }
1870                }
1871
1872                
1873                List<AutoAssignScore> shipScores = new ArrayList<AutoAssignScore>();
1874                List<AutoAssignScore> officerScores = new ArrayList<AutoAssignScore>();
1875                
1876                float maxMemberTotal = 1f;
1877                float maxOfficerTotal = 1f;
1878                
1879                for (FleetMemberAPI member : members) {
1880                        AutoAssignScore score = new AutoAssignScore();
1881                        shipScores.add(score);
1882                        score.member = member;
1883                        score.score = computeMemberScore(member);
1884                        
1885                        maxMemberTotal = Math.max(maxMemberTotal, score.score[4]);
1886                }
1887                
1888                for (OfficerDataAPI officer : officers) {
1889                        AutoAssignScore score = new AutoAssignScore();
1890                        officerScores.add(score);
1891                        score.officer = officer.getPerson();
1892                        score.score = computeOfficerScore(officer.getPerson());
1893                        maxOfficerTotal = Math.max(maxOfficerTotal, score.score[4]);
1894                }
1895                
1896                for (AutoAssignScore score : officerScores) {
1897                        // so that the best officers are closer to the best ships
1898                        // and the lowest-level officers are still closer to the best ships than to the worst ships
1899                        score.score[4] = maxMemberTotal + (maxOfficerTotal - score.score[4]);
1900                }
1901                
1902                while (!shipScores.isEmpty() && !officerScores.isEmpty()) {
1903                        float minDist = Float.MAX_VALUE;
1904                        AutoAssignScore bestShip = null;
1905                        AutoAssignScore bestOfficer = null;
1906                        for (AutoAssignScore ship : shipScores) {
1907//                              if (ship.member.getHullId().equals("condor")) {
1908//                                      System.out.println("wefewfew");
1909//                              }
1910                                for (AutoAssignScore officer : officerScores) {
1911                                        float dist = Math.abs(ship.score[0] - officer.score[0]) + 
1912                                                                 Math.abs(ship.score[1] - officer.score[1]) +
1913                                                                 Math.abs(ship.score[2] - officer.score[2]) +
1914                                                                 Math.abs(ship.score[3] - officer.score[3]) +
1915                                                                 Math.abs(ship.score[4] - officer.score[4]);
1916
1917                                        if (dist < minDist) {
1918                                                minDist = dist;
1919                                                bestShip = ship;
1920                                                bestOfficer = officer;
1921                                        }
1922                                }
1923                        }
1924                        if (bestShip == null) {
1925                                break;
1926                        }
1927                        
1928                        shipScores.remove(bestShip);
1929                        officerScores.remove(bestOfficer);
1930                        bestShip.member.setCaptain(bestOfficer.officer);
1931                }
1932        }
1933
1934        public float [] computeOfficerScore(PersonAPI officer) {
1935                float energy = 0f;
1936                float ballistic = 0f;
1937                float missile = 0f;
1938                float defense = 0f;
1939                float total = 0f;
1940                
1941                for (SkillLevelAPI sl : officer.getStats().getSkillsCopy()) {
1942                        if (!sl.getSkill().isCombatOfficerSkill()) continue;
1943                        float w = sl.getLevel();
1944                        if (w == 2) w = 1.33f; // weigh elite skills as less than double
1945                        if (w <= 0f) {
1946                                continue;
1947                        }
1948                        
1949                        if (sl.getSkill().hasTag(Skills.TAG_ENERGY_WEAPONS)) {
1950                                energy++;
1951                        } else if (sl.getSkill().hasTag(Skills.TAG_BALLISTIC_WEAPONS)) {
1952                                ballistic++;
1953                        } else if (sl.getSkill().hasTag(Skills.TAG_MISSILE_WEAPONS)) {
1954                                missile++;
1955                        } else if (sl.getSkill().hasTag(Skills.TAG_ACTIVE_DEFENSES)) {
1956                                defense++;
1957                        }
1958                        total++;
1959                }
1960                
1961                if (total < 1f) total = 1f;
1962                energy /= total;
1963                ballistic /= total;
1964                missile /= total;
1965                defense /= total;
1966                
1967                float [] result = new float [5];
1968                result[0] = energy;
1969                result[1] = ballistic;
1970                result[2] = missile;
1971                result[3] = defense;
1972                result[4] = total;
1973                return result;
1974        }
1975        
1976        public float [] computeMemberScore(FleetMemberAPI member) {
1977                float energy = 0f;
1978                float ballistic = 0f;
1979                float missile = 0f;
1980                float total = 0f;
1981                
1982                boolean civ = member.isCivilian();
1983                
1984                for (String slotId : member.getVariant().getFittedWeaponSlots()) {
1985                        WeaponSlotAPI slot = member.getVariant().getSlot(slotId);
1986                        if (slot.isDecorative() || slot.isSystemSlot()) continue;
1987                        
1988                        WeaponSpecAPI weapon = member.getVariant().getWeaponSpec(slotId);
1989                        float w = 1f;
1990                        switch (weapon.getSize()) {
1991                        case LARGE: w = 4f; break;
1992                        case MEDIUM: w = 2f; break;
1993                        case SMALL: w = 1f; break;
1994                        }
1995                        if (civ) w *= 0.1f;
1996                        WeaponType type = weapon.getType();
1997                        if (type == WeaponType.BALLISTIC) { 
1998                                ballistic += w;
1999                                total += w;
2000                        } else if (type == WeaponType.ENERGY) { 
2001                                energy += w;
2002                                total += w;
2003                        } else if (type == WeaponType.MISSILE) { 
2004                                missile += w;
2005                                total += w;
2006                        } else {
2007                                total += w;
2008                        }
2009                }
2010                if (total < 1f) total = 1f;
2011                energy /= total;
2012                ballistic /= total;
2013                missile /= total;
2014
2015                boolean d = member.getHullSpec().getShieldType() == ShieldType.FRONT ||
2016                                        member.getHullSpec().getShieldType() == ShieldType.OMNI || 
2017                                        member.getHullSpec().isPhase();
2018                
2019                float [] result = new float [5];
2020                result[0] = energy;
2021                result[1] = ballistic;
2022                result[2] = missile;
2023                if (d) {
2024                        result[3] = 1f;
2025                } else {
2026                        result[3] = 0f;
2027                }
2028                result[4] = total;
2029                
2030                return result;
2031        }
2032        
2033        
2034        
2035        public float getVariantOPFraction(FleetMemberAPI member) {
2036                float f = 1f;
2037                float op = member.getVariant().getHullSpec().getOrdnancePoints(stats);
2038                if (op > 0) {
2039                        f = (op - member.getVariant().getUnusedOP(stats)) / op;
2040                }
2041                return f;
2042        }
2043        
2044        public float getSkillTotal(OfficerDataAPI officer, boolean carrier) {
2045                float total = 0f;
2046                for (SkillLevelAPI skill : officer.getPerson().getStats().getSkillsCopy()) {
2047                        SkillSpecAPI spec = skill.getSkill();
2048                        if (!spec.isCombatOfficerSkill()) continue;
2049                        
2050                        float level = skill.getLevel();
2051                        if (level <= 0) continue;
2052                        
2053                        if (!carrier || spec.hasTag(Skills.TAG_CARRIER)) {
2054                                total += level;
2055                        }
2056                }
2057                return total;
2058        }
2059        
2060        
2061        
2062        protected int addRandomizedHullmodsPre(ShipVariantAPI current, AutofitPluginDelegate delegate) {
2063                int num = 0;
2064                if (random.nextFloat() > 0.5f){
2065                        num++;
2066                        if (random.nextFloat() > 0.75f) {
2067                                num++;
2068                        }
2069                }
2070                
2071                if (num <= 0) return 0;
2072                
2073                ShipHullSpecAPI hull = current.getHullSpec();
2074                boolean omni = hull.getShieldType() == ShieldType.OMNI;
2075                boolean front = hull.getShieldType() == ShieldType.FRONT;
2076                boolean shield = omni || front;
2077                boolean phase = hull.getShieldType() == ShieldType.PHASE;
2078                int bays = hull.getFighterBays();
2079                float shieldArc = hull.getShieldSpec().getArc();
2080                
2081                
2082                WeightedRandomPicker<String> picker = new WeightedRandomPicker<String>(random);
2083                
2084                if (availableMods.contains(HullMods.FRONT_SHIELD_CONVERSION)) {
2085                        if (omni && shieldArc < 270) {
2086                                picker.add(HullMods.FRONT_SHIELD_CONVERSION, 1f);
2087                        }
2088                }
2089                
2090                if (availableMods.contains(HullMods.EXTENDED_SHIELDS)) {
2091                        if (shield && shieldArc <= 300) {
2092                                picker.add(HullMods.EXTENDED_SHIELDS, 1f);
2093                        }
2094                }
2095                
2096                if (availableMods.contains(HullMods.CONVERTED_HANGAR) && hull.getHullSize() != HullSize.FRIGATE) {
2097                        if (bays <= 0) {
2098                                FactionAPI faction = delegate.getFaction();
2099                                if (faction == null) {
2100                                        if (random.nextFloat() < 0.2f) {
2101                                                picker.add(HullMods.CONVERTED_HANGAR, 1f);
2102                                        }
2103                                } else {
2104                                        if (random.nextFloat() < (float) faction.getDoctrine().getCarriers() / 5f) {
2105                                                picker.add(HullMods.CONVERTED_HANGAR, 1f);
2106                                        }
2107                                }
2108                        }
2109                }
2110                
2111                if (availableMods.contains(HullMods.MAKESHIFT_GENERATOR)) {
2112                        if (!shield && !phase) {
2113                                picker.add(HullMods.MAKESHIFT_GENERATOR, 1f);
2114                        }
2115                }
2116                
2117                if (availableMods.contains(HullMods.EXPANDED_DECK_CREW)) {
2118                        if (bays >= 2) {
2119                                picker.add(HullMods.EXPANDED_DECK_CREW, 1f);
2120                        }
2121                }
2122                
2123                if (availableMods.contains(HullMods.ECM)) {
2124                        picker.add(HullMods.ECM, 1f);
2125                }
2126                
2127                if (availableMods.contains(HullMods.INTEGRATED_TARGETING_UNIT)) {
2128                        picker.add(HullMods.INTEGRATED_TARGETING_UNIT, 100f);
2129                } else if (availableMods.contains(HullMods.DEDICATED_TARGETING_CORE)) {
2130                        if (hull.getHullSize().ordinal() >= HullSize.CRUISER.ordinal()) {
2131                                picker.add(HullMods.DEDICATED_TARGETING_CORE, 100f);
2132                        }
2133                }
2134                
2135                if (availableMods.contains(HullMods.HARDENED_SHIELDS)) {
2136                        if (shield) {
2137                                picker.add(HullMods.HARDENED_SHIELDS, 1f);
2138                        }
2139                }
2140                
2141                if (availableMods.contains(HullMods.STABILIZEDSHIELDEMITTER)) {
2142                        if (shield) {
2143                                picker.add(HullMods.STABILIZEDSHIELDEMITTER, 1f);
2144                        }
2145                }
2146                
2147                if (availableMods.contains(HullMods.HEAVYARMOR)) {
2148                        picker.add(HullMods.HEAVYARMOR, 1f);
2149                }
2150                
2151                if (availableMods.contains(HullMods.INSULATEDENGINE)) {
2152                        if (!omni) {
2153                                picker.add(HullMods.INSULATEDENGINE, 1f);
2154                        }
2155                }
2156                
2157                if (availableMods.contains(HullMods.FLUXBREAKERS)) {
2158                        if (shield) {
2159                                picker.add(HullMods.FLUXBREAKERS, 1f);
2160                        } else {
2161                                picker.add(HullMods.FLUXBREAKERS, 10f);
2162                        }
2163                }
2164                
2165                if (availableMods.contains(HullMods.UNSTABLE_INJECTOR)) {
2166                        picker.add(HullMods.UNSTABLE_INJECTOR, 1f);
2167                }
2168                
2169//              if (availableMods.contains(HullMods.SAFETYOVERRIDES)) {
2170//                      if (hull.getHullSize().ordinal() <= HullSize.CRUISER.ordinal()) {
2171//                              picker.add(HullMods.SAFETYOVERRIDES, 1f);
2172//                      }
2173//              }
2174                
2175                
2176                float addedTotal = 0;
2177                float addedMax = current.getHullSpec().getOrdnancePoints(stats) * 0.2f;
2178                for (int i = 0; i < num; i++) {
2179                        String modId = picker.pickAndRemove();
2180                        if (modId == null) break;
2181                        if (current.hasHullMod(modId)) {
2182                                i--;
2183                                continue;
2184                        }
2185                        
2186                        if (modId.equals(HullMods.EXTENDED_SHIELDS)) {
2187                                picker.remove(HullMods.FRONT_SHIELD_CONVERSION);
2188                        } else if (modId.equals(HullMods.FRONT_SHIELD_CONVERSION) && shieldArc >= 180) {
2189                                picker.remove(HullMods.EXTENDED_SHIELDS);
2190                        }
2191                        addedTotal = addHullmods(current, delegate, modId);
2192                        if (addedTotal >= addedMax) break;
2193                }
2194                
2195                return (int) addedTotal;
2196        }
2197        
2198        
2199        protected int addRandomizedHullmodsPost(ShipVariantAPI current, AutofitPluginDelegate delegate) {
2200                int num = 0;
2201                if (random.nextFloat() > 0.5f){
2202                        num++;
2203                        if (random.nextFloat() > 0.75f) {
2204                                num++;
2205                        }
2206                }
2207                
2208                if (num <= 0) return 0;
2209                
2210                ShipHullSpecAPI hull = current.getHullSpec();
2211                boolean omni = hull.getShieldType() == ShieldType.OMNI;
2212                boolean front = hull.getShieldType() == ShieldType.FRONT;
2213//              boolean shield = omni || front;
2214//              boolean phase = hull.getShieldType() == ShieldType.PHASE;
2215//              int bays = hull.getFighterBays();
2216//              float shieldArc = hull.getShieldSpec().getArc();
2217                
2218                
2219                WeightedRandomPicker<String> picker = new WeightedRandomPicker<String>(random);
2220                
2221                if (availableMods.contains(HullMods.ARMOREDWEAPONS)) {
2222                        picker.add(HullMods.ARMOREDWEAPONS, 1f);
2223                }
2224                
2225                if (availableMods.contains(HullMods.MISSLERACKS)) {
2226                        if (missilesWithAmmoOnCurrent >= 2) {
2227                                picker.add(HullMods.MISSLERACKS, missilesWithAmmoOnCurrent);
2228                        }
2229                }
2230                
2231                if (availableMods.contains(HullMods.ECCM)) {
2232                        if (missilesWithAmmoOnCurrent >= 2) {
2233                                picker.add(HullMods.ECCM, 1f);
2234                        }
2235                }
2236                
2237                float addedTotal = 0;
2238                float addedMax = current.getHullSpec().getOrdnancePoints(stats) * 0.2f;
2239                for (int i = 0; i < num; i++) {
2240                        String modId = picker.pickAndRemove();
2241                        if (modId == null) break;
2242                        if (current.hasHullMod(modId)) {
2243                                i--;
2244                                continue;
2245                        }
2246
2247                        addedTotal = addHullmods(current, delegate, modId);
2248                        if (addedTotal >= addedMax) break;
2249                }
2250                
2251                return (int) addedTotal;
2252        }
2253        
2254        
2255        
2256        public void addSMods(FleetMemberAPI member, int numSmods, AutofitPluginDelegate delegate) {
2257                availableMods = new LinkedHashSet<String>(delegate.getAvailableHullmods());
2258                
2259                ShipVariantAPI current = member.getVariant();
2260                
2261                int added = convertToSMods(current, numSmods);
2262                addExtraVents(current);
2263                addExtraCaps(current);
2264                //addHullmods(current, delegate, HullMods.FLUX_DISTRIBUTOR, HullMods.FLUX_COIL);
2265                if (!current.hasHullMod(HullMods.FLUX_DISTRIBUTOR)) {
2266                        addDistributor(current, delegate);
2267                }
2268                if (!current.hasHullMod(HullMods.FLUX_COIL)) {
2269                        addCoil(current, delegate);
2270                }
2271                //addModsWithSpareOPIfAny(current, target, true, delegate);
2272                //addHullmods(current, delegate, HullMods.FLUX_DISTRIBUTOR, HullMods.FLUX_COIL);
2273                if (current.getHullSize() == HullSize.FRIGATE || current.hasHullMod(HullMods.SAFETYOVERRIDES)) {
2274                        addHullmods(current, delegate, HullMods.HARDENED_SUBSYSTEMS, HullMods.REINFORCEDHULL, HullMods.BLAST_DOORS);
2275                } else {
2276                        addHullmods(current, delegate, HullMods.REINFORCEDHULL, HullMods.BLAST_DOORS, HullMods.HARDENED_SUBSYSTEMS);
2277                }
2278                int remaining = numSmods - added;
2279                if (remaining > 0) {
2280                        List<String> mods = new ArrayList<String>();
2281                        mods.add(HullMods.FLUX_DISTRIBUTOR);
2282                        mods.add(HullMods.FLUX_COIL);
2283                        if (current.getHullSize() == HullSize.FRIGATE || current.hasHullMod(HullMods.SAFETYOVERRIDES)) {
2284                                mods.add(HullMods.HARDENED_SUBSYSTEMS);
2285                                mods.add(HullMods.REINFORCEDHULL);
2286                        } else {
2287                                mods.add(HullMods.REINFORCEDHULL);
2288                                mods.add(HullMods.HARDENED_SUBSYSTEMS);
2289                        }
2290                        mods.add(HullMods.BLAST_DOORS);
2291                        Iterator<String> iter = mods.iterator();
2292                        while (iter.hasNext()) {
2293                                String modId = iter.next();
2294                                if (current.getPermaMods().contains(modId)) {
2295                                        iter.remove();
2296                                }
2297                        }
2298//                              while (!mods.isEmpty() && current.hasHullMod(mods.get(0))) {
2299//                                      mods.remove(0);
2300//                              }
2301                        for (int i = 0; i < remaining && !mods.isEmpty(); i++) {
2302                                current.setNumFluxCapacitors(0);
2303                                current.setNumFluxVents(0);
2304                                String modId = mods.get(Math.min(i, mods.size() - 1));
2305                                addHullmods(current, delegate, modId);
2306                                convertToSMods(current, 1);
2307                        }
2308                }
2309        }
2310        
2311}
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322