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