001package com.fs.starfarer.api.impl.campaign.rulecmd; 002 003import java.util.ArrayList; 004import java.util.LinkedHashSet; 005import java.util.List; 006import java.util.Map; 007import java.util.Random; 008import java.util.Set; 009 010import com.fs.starfarer.api.Global; 011import com.fs.starfarer.api.campaign.BaseCustomProductionPickerDelegateImpl; 012import com.fs.starfarer.api.campaign.CampaignFleetAPI; 013import com.fs.starfarer.api.campaign.CargoAPI; 014import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType; 015import com.fs.starfarer.api.campaign.FactionAPI; 016import com.fs.starfarer.api.campaign.FactionAPI.ShipPickMode; 017import com.fs.starfarer.api.campaign.FactionAPI.ShipPickParams; 018import com.fs.starfarer.api.campaign.FactionProductionAPI; 019import com.fs.starfarer.api.campaign.FactionProductionAPI.ItemInProductionAPI; 020import com.fs.starfarer.api.campaign.FactionProductionAPI.ProductionItemType; 021import com.fs.starfarer.api.campaign.FleetEncounterContextPlugin.DataForEncounterSide; 022import com.fs.starfarer.api.campaign.FleetEncounterContextPlugin.FleetMemberData; 023import com.fs.starfarer.api.campaign.InteractionDialogAPI; 024import com.fs.starfarer.api.campaign.OptionPanelAPI; 025import com.fs.starfarer.api.campaign.SectorEntityToken; 026import com.fs.starfarer.api.campaign.SpecialItemData; 027import com.fs.starfarer.api.campaign.SpecialItemPlugin.RightClickActionHelper; 028import com.fs.starfarer.api.campaign.TextPanelAPI; 029import com.fs.starfarer.api.campaign.impl.items.ShroudedHullmodItemPlugin; 030import com.fs.starfarer.api.campaign.impl.items.ShroudedSubstratePlugin; 031import com.fs.starfarer.api.campaign.rules.MemKeys; 032import com.fs.starfarer.api.campaign.rules.MemoryAPI; 033import com.fs.starfarer.api.combat.BattleCreationContext; 034import com.fs.starfarer.api.combat.ShipVariantAPI; 035import com.fs.starfarer.api.fleet.FleetMemberAPI; 036import com.fs.starfarer.api.fleet.ShipRolePick; 037import com.fs.starfarer.api.impl.campaign.AbyssalLightEntityPlugin; 038import com.fs.starfarer.api.impl.campaign.AbyssalLightEntityPlugin.DespawnType; 039import com.fs.starfarer.api.impl.campaign.FleetEncounterContext; 040import com.fs.starfarer.api.impl.campaign.FleetInteractionDialogPluginImpl; 041import com.fs.starfarer.api.impl.campaign.FleetInteractionDialogPluginImpl.BaseFIDDelegate; 042import com.fs.starfarer.api.impl.campaign.FleetInteractionDialogPluginImpl.FIDConfig; 043import com.fs.starfarer.api.impl.campaign.RuleBasedInteractionDialogPluginImpl; 044import com.fs.starfarer.api.impl.campaign.ids.Factions; 045import com.fs.starfarer.api.impl.campaign.ids.FleetTypes; 046import com.fs.starfarer.api.impl.campaign.ids.Items; 047import com.fs.starfarer.api.impl.campaign.ids.MemFlags; 048import com.fs.starfarer.api.impl.campaign.ids.ShipRoles; 049import com.fs.starfarer.api.impl.campaign.ids.Tags; 050import com.fs.starfarer.api.loading.HullModSpecAPI; 051import com.fs.starfarer.api.loading.WeaponSpecAPI; 052import com.fs.starfarer.api.util.ListMap; 053import com.fs.starfarer.api.util.Misc; 054import com.fs.starfarer.api.util.Misc.Token; 055import com.fs.starfarer.api.util.WeightedRandomPicker; 056 057/** 058 */ 059public class DwellerCMD extends BaseCommandPlugin { 060 061 public static enum DwellerStrength { 062 LOW, 063 MEDIUM, 064 HIGH, 065 EXTREME, 066 } 067 068 public static String SHROUDED_TENDRIL = "shrouded_tendril"; 069 public static String SHROUDED_EYE = "shrouded_eye"; 070 public static String SHROUDED_MAELSTROM = "shrouded_maelstrom"; 071 public static String SHROUDED_MAW = "shrouded_maw"; 072 073 074 public static ListMap<String> GUARANTEED_FIRST_TIME_ITEMS = new ListMap<>(); 075 static { 076 GUARANTEED_FIRST_TIME_ITEMS.add(SHROUDED_EYE, Items.SHROUDED_LENS); 077 GUARANTEED_FIRST_TIME_ITEMS.add(SHROUDED_MAELSTROM, Items.SHROUDED_THUNDERHEAD); 078 GUARANTEED_FIRST_TIME_ITEMS.add(SHROUDED_MAW, Items.SHROUDED_MANTLE); 079 } 080 081 public static ListMap<String> DROP_GROUPS = new ListMap<>(); 082 static { 083 DROP_GROUPS.add(SHROUDED_EYE, "drops_shrouded_eye"); 084 DROP_GROUPS.add(SHROUDED_MAELSTROM, "drops_shrouded_maelstrom"); 085 DROP_GROUPS.add(SHROUDED_MAW, "drops_shrouded_maw"); 086 } 087 088 089 090 public boolean execute(String ruleId, InteractionDialogAPI dialog, List<Token> params, Map<String, MemoryAPI> memoryMap) { 091 if (dialog == null) return false; 092 093 OptionPanelAPI options = dialog.getOptionPanel(); 094 TextPanelAPI text = dialog.getTextPanel(); 095 CampaignFleetAPI pf = Global.getSector().getPlayerFleet(); 096 CargoAPI cargo = pf.getCargo(); 097 098 final SectorEntityToken entity = dialog.getInteractionTarget(); 099 long seed = Misc.getSalvageSeed(entity); 100 Random random = Misc.getRandom(seed, 11); 101 //random = new Random(); 102 103 String action = params.get(0).getString(memoryMap); 104 105 MemoryAPI memory = memoryMap.get(MemKeys.LOCAL); 106 if (memory == null) return false; // should not be possible unless there are other big problems already 107 108 if ("smallFleet".equals(action)) { 109 return engageFleet(dialog, memoryMap, memory, DwellerStrength.LOW, random); 110 } else if ("mediumFleet".equals(action)) { 111 return engageFleet(dialog, memoryMap, memory, DwellerStrength.MEDIUM, random); 112 } else if ("largeFleet".equals(action)) { 113 return engageFleet(dialog, memoryMap, memory, DwellerStrength.HIGH, random); 114 } else if ("hugeFleet".equals(action)) { 115 return engageFleet(dialog, memoryMap, memory, DwellerStrength.EXTREME, random); 116 } else if ("showWeaponPicker".equals(action)) { 117 showWeaponPicker(dialog, memoryMap); 118 return true; 119 } else if ("unlockHullmod".equals(action)) { 120 unlockHullmod(dialog, memoryMap); 121 return true; 122 } 123 return false; 124 } 125 126 protected void unlockHullmod(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) { 127 String modId = Global.getSector().getPlayerMemoryWithoutUpdate().getString( 128 ShroudedHullmodItemPlugin.SHROUDED_HULLMOD_ID); 129 HullModSpecAPI modSpec = Global.getSettings().getHullModSpec(modId); 130 131 Global.getSoundPlayer().playUISound("ui_acquired_hullmod", 1, 1); 132 TextPanelAPI text = dialog.getTextPanel(); 133 text.setFontSmallInsignia(); 134 String str = modSpec.getDisplayName(); 135 text.addParagraph("Acquired hull mod: " + str + "", Misc.getPositiveHighlightColor()); 136 text.highlightInLastPara(Misc.getHighlightColor(), str); 137 text.setFontInsignia(); 138 139// Global.getSector().getCampaignUI().getMessageDisplay().addMessage( 140// "Acquired hull mod: " + modSpec.getDisplayName() + ""); 141 142 Global.getSector().getPlayerFaction().addKnownHullMod(modId);; 143 } 144 145 public static int getSubstrateCost(WeaponSpecAPI spec) { 146 if (!spec.hasTag(Tags.DWELLER)) return 0; 147 String substrate = "substrate_"; 148 for (String tag : spec.getTags()) { 149 if (tag.startsWith(substrate)) { 150 String num = tag.replaceFirst(substrate, ""); 151 return Integer.parseInt(num); 152 } 153 } 154 return 0; 155 } 156 157 protected void showWeaponPicker(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) { 158 159 int substrate = Global.getSector().getPlayerMemoryWithoutUpdate().getInt(ShroudedSubstratePlugin.SHROUDED_SUBSTRATE_AVAILABLE); 160 161 Set<String> weapons = new LinkedHashSet<>(); 162 for (WeaponSpecAPI spec : Global.getSettings().getAllWeaponSpecs()) { 163 int cost = getSubstrateCost(spec); 164 if (cost > 0 && cost <= substrate) { 165 weapons.add(spec.getWeaponId()); 166 } 167 } 168 169 dialog.showCustomProductionPicker(new BaseCustomProductionPickerDelegateImpl() { 170 @Override 171 public Set<String> getAvailableFighters() { 172 return new LinkedHashSet<>(); 173 } 174 @Override 175 public Set<String> getAvailableShipHulls() { 176 return new LinkedHashSet<>(); 177 } 178 @Override 179 public Set<String> getAvailableWeapons() { 180 return weapons; 181 } 182 @Override 183 public float getCostMult() { 184 return 1f; 185 } 186 @Override 187 public float getMaximumValue() { 188 return substrate; 189 } 190 191 @Override 192 public String getWeaponColumnNameOverride() { 193 return "Weapon"; 194 } 195 196 @Override 197 public String getNoMatchingBlueprintsLabelOverride() { 198 return "No matching weapons"; 199 } 200 201 @Override 202 public String getMaximumOrderValueLabelOverride() { 203 return "Shrouded Substrate available"; 204 } 205 206 @Override 207 public String getCurrentOrderValueLabelOverride() { 208 return "Shrouded Substrate required"; 209 } 210 @Override 211 public String getItemGoesOverMaxValueStringOverride() { 212 return "Not enough Shrouded Substrate"; 213 } 214 @Override 215 public String getCustomOrderLabelOverride() { 216 return "Weapon assembly"; 217 } 218 @Override 219 public String getNoProductionOrdersLabelOverride() { 220 return "No assembly orders"; 221 } 222 @Override 223 public boolean withQuantityLimits() { 224 return false; 225 } 226 @Override 227 public boolean isUseCreditSign() { 228 return false; 229 } 230 231 @Override 232 public int getCostOverride(Object item) { 233 if (item instanceof WeaponSpecAPI) { 234 return getSubstrateCost((WeaponSpecAPI) item); 235 } 236 return -1; 237 } 238 239 @Override 240 public void notifyProductionSelected(FactionProductionAPI production) { 241 if (!(dialog.getPlugin() instanceof RuleBasedInteractionDialogPluginImpl)) return; 242 RuleBasedInteractionDialogPluginImpl plugin = (RuleBasedInteractionDialogPluginImpl) dialog.getPlugin(); 243 if (!(plugin.getCustom1() instanceof RightClickActionHelper)) return; 244 RightClickActionHelper helper = (RightClickActionHelper) plugin.getCustom1(); 245 246 int cost = production.getTotalCurrentCost(); 247 helper.removeFromClickedStackFirst(cost); 248 int substrate = (int) helper.getNumItems(CargoItemType.SPECIAL, new SpecialItemData(Items.SHROUDED_SUBSTRATE, null)); 249 Global.getSector().getPlayerMemoryWithoutUpdate().set(ShroudedSubstratePlugin.SHROUDED_SUBSTRATE_AVAILABLE, substrate); 250 251 for (ItemInProductionAPI item : production.getCurrent()) { 252 if (item.getType() == ProductionItemType.WEAPON) { 253 helper.addItems(CargoItemType.WEAPONS, item.getSpecId(), item.getQuantity()); 254 AddRemoveCommodity.addWeaponGainText(item.getSpecId(), item.getQuantity(), dialog.getTextPanel()); 255 } 256 } 257 258 FireBest.fire(null, dialog, memoryMap, "SubstrateWeaponsPicked"); 259 260 Global.getSoundPlayer().playUISound("ui_cargo_machinery_drop", 1f, 1f); 261 } 262 }); 263 } 264 265 266 protected boolean engageFleet(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap, MemoryAPI memory, DwellerStrength str, Random random) { 267 CampaignFleetAPI fleet = createDwellerFleet(str, random); 268 if (fleet == null) return false; 269 270 CampaignFleetAPI pf = Global.getSector().getPlayerFleet(); 271 fleet.setContainingLocation(pf.getContainingLocation()); 272 273 final SectorEntityToken entity = dialog.getInteractionTarget(); 274 275 dialog.setInteractionTarget(fleet); 276 277 Global.getSector().getCampaignUI().restartEncounterMusic(fleet); 278 279 FIDConfig config = new FIDConfig(); 280 281 config.delegate = new BaseFIDDelegate() { 282 public void postPlayerSalvageGeneration(InteractionDialogAPI dialog, FleetEncounterContext context, CargoAPI salvage) { 283 if (!(dialog.getInteractionTarget() instanceof CampaignFleetAPI)) return; 284 285 float mult = context.computePlayerContribFraction(); 286 287 CampaignFleetAPI fleet = (CampaignFleetAPI) dialog.getInteractionTarget(); 288 289 DataForEncounterSide data = context.getDataFor(fleet); 290 List<FleetMemberAPI> losses = new ArrayList<FleetMemberAPI>(); 291 for (FleetMemberData fmd : data.getOwnCasualties()) { 292 losses.add(fmd.getMember()); 293 } 294 295 float min = 0f; 296 float max = 0f; 297 boolean gotGuaranteed = false; 298 for (FleetMemberAPI member : losses) { 299 if (member.getHullSpec().hasTag(Tags.DWELLER)) { 300 String key = "substrate_"; 301 float [] sDrops = Misc.getFloatArray(key + member.getHullSpec().getHullId()); 302 if (sDrops == null) { 303 sDrops = Misc.getFloatArray(key + member.getHullSpec().getHullSize().name()); 304 } 305 if (sDrops == null) continue; 306 307 min += sDrops[0]; 308 max += sDrops[1]; 309 310 String hullId = member.getHullSpec().getRestoredToHullId(); 311 String defeatedKey = "$defeatedDweller_" + hullId; 312 boolean firstTime = !Global.getSector().getPlayerMemoryWithoutUpdate().getBoolean(defeatedKey); 313 Global.getSector().getPlayerMemoryWithoutUpdate().set(defeatedKey, true); 314 if (firstTime && !gotGuaranteed) { 315 List<String> drops = GUARANTEED_FIRST_TIME_ITEMS.get(hullId); 316 for (String itemId : drops) { 317 SpecialItemData sid = new SpecialItemData(itemId, null); 318 boolean add = firstTime && salvage.getQuantity(CargoItemType.SPECIAL, sid) <= 0; 319 if (add) { 320 salvage.addItems(CargoItemType.SPECIAL, sid, 1); 321 gotGuaranteed = true; 322 } 323 } 324 } 325 } 326 } 327 328 long seed = Misc.getSalvageSeed(entity); 329 Random random = Misc.getRandom(seed, 50); 330 int substrate = 0; 331 if (min + max < 1f) { 332 if (random.nextFloat() < (min + max) / 2f) { 333 substrate = 1; 334 } 335 } else { 336 substrate = (int) Math.round(min + (max - min) * random.nextFloat()); 337 } 338 339 if (substrate > 0) { 340 salvage.addItems(CargoItemType.SPECIAL, new SpecialItemData(Items.SHROUDED_SUBSTRATE, null), substrate); 341 } 342 } 343 344 public void battleContextCreated(InteractionDialogAPI dialog, BattleCreationContext bcc) { 345 bcc.aiRetreatAllowed = false; 346 bcc.fightToTheLast = true; 347 bcc.objectivesAllowed = false; 348 bcc.enemyDeployAll = true; 349 350 // despawn the light here - the salvage gen method is only called if the player won 351 // but want to despawn the light after any fight, regardless 352 if (entity.getCustomPlugin() instanceof AbyssalLightEntityPlugin) { 353 AbyssalLightEntityPlugin plugin = (AbyssalLightEntityPlugin) entity.getCustomPlugin(); 354 plugin.despawn(DespawnType.FADE_OUT); 355 } 356 } 357 }; 358 359 config.alwaysAttackVsAttack = true; 360 //config.alwaysPursue = true; 361 config.alwaysHarry = true; 362 config.showTransponderStatus = false; 363 //config.showEngageText = false; 364 config.lootCredits = false; 365 366 config.showCommLinkOption = false; 367 config.showEngageText = false; 368 config.showFleetAttitude = false; 369 config.showTransponderStatus = false; 370 config.showWarningDialogWhenNotHostile = false; 371 config.impactsAllyReputation = false; 372 config.impactsEnemyReputation = false; 373 config.pullInAllies = false; 374 config.pullInEnemies = false; 375 config.pullInStations = false; 376 377 config.showCrRecoveryText = false; 378 config.firstTimeEngageOptionText = "\"Battle stations!\""; 379 config.afterFirstTimeEngageOptionText = "Move in to re-engage"; 380 381 if (str == DwellerStrength.LOW) { 382 config.firstTimeEngageOptionText = null; 383 config.leaveAlwaysAvailable = true; 384 } else { 385 config.leaveAlwaysAvailable = true; // except for first engagement 386 config.noLeaveOptionOnFirstEngagement = true; 387 } 388 //config.noLeaveOption = true; 389 390// config.noSalvageLeaveOptionText = "Continue"; 391 392// config.dismissOnLeave = false; 393// config.printXPToDialog = true; 394 395 long seed = Misc.getSalvageSeed(entity); 396 config.salvageRandom = Misc.getRandom(seed, 75); 397 398 Global.getSector().getPlayerMemoryWithoutUpdate().set("$encounteredDweller", true); 399 Global.getSector().getPlayerMemoryWithoutUpdate().set("$encounteredMonster", true); 400 Global.getSector().getPlayerMemoryWithoutUpdate().set("$encounteredWeird", true); 401 402 final FleetInteractionDialogPluginImpl plugin = new FleetInteractionDialogPluginImpl(config); 403 404 //final InteractionDialogPlugin originalPlugin = dialog.getPlugin(); 405 406 dialog.setPlugin(plugin); 407 plugin.init(dialog); 408 409 410 return true; 411 } 412 413 414 415 public static CampaignFleetAPI createDwellerFleet(DwellerStrength str, Random random) { 416 CampaignFleetAPI f = Global.getFactory().createEmptyFleet(Factions.DWELLER, "Manifestation", true); 417 418 FactionAPI faction = Global.getSector().getFaction(Factions.DWELLER); 419 String typeKey = FleetTypes.PATROL_SMALL; 420 if (str == DwellerStrength.MEDIUM) typeKey = FleetTypes.PATROL_MEDIUM; 421 if (str == DwellerStrength.HIGH) typeKey = FleetTypes.PATROL_LARGE; 422 if (str == DwellerStrength.EXTREME) typeKey = FleetTypes.PATROL_LARGE; 423 f.setName(faction.getFleetTypeName(typeKey)); 424 425 f.setInflater(null); 426 427 if (str == DwellerStrength.LOW) { 428 addShips(f, 6, 8, random, ShipRoles.DWELLER_TENDRIL); 429 addShips(f, 1, 1, random, ShipRoles.DWELLER_EYE); 430 addShips(f, 1, 2, random, ShipRoles.DWELLER_MAELSTROM); 431 } else if (str == DwellerStrength.MEDIUM) { 432 addShips(f, 9, 12, random, ShipRoles.DWELLER_TENDRIL); 433 int eyes = addShips(f, 1, 1, random, ShipRoles.DWELLER_EYE); 434 addShips(f, 2 - eyes, 3 - eyes, random, ShipRoles.DWELLER_MAELSTROM); 435 addShips(f, 1, 1, random, ShipRoles.DWELLER_MAW); 436 } else if (str == DwellerStrength.HIGH) { 437 addShips(f, 11, 14, random, ShipRoles.DWELLER_TENDRIL); 438 int eyes = addShips(f, 2, 3, random, ShipRoles.DWELLER_EYE); 439 addShips(f, 3 - eyes, 5 - eyes, random, ShipRoles.DWELLER_MAELSTROM); 440 addShips(f, 1, 1, random, ShipRoles.DWELLER_MAW); 441 } else if (str == DwellerStrength.EXTREME) { 442 addShips(f, 12, 15, random, ShipRoles.DWELLER_TENDRIL); 443 int eyes = addShips(f, 2, 3, random, ShipRoles.DWELLER_EYE); 444 addShips(f, 3 - eyes, 5 - eyes, random, ShipRoles.DWELLER_MAELSTROM); 445 addShips(f, 2, 2, random, ShipRoles.DWELLER_MAW); 446 } 447 448 f.getFleetData().setSyncNeeded(); 449 f.getFleetData().syncIfNeeded(); 450 f.getFleetData().sort(); 451 452 for (FleetMemberAPI curr : f.getFleetData().getMembersListCopy()) { 453 curr.getRepairTracker().setCR(curr.getRepairTracker().getMaxCR()); 454 455 // tag is added to ships now 456// ShipVariantAPI v = curr.getVariant().clone(); 457// v.addTag(Tags.LIMITED_TOOLTIP_IF_LOCKED); 458// curr.setVariant(v, false, false); 459 } 460 461 462// f.getMemoryWithoutUpdate().set(MemFlags.FLEET_INTERACTION_DIALOG_CONFIG_OVERRIDE_GEN, 463// new DwellerFIDConfig()); 464// f.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_MAKE_AGGRESSIVE, true); 465 466 // required for proper music track to play, see: DwellerCMD 467 f.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_MAKE_HOSTILE, true); 468 469// //f.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_MAKE_ALWAYS_PURSUE, true); 470// f.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_ALLOW_LONG_PURSUIT, true); 471 f.getMemoryWithoutUpdate().set(MemFlags.MAY_GO_INTO_ABYSS, true); 472 473 return f; 474 } 475 476 477 public static int addShips(CampaignFleetAPI fleet, int min, int max, Random random, Object ... roles) { 478 if (min < 0) min = 0; 479 if (max < 0) max = 0; 480 481 WeightedRandomPicker<String> picker = new WeightedRandomPicker<>(); 482 if (roles.length == 1) { 483 picker.add((String) roles[0], 1f); 484 } else { 485 for (int i = 0; i < roles.length; i += 2) { 486 picker.add((String) roles[i], (float) roles[i + 1]); 487 } 488 } 489 int num = min + random.nextInt(max - min + 1); 490 FactionAPI faction = Global.getSector().getFaction(Factions.DWELLER); 491 492 ShipPickParams p = new ShipPickParams(ShipPickMode.ALL); 493 p.blockFallback = true; 494 p.maxFP = 1000000; 495 for (int i = 0; i < num; i++) { 496 String role = picker.pick(); 497 List<ShipRolePick> picks = faction.pickShip(role, p, null, random); 498 for (ShipRolePick pick : picks) { 499 fleet.getFleetData().addFleetMember(pick.variantId); 500 501 ShipVariantAPI variant = Global.getSettings().getVariant(pick.variantId); 502 if (variant != null) { 503 String hullId = variant.getHullSpec().getRestoredToHullId(); 504 List<String> dropGroups = DROP_GROUPS.get(hullId); 505 for (String group : dropGroups) { 506 fleet.addDropRandom(group, 1); 507 } 508 } 509 } 510 } 511 return num; 512 } 513} 514