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