001package com.fs.starfarer.api.impl.campaign.procgen; 002 003import java.awt.Color; 004import java.util.ArrayList; 005import java.util.LinkedHashSet; 006import java.util.List; 007import java.util.Map; 008import java.util.Random; 009import java.util.Set; 010 011import com.fs.starfarer.api.Global; 012import com.fs.starfarer.api.campaign.BaseCustomProductionPickerDelegateImpl; 013import com.fs.starfarer.api.campaign.CampaignFleetAPI; 014import com.fs.starfarer.api.campaign.CargoAPI; 015import com.fs.starfarer.api.campaign.FactionAPI; 016import com.fs.starfarer.api.campaign.FactionAPI.ShipPickMode; 017import com.fs.starfarer.api.campaign.FactionProductionAPI; 018import com.fs.starfarer.api.campaign.FactionProductionAPI.ItemInProductionAPI; 019import com.fs.starfarer.api.campaign.FactionProductionAPI.ProductionItemType; 020import com.fs.starfarer.api.campaign.FleetInflater; 021import com.fs.starfarer.api.campaign.InteractionDialogAPI; 022import com.fs.starfarer.api.campaign.InteractionDialogPlugin; 023import com.fs.starfarer.api.campaign.OptionPanelAPI; 024import com.fs.starfarer.api.campaign.TextPanelAPI; 025import com.fs.starfarer.api.campaign.VisualPanelAPI; 026import com.fs.starfarer.api.campaign.econ.MarketAPI; 027import com.fs.starfarer.api.campaign.rules.MemoryAPI; 028import com.fs.starfarer.api.characters.MutableCharacterStatsAPI.SkillLevelAPI; 029import com.fs.starfarer.api.characters.OfficerDataAPI; 030import com.fs.starfarer.api.characters.PersonAPI; 031import com.fs.starfarer.api.combat.EngagementResultAPI; 032import com.fs.starfarer.api.combat.ShipAPI.HullSize; 033import com.fs.starfarer.api.combat.ShipHullSpecAPI; 034import com.fs.starfarer.api.combat.ShipHullSpecAPI.ShipTypeHints; 035import com.fs.starfarer.api.combat.WeaponAPI.AIHints; 036import com.fs.starfarer.api.fleet.FleetMemberAPI; 037import com.fs.starfarer.api.impl.campaign.fleets.DefaultFleetInflaterParams; 038import com.fs.starfarer.api.impl.campaign.fleets.FleetFactoryV3; 039import com.fs.starfarer.api.impl.campaign.fleets.FleetParamsV3; 040import com.fs.starfarer.api.impl.campaign.ids.Factions; 041import com.fs.starfarer.api.impl.campaign.ids.MemFlags; 042import com.fs.starfarer.api.impl.campaign.ids.Tags; 043import com.fs.starfarer.api.loading.FighterWingSpecAPI; 044import com.fs.starfarer.api.loading.HullModSpecAPI; 045import com.fs.starfarer.api.loading.WeaponSpecAPI; 046import com.fs.starfarer.api.plugins.OfficerLevelupPlugin; 047import com.fs.starfarer.api.util.Misc; 048 049public class PlayerFleetGenPluginImpl implements InteractionDialogPlugin { 050 051 protected static enum FleetQuality { 052 MAX_DMODS, 053 NO_DMODS, 054 ONE_SMOD, 055 TWO_SMODS, 056 THREE_SMODS, 057 } 058 059 protected static enum OptionId { 060 INIT, 061 SELECT_SHIPS, 062 ADD_UNSELECTED_OFFICERS, 063 OFFICER_LEVEL_5, 064 OFFICER_LEVEL_6, 065 OFFICER_LEVEL_7, 066 OFFICERS_GO_BACK, 067 068 QUALITY_MAX_DMODS, 069 QUALITY_NO_DMODS, 070 QUALITY_1_SMODS, 071 QUALITY_2_SMODS, 072 QUALITY_2_SMODS_OMEGA, 073 QUALITY_3_SMODS, 074 QUALITY_3_SMODS_OMEGA, 075 076// SMODS_OFFICER_SHIPS_ONLY, 077// SMODS_ALL_SHIPS, 078// NORMAL_LEVEL_OFFICERS, 079// HIGH_LEVEL_OFFICERS, 080 081 ADD_OFFICERS, 082 ADD_OFFICERS_NO_SKILLS, 083 NO_OFFICERS, 084 085 CREATE_REPLACE, 086 CREATE_ADD, 087 088 LEAVE, 089 } 090 091 092 public static class FleetGenData { 093 public FactionProductionAPI prod; 094 public FleetQuality quality; 095 public boolean smodsOnAllShips = true; 096 public boolean allowOmega = false; 097 //public boolean eliteOfficers = false; 098 public boolean addOfficers = false; 099 public boolean keepOfficerPointsFree = false; 100 public boolean append = false; 101 102 103 public FleetGenData() { 104 prod = Global.getSector().getPlayerFaction().getProduction().clone(); 105 prod.clear(); 106 } 107 } 108 109 110 protected InteractionDialogAPI dialog; 111 protected TextPanelAPI textPanel; 112 protected OptionPanelAPI options; 113 protected VisualPanelAPI visual; 114 115 protected FleetGenData data = new FleetGenData(); 116 protected CampaignFleetAPI playerFleet; 117 118 protected Set<String> ships = new LinkedHashSet<String>(); 119 protected Set<String> fighters = new LinkedHashSet<String>(); 120 protected Set<String> weapons = new LinkedHashSet<String>(); 121 protected Set<String> unrestrictedFighters = new LinkedHashSet<String>(); 122 protected Set<String> unrestrictedWeapons = new LinkedHashSet<String>(); 123 protected Set<String> hullmods = new LinkedHashSet<String>(); 124 125 protected static final Color HIGHLIGHT_COLOR = Global.getSettings().getColor("buttonShortcut"); 126 127 public void init(InteractionDialogAPI dialog) { 128 this.dialog = dialog; 129 130 131 textPanel = dialog.getTextPanel(); 132 options = dialog.getOptionPanel(); 133 visual = dialog.getVisualPanel(); 134 135 playerFleet = Global.getSector().getPlayerFleet(); 136 137 visual.setVisualFade(0.25f, 0.25f); 138 139 initAvailable(); 140 141 dialog.setOptionOnEscape("Leave", OptionId.LEAVE); 142 optionSelected(null, OptionId.INIT); 143 } 144 145 public Map<String, MemoryAPI> getMemoryMap() { 146 return null; 147 } 148 149 public void backFromEngagement(EngagementResultAPI result) { 150 // no combat here, so this won't get called 151 } 152 153 154 public void optionSelected(String text, Object optionData) { 155 if (optionData == null) return; 156 157 OptionId option = (OptionId) optionData; 158 159 if (text != null) { 160 //textPanel.addParagraph(text, Global.getSettings().getColor("buttonText")); 161 dialog.addOptionSelectedText(option); 162 //textPanel.addParagraph(""); 163 } 164 165 switch (option) { 166 case INIT: 167 selectShipsAndWeapons(); 168 break; 169 case SELECT_SHIPS: 170 showBlueprintPicker(); 171 break; 172 case ADD_UNSELECTED_OFFICERS: 173 showOfficerSelector(); 174 break; 175 case OFFICERS_GO_BACK: 176 selectShipsAndWeapons(); 177 break; 178 case OFFICER_LEVEL_5: 179 addNoSkillOfficer(5); 180 break; 181 case OFFICER_LEVEL_6: 182 addNoSkillOfficer(6); 183 break; 184 case OFFICER_LEVEL_7: 185 addNoSkillOfficer(7); 186 break; 187 case QUALITY_MAX_DMODS: 188 data.quality = FleetQuality.MAX_DMODS; 189 selectOfficerLevel(); 190 break; 191 case QUALITY_NO_DMODS: 192 data.quality = FleetQuality.NO_DMODS; 193 selectOfficerLevel(); 194 break; 195 case QUALITY_1_SMODS: 196 data.quality = FleetQuality.ONE_SMOD; 197 selectOfficerLevel(); 198 break; 199 case QUALITY_2_SMODS: 200 data.quality = FleetQuality.TWO_SMODS; 201 selectOfficerLevel(); 202 break; 203 case QUALITY_2_SMODS_OMEGA: 204 data.quality = FleetQuality.TWO_SMODS; 205 data.allowOmega = true; 206 selectOfficerLevel(); 207 break; 208 case QUALITY_3_SMODS: 209 data.quality = FleetQuality.THREE_SMODS; 210 selectOfficerLevel(); 211 break; 212 case QUALITY_3_SMODS_OMEGA: 213 data.quality = FleetQuality.THREE_SMODS; 214 data.allowOmega = true; 215 selectOfficerLevel(); 216 break; 217 case NO_OFFICERS: 218 data.addOfficers = false; 219 selectAppendOrReplace(); 220 break; 221 case ADD_OFFICERS: 222 data.addOfficers = true; 223 selectAppendOrReplace(); 224 break; 225 case ADD_OFFICERS_NO_SKILLS: 226 data.addOfficers = true; 227 data.keepOfficerPointsFree = true; 228 selectAppendOrReplace(); 229 break; 230// case NORMAL_LEVEL_OFFICERS: 231// data.eliteOfficers = false; 232// selectAppendOrReplace(); 233// break; 234// case HIGH_LEVEL_OFFICERS: 235// data.eliteOfficers = true; 236// selectAppendOrReplace(); 237// break; 238 case CREATE_ADD: 239 data.append = true; 240 createFleet(); 241 dialog.dismiss(); 242 break; 243 case CREATE_REPLACE: 244 data.append = false; 245 createFleet(); 246 dialog.dismiss(); 247 break; 248 case LEAVE: 249 dialog.dismiss(); 250 break; 251 default: 252 break; 253 } 254 } 255 256 protected MarketAPI getNearestMarket(boolean playerOnly) { 257 MarketAPI nearest = null; 258 float minDist = Float.MAX_VALUE; 259 CampaignFleetAPI pf = Global.getSector().getPlayerFleet(); 260 for (MarketAPI curr : Global.getSector().getEconomy().getMarketsCopy()) { 261 if (curr.isHidden()) continue; 262 if (playerOnly && !curr.isPlayerOwned()) continue; 263 264 float dist = Misc.getDistanceLY(pf, curr.getPrimaryEntity()); 265 boolean nearer = dist < minDist; 266 if (dist == minDist && dist == 0 && nearest != null) { 267 float d1 = Misc.getDistance(pf, curr.getPrimaryEntity()); 268 float d2 = Misc.getDistance(pf, nearest.getPrimaryEntity()); 269 nearer = d1 < d2; 270 } 271 if (nearer) { 272 nearest = curr; 273 minDist = dist; 274 } 275 } 276 return nearest; 277 } 278 279 protected void print(String str) { 280 textPanel.appendToLastParagraph("\n" + str); 281 System.out.println(str); 282 } 283 284 protected void selectShipsAndWeapons() { 285 textPanel.addPara("--- QUICK PLAYER FLEET CREATOR ---"); 286 textPanel.addPara("Ship variants will be randomly picked from those available for a given hull."); 287 textPanel.addPara("Selected weapons and fighters will be added to your cargo."); 288 289 options.clearOptions(); 290 options.addOption("Select ships and weapons", OptionId.SELECT_SHIPS); 291 options.addOption("Add officers with unselected skills", OptionId.ADD_UNSELECTED_OFFICERS); 292 options.addOption("Leave", OptionId.LEAVE, null); 293 } 294 295 protected void showOfficerSelector() { 296 textPanel.addPara("Select option to instantly add an officer of that level with all unspent skills. The officer " 297 + "level will be 1 higher than selected and they will not have an initial skill."); 298 options.clearOptions(); 299 options.addOption("Level 5", OptionId.OFFICER_LEVEL_5); 300 options.addOption("Level 6", OptionId.OFFICER_LEVEL_6); 301 options.addOption("Level 7", OptionId.OFFICER_LEVEL_7); 302 options.addOption("Go back", OptionId.OFFICERS_GO_BACK); 303 } 304 305 protected void selectQuality() { 306 textPanel.addPara("Select ship quality."); 307 options.clearOptions(); 308 options.addOption("Maximum number of d-mods", OptionId.QUALITY_MAX_DMODS); 309 options.addOption("No d-mods", OptionId.QUALITY_NO_DMODS); 310 options.addOption("Average of 1 s-mod", OptionId.QUALITY_1_SMODS); 311 options.addOption("Average of 2 s-mods", OptionId.QUALITY_2_SMODS); 312 options.addOption("Average of 2 s-mods; allow some Omega weapons to be mounted", OptionId.QUALITY_2_SMODS_OMEGA); 313 options.addOption("Average of 3 s-mods", OptionId.QUALITY_3_SMODS); 314 options.addOption("Average of 3 s-mods; allow some Omega weapons to be mounted", OptionId.QUALITY_3_SMODS_OMEGA); 315 options.addOption("Leave", OptionId.LEAVE, null); 316 } 317 318 protected void selectOfficerLevel() { 319 textPanel.addPara("The total number of officers and their level and elite skills will be based on your character's skills."); 320 options.clearOptions(); 321 options.addOption("Add officers (number / level / elite skills based on your character's skills)", OptionId.ADD_OFFICERS); 322 //options.addOption("Add officers, but do not spend their skill points", OptionId.ADD_OFFICERS_NO_SKILLS); 323 options.addOption("Do not add officers", OptionId.NO_OFFICERS); 324 options.addOption("Leave", OptionId.LEAVE, null); 325 } 326 327 protected void selectAppendOrReplace() { 328 textPanel.addPara("Select fleet creation mode."); 329 options.clearOptions(); 330 options.addOption("Replace current fleet with new ships", OptionId.CREATE_REPLACE); 331 options.addOption("Add new ships to current fleet", OptionId.CREATE_ADD); 332 options.addOption("Leave", OptionId.LEAVE, null); 333 } 334 335 protected OptionId lastOptionMousedOver = null; 336 public void optionMousedOver(String optionText, Object optionData) { 337 338 } 339 340 public void advance(float amount) { 341 342 } 343 344 public Object getContext() { 345 return null; 346 } 347 348 public void showBlueprintPicker() { 349 dialog.showCustomProductionPicker(new BaseCustomProductionPickerDelegateImpl() { 350 @Override 351 public Set<String> getAvailableFighters() { 352 return fighters; 353 } 354 @Override 355 public Set<String> getAvailableShipHulls() { 356 return ships; 357 } 358 @Override 359 public Set<String> getAvailableWeapons() { 360 return weapons; 361 } 362 @Override 363 public float getCostMult() { 364 return 0.001f; 365 } 366 @Override 367 public float getMaximumValue() { 368 return 1000000; 369 } 370 public boolean withQuantityLimits() { 371 return false; 372 } 373 @Override 374 public void notifyProductionSelected(FactionProductionAPI production) { 375 //convertProdToCargo(production); 376 data.prod = production; 377 selectQuality(); 378 } 379 }); 380 } 381 382 protected void createFleet() { 383 384 FactionProductionAPI prod = data.prod; 385 CargoAPI cargo = Global.getFactory().createCargo(true); 386 387 388 Random random = Misc.random; 389 390 float quality = 2f; 391 if (data.quality == FleetQuality.MAX_DMODS) { 392 quality = -2f; 393 } 394 395 CampaignFleetAPI ships = Global.getFactory().createEmptyFleet(Factions.PLAYER, "temp", true); 396 ships.setCommander(Global.getSector().getPlayerPerson()); 397 DefaultFleetInflaterParams p = new DefaultFleetInflaterParams(); 398 p.quality = quality; 399 p.mode = ShipPickMode.PRIORITY_THEN_ALL; 400 p.persistent = false; 401 p.seed = random.nextLong(); 402 p.timestamp = null; 403 p.rProb = 0f; 404 p.allWeapons = true; 405 406 if (data.quality == FleetQuality.ONE_SMOD) { 407 p.averageSMods = 1; 408 } else if (data.quality == FleetQuality.TWO_SMODS) { 409 p.averageSMods = 2; 410 } else if (data.quality == FleetQuality.THREE_SMODS) { 411 p.averageSMods = 3; 412 } 413 414 FleetInflater inflater = Misc.getInflater(ships, p); 415 ships.setInflater(inflater); 416 417 for (ItemInProductionAPI pick : prod.getCurrent()) { 418 int count = pick.getQuantity(); 419 420 if (pick.getType() == ProductionItemType.SHIP) { 421 List<String> variants = Global.getSettings().getHullIdToVariantListMap().get(pick.getSpecId()); 422 if (variants.isEmpty()) { 423 variants.add(pick.getSpecId() + "_Hull"); 424 } 425 426 for (int i = 0; i < count; i++) { 427 int index = random.nextInt(variants.size()); 428 ships.getFleetData().addFleetMember(variants.get(index)); 429 } 430 } else if (pick.getType() == ProductionItemType.FIGHTER) { 431 cargo.addFighters(pick.getSpecId(), count); 432 } else if (pick.getType() == ProductionItemType.WEAPON) { 433 cargo.addWeapons(pick.getSpecId(), count); 434 } 435 prod.removeItem(pick.getType(), pick.getSpecId(), count); 436 } 437 438 FactionAPI pf = Global.getSector().getPlayerFaction(); 439 List<String> addedWeapons = new ArrayList<String>(); 440 List<String> addedFighters = new ArrayList<String>(); 441 List<String> addedHullmods = new ArrayList<String>(); 442 443 if (data.allowOmega) { 444 for (String id : weapons) { 445 if (unrestrictedWeapons.contains(id)) continue; 446 WeaponSpecAPI spec = Global.getSettings().getWeaponSpec(id); 447 if (spec.hasTag(Tags.OMEGA)) { 448 unrestrictedWeapons.add(id); 449 } 450 } 451 } 452 453 for (String id : unrestrictedWeapons) { 454 if (!pf.knowsWeapon(id)) { 455 addedWeapons.add(id); 456 pf.addKnownWeapon(id, false); 457 } 458 } 459 for (String id : unrestrictedFighters) { 460 if (!pf.knowsFighter(id)) { 461 addedFighters.add(id); 462 pf.addKnownFighter(id, false); 463 } 464 } 465 for (String id : hullmods) { 466 if (!pf.knowsHullMod(id)) { 467 addedHullmods.add(id); 468 pf.addKnownHullMod(id); 469 } 470 } 471 472 ships.inflateIfNeeded(); 473 474 for (String id : addedWeapons) { 475 pf.removeKnownWeapon(id); 476 } 477 for (String id : addedFighters) { 478 pf.removeKnownFighter(id); 479 } 480 for (String id : addedHullmods) { 481 pf.removeKnownHullMod(id); 482 } 483 484 if (!data.append && !ships.isEmpty()) { 485 playerFleet.getFleetData().clear(); 486 if (data.addOfficers) { 487 for (OfficerDataAPI od : playerFleet.getFleetData().getOfficersCopy()) { 488 playerFleet.getFleetData().removeOfficer(od.getPerson()); 489 } 490 } 491 } 492 493 playerFleet.getCargo().addAll(cargo); 494 495 for (FleetMemberAPI member : ships.getFleetData().getMembersListCopy()) { 496 playerFleet.getFleetData().addFleetMember(member); 497 } 498 499 if (data.addOfficers) { 500 int maxOfficers = Misc.getMaxOfficers(playerFleet); 501 int add = maxOfficers - playerFleet.getFleetData().getOfficersCopy().size(); 502 if (add > 0) { 503 FleetParamsV3 fp = new FleetParamsV3(); 504 fp.commander = playerFleet.getCommander(); 505 fp.maxOfficersToAdd = add; 506 FleetFactoryV3.addCommanderAndOfficersV2(playerFleet, fp, random); 507 } 508 } 509 510 511 playerFleet.forceSync(); 512 513 cargo = playerFleet.getCargo(); 514 515 int neededCrew = (int) ((playerFleet.getFleetData().getMinCrew() + cargo.getMaxPersonnel()) / 2f 516 - cargo.getCrew()); 517 if (neededCrew > 0) { 518 cargo.addCrew(neededCrew); 519 } 520 cargo.addFuel(cargo.getMaxFuel() - cargo.getFuel()); 521 cargo.addSupplies(cargo.getSpaceLeft() * 0.5f); 522 523 524 playerFleet.forceSync(); 525 526 for (FleetMemberAPI member : playerFleet.getFleetData().getMembersListCopy()) { 527 float max = member.getRepairTracker().getMaxCR(); 528 member.getRepairTracker().setCR(max); 529 } 530 531 532 533 } 534 535 protected void initAvailable() { 536 for (ShipHullSpecAPI spec : Global.getSettings().getAllShipHullSpecs()) { 537 //if (spec.isDefaultDHull() || spec.isDHull()) continue; 538 if (spec.isDefaultDHull()) continue; 539 if (spec.getHullSize() == HullSize.FIGHTER) continue; 540 if (!spec.hasHullName()) continue; 541 if (spec.getHints().contains(ShipTypeHints.STATION)) continue; 542// if (spec.getHints().contains(ShipTypeHints.HIDE_IN_CODEX)) continue; 543// if (spec.getHints().contains(ShipTypeHints.UNBOARDABLE)) continue; 544 if ("shuttlepod".equals(spec.getHullId())) continue; 545 ships.add(spec.getHullId()); 546 } 547 548 for (WeaponSpecAPI spec : Global.getSettings().getAllWeaponSpecs()) { 549 //if (!spec.hasTag(Items.TAG_RARE_BP) && !spec.hasTag(Items.TAG_DEALER)) continue; 550 if (spec.getAIHints().contains(AIHints.SYSTEM)) continue; 551 if (!spec.hasTag(Tags.RESTRICTED)) { 552 unrestrictedWeapons.add(spec.getWeaponId()); 553 } 554 weapons.add(spec.getWeaponId()); 555 } 556 557 for (FighterWingSpecAPI spec : Global.getSettings().getAllFighterWingSpecs()) { 558 if (!spec.hasTag(Tags.RESTRICTED)) { 559 unrestrictedFighters.add(spec.getId()); 560 } 561 fighters.add(spec.getId()); 562 } 563 564 for (HullModSpecAPI spec : Global.getSettings().getAllHullModSpecs()) { 565 hullmods.add(spec.getId()); 566 } 567 } 568 569 protected void addNoSkillOfficer(int level) { 570 PersonAPI p = Global.getSector().getPlayerFaction().createRandomPerson(); 571 p.getMemoryWithoutUpdate().set(MemFlags.OFFICER_SKILL_PICKS_PER_LEVEL, 10); 572 p.getMemoryWithoutUpdate().set(MemFlags.OFFICER_MAX_LEVEL, level + 1); 573 if (level >= 7) { 574 p.getMemoryWithoutUpdate().set(MemFlags.OFFICER_MAX_ELITE_SKILLS, 4); 575 } 576// p.getStats().setLevel(level + 1); 577 for (SkillLevelAPI sl : p.getStats().getSkillsCopy()) { 578 p.getStats().setSkillLevel(sl.getSkill().getId(), 0); 579 } 580 //p.setPersonality(Personalities.AGGRESSIVE); 581 OfficerLevelupPlugin plugin = (OfficerLevelupPlugin) Global.getSettings().getPlugin("officerLevelUp"); 582 long xp = plugin.getXPForLevel(level + 1); 583 OfficerDataAPI od = Global.getFactory().createOfficerData(p); 584 od.addXP(xp); 585 Global.getSector().getPlayerFleet().getFleetData().addOfficer(od); 586 if (level >= 7) { 587 textPanel.addPara("Added level " + (level + 1) + " officer with NO initial skill and 4 elite skills max."); 588 } else { 589 textPanel.addPara("Added level " + (level + 1) + " officer with NO initial skill."); 590 } 591 } 592} 593 594 595 596