From 446df8f80a78ed0d5af36c51404f6ca7c2c573e3 Mon Sep 17 00:00:00 2001 From: Kayla Glick <12898988+kayla-glick@users.noreply.github.com> Date: Tue, 30 Jan 2024 12:18:54 -0500 Subject: [PATCH] update settings/consumes (#4208) --- ui/balance_druid/sim.ts | 16 +- ui/core/components/icon_enum_picker.tsx | 27 +- ui/core/components/icon_inputs.ts | 716 +++++------------- ui/core/components/icon_picker.tsx | 14 +- .../individual_sim_ui/consumes_picker.ts | 343 ++++----- .../individual_sim_ui/settings_tab.ts | 123 +-- ui/core/components/input_helpers.ts | 20 +- ui/core/components/inputs/buffs_debuffs.ts | 549 ++++++++++++++ ui/core/components/inputs/consumables.ts | 341 +++++++++ ui/core/components/inputs/stat_options.ts | 60 ++ ui/core/components/multi_icon_picker.ts | 18 +- ui/core/individual_sim_ui.ts | 17 - ui/deathknight/sim.ts | 15 +- ui/elemental_shaman/sim.ts | 1 + ui/enhancement_shaman/sim.ts | 13 +- ui/feral_druid/sim.ts | 8 +- ui/feral_tank_druid/sim.ts | 12 +- ui/hunter/sim.ts | 9 +- ui/protection_paladin/sim.ts | 4 +- ui/protection_warrior/sim.ts | 4 +- ui/raid/settings_tab.ts | 4 +- ui/retribution_paladin/sim.ts | 4 +- ui/rogue/sim.ts | 10 +- ui/scss/core/components/_icon_picker.scss | 2 +- ui/shadow_priest/sim.ts | 17 +- ui/tank_deathknight/sim.ts | 4 +- ui/warlock/sim.ts | 28 +- ui/warrior/sim.ts | 8 +- 28 files changed, 1447 insertions(+), 940 deletions(-) create mode 100644 ui/core/components/inputs/buffs_debuffs.ts create mode 100644 ui/core/components/inputs/consumables.ts create mode 100644 ui/core/components/inputs/stat_options.ts diff --git a/ui/balance_druid/sim.ts b/ui/balance_druid/sim.ts index 1190eb4579..2be382d332 100644 --- a/ui/balance_druid/sim.ts +++ b/ui/balance_druid/sim.ts @@ -13,7 +13,7 @@ import { getSpecIcon, specNames } from '../core/proto_utils/utils.js'; import { Player } from '../core/player.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; -import * as IconInputs from '../core/components/icon_inputs.js'; +import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as DruidInputs from './inputs.js'; @@ -83,13 +83,13 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecBalanceDruid, { ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ - IconInputs.MeleeHasteBuff, - IconInputs.MeleeCritBuff, - IconInputs.AttackPowerPercentBuff, - IconInputs.AttackPowerBuff, - IconInputs.MajorArmorDebuff, - IconInputs.MinorArmorDebuff, - IconInputs.PhysicalDamageDebuff, + BuffDebuffInputs.MeleeHasteBuff, + BuffDebuffInputs.MeleeCritBuff, + BuffDebuffInputs.AttackPowerPercentBuff, + BuffDebuffInputs.AttackPowerBuff, + BuffDebuffInputs.MajorArmorDebuff, + BuffDebuffInputs.MinorArmorDebuff, + BuffDebuffInputs.PhysicalDamageDebuff, ], excludeBuffDebuffInputs: [ ], diff --git a/ui/core/components/icon_enum_picker.tsx b/ui/core/components/icon_enum_picker.tsx index 1a1c3e6d92..816d02aef6 100644 --- a/ui/core/components/icon_enum_picker.tsx +++ b/ui/core/components/icon_enum_picker.tsx @@ -1,10 +1,16 @@ import { Tooltip } from 'bootstrap'; +// eslint-disable-next-line unused-imports/no-unused-imports +import { element, fragment } from 'tsx-vanilla'; + import { ActionId } from '../proto_utils/action_id.js'; import { TypedEvent } from '../typed_event.js'; import { Input, InputConfig } from './input.js'; -import { element, fragment } from 'tsx-vanilla'; +export enum IconEnumPickerDirection { + Vertical = 'vertical', + Horizontal = 'Horizontal', +} export interface IconEnumValueConfig { value: T, @@ -21,13 +27,15 @@ export interface IconEnumValueConfig { } export interface IconEnumPickerConfig extends InputConfig { - numColumns: number, + numColumns?: number, values: Array>; // Value that will be considered inactive. zeroValue: T, // Function for comparing two values. // Tooltip that will be shown whne hovering over the icon-picker-button tooltip?: string, + // The direction the menu will open in relative to the root element + direction?: IconEnumPickerDirection, equals: (a: T, b: T) => boolean, backupIconUrl?: (value: T) => ActionId, showWhen?: (obj: ModObject) => boolean, @@ -50,7 +58,7 @@ export class IconEnumPicker extends Input { this.currentValue = this.config.zeroValue; if (config.showWhen) { - config.changedEvent(this.modObject).on(eventID => { + config.changedEvent(this.modObject).on(_eventID => { const show = config.showWhen && config.showWhen(this.modObject); if (!show) this.rootElem.classList.add('hide'); @@ -89,9 +97,13 @@ export class IconEnumPicker extends Input { this.buttonText = this.buttonElem.querySelector('.icon-picker-label') as HTMLElement; const dropdownMenu = this.rootElem.querySelector('.dropdown-menu') as HTMLElement; - dropdownMenu.style.gridTemplateColumns = `repeat(${this.config.numColumns}, 1fr)`; + if (this.config.numColumns) + dropdownMenu.style.gridTemplateColumns = `repeat(${this.config.numColumns}, 1fr)`; + + if (this.config.direction == IconEnumPickerDirection.Horizontal) + dropdownMenu.style.gridAutoFlow = 'column'; - config.values.forEach((valueConfig, i) => { + config.values.forEach((valueConfig, _i) => { const optionContainer = document.createElement('li'); optionContainer.classList.add('icon-dropdown-option', 'dropdown-option') dropdownMenu.appendChild(optionContainer); @@ -116,8 +128,11 @@ export class IconEnumPicker extends Input { }); } + const show = !valueConfig.showWhen || valueConfig.showWhen(this.modObject); + if (!show) optionContainer.classList.add('hide') + if (valueConfig.showWhen) { - config.changedEvent(this.modObject).on(eventID => { + config.changedEvent(this.modObject).on(_eventID => { const show = valueConfig.showWhen && valueConfig.showWhen(this.modObject); if (show) optionContainer.classList.remove('hide'); diff --git a/ui/core/components/icon_inputs.ts b/ui/core/components/icon_inputs.ts index e37db68382..59fb2f5a82 100644 --- a/ui/core/components/icon_inputs.ts +++ b/ui/core/components/icon_inputs.ts @@ -1,29 +1,22 @@ -import { ActionId } from '../proto_utils/action_id.js'; -import { BattleElixir } from '../proto/common.js'; -import { Explosive } from '../proto/common.js'; -import { Flask } from '../proto/common.js'; -import { Food } from '../proto/common.js'; -import { GuardianElixir } from '../proto/common.js'; -import { RaidBuffs } from '../proto/common.js'; -import { PartyBuffs } from '../proto/common.js'; -import { IndividualBuffs } from '../proto/common.js'; -import { Conjured } from '../proto/common.js'; -import { Consumes } from '../proto/common.js'; -import { Debuffs } from '../proto/common.js'; - -import { PetFood } from '../proto/common.js'; -import { Potions } from '../proto/common.js'; -import { Spec } from '../proto/common.js'; -import { TristateEffect } from '../proto/common.js'; import { Party } from '../party.js'; -import { Player } from '../player.js'; -import { Raid } from '../raid.js'; -import { EventID, TypedEvent } from '../typed_event.js'; +import { Player } from '../player'; +import { + Consumes, + Debuffs, + Faction, + IndividualBuffs, + PartyBuffs, + RaidBuffs, + Spec, +} from '../proto/common.js'; +import { ActionId } from '../proto_utils/action_id.js'; +import { Raid } from '../raid'; +import { EventID, TypedEvent } from '../typed_event'; -import { IconPicker, } from './icon_picker.js'; -import { IconEnumPicker, IconEnumValueConfig } from './icon_enum_picker.js'; +import { IconEnumPicker } from './icon_enum_picker'; +import { IconPicker } from './icon_picker'; -import * as InputHelpers from './input_helpers.js'; +import * as InputHelpers from './input_helpers'; // Component Functions @@ -40,543 +33,190 @@ export const buildIconInput = (parent: HTMLElement, player: Player, inputC } }; -// Raid Buffs - -export const AllStatsBuff = InputHelpers.makeMultiIconInput([ - makeTristateRaidBuffInput(ActionId.fromSpellId(48470), ActionId.fromSpellId(17051), 'giftOfTheWild'), - makeBooleanRaidBuffInput(ActionId.fromItemId(49634), 'drumsOfTheWild'), -], 'Stats'); - -export const AllStatsPercentBuff = InputHelpers.makeMultiIconInput([ - makeBooleanIndividualBuffInput(ActionId.fromSpellId(25898), 'blessingOfKings'), - makeBooleanRaidBuffInput(ActionId.fromItemId(49633), 'drumsOfForgottenKings'), - makeBooleanIndividualBuffInput(ActionId.fromSpellId(25899), 'blessingOfSanctuary'), -], 'Stats %'); - -export const ArmorBuff = InputHelpers.makeMultiIconInput([ - makeTristateRaidBuffInput(ActionId.fromSpellId(48942), ActionId.fromSpellId(20140), 'devotionAura'), - makeTristateRaidBuffInput(ActionId.fromSpellId(58753), ActionId.fromSpellId(16293), 'stoneskinTotem'), - makeBooleanRaidBuffInput(ActionId.fromItemId(43468), 'scrollOfProtection'), -], 'Armor'); - -export const StaminaBuff = InputHelpers.makeMultiIconInput([ - makeTristateRaidBuffInput(ActionId.fromSpellId(48161), ActionId.fromSpellId(14767), 'powerWordFortitude'), - makeBooleanRaidBuffInput(ActionId.fromItemId(37094), 'scrollOfStamina'), -], 'Stamina'); - -export const StrengthAndAgilityBuff = InputHelpers.makeMultiIconInput([ - makeTristateRaidBuffInput(ActionId.fromSpellId(58643), ActionId.fromSpellId(52456), 'strengthOfEarthTotem'), - makeBooleanRaidBuffInput(ActionId.fromSpellId(57623), 'hornOfWinter'), - makeBooleanRaidBuffInput(ActionId.fromItemId(43464), 'scrollOfAgility'), - makeBooleanRaidBuffInput(ActionId.fromItemId(43466), 'scrollOfStrength'), -], 'Str/Agi'); - -export const IntellectBuff = InputHelpers.makeMultiIconInput([ - makeBooleanRaidBuffInput(ActionId.fromSpellId(43002), 'arcaneBrilliance'), - makeTristateRaidBuffInput(ActionId.fromSpellId(57567), ActionId.fromSpellId(54038), 'felIntelligence'), - makeBooleanRaidBuffInput(ActionId.fromItemId(37092), 'scrollOfIntellect'), -], 'Int'); - -export const SpiritBuff = InputHelpers.makeMultiIconInput([ - makeBooleanRaidBuffInput(ActionId.fromSpellId(48073), 'divineSpirit'), - makeTristateRaidBuffInput(ActionId.fromSpellId(57567), ActionId.fromSpellId(54038), 'felIntelligence'), - makeBooleanRaidBuffInput(ActionId.fromItemId(37098), 'scrollOfSpirit'), -], 'Spirit'); - -export const AttackPowerBuff = InputHelpers.makeMultiIconInput([ - makeTristateIndividualBuffInput(ActionId.fromSpellId(48934), ActionId.fromSpellId(20045), 'blessingOfMight'), - makeTristateRaidBuffInput(ActionId.fromSpellId(47436), ActionId.fromSpellId(12861), 'battleShout'), -], 'AP'); - -export const AttackPowerPercentBuff = InputHelpers.makeMultiIconInput([ - makeBooleanRaidBuffInput(ActionId.fromSpellId(53138), 'abominationsMight'), - makeBooleanRaidBuffInput(ActionId.fromSpellId(30809), 'unleashedRage'), - makeBooleanRaidBuffInput(ActionId.fromSpellId(19506), 'trueshotAura'), -], 'Atk Pwr %'); - -export const DamagePercentBuff = InputHelpers.makeMultiIconInput([ - makeBooleanRaidBuffInput(ActionId.fromSpellId(31869), 'sanctifiedRetribution'), - makeBooleanRaidBuffInput(ActionId.fromSpellId(31583), 'arcaneEmpowerment'), - makeBooleanRaidBuffInput(ActionId.fromSpellId(34460), 'ferociousInspiration'), -], 'Dmg %'); - -export const DamageReductionPercentBuff = InputHelpers.makeMultiIconInput([ - makeBooleanIndividualBuffInput(ActionId.fromSpellId(57472), 'renewedHope'), - makeBooleanIndividualBuffInput(ActionId.fromSpellId(25899), 'blessingOfSanctuary'), - makeBooleanIndividualBuffInput(ActionId.fromSpellId(50720), 'vigilance'), -], 'Mit %'); - -export const ResistanceBuff = InputHelpers.makeMultiIconInput([ - makeBooleanRaidBuffInput(ActionId.fromSpellId(48170), 'shadowProtection'), - makeBooleanRaidBuffInput(ActionId.fromSpellId(58749), 'natureResistanceTotem'), - makeBooleanRaidBuffInput(ActionId.fromSpellId(49071), 'aspectOfTheWild'), - makeBooleanRaidBuffInput(ActionId.fromSpellId(48945), 'frostResistanceAura'), - makeBooleanRaidBuffInput(ActionId.fromSpellId(58745), 'frostResistanceTotem'), -], 'Resistances'); - -export const HastePercentBuff = InputHelpers.makeMultiIconInput([ - makeBooleanRaidBuffInput(ActionId.fromSpellId(53648), 'swiftRetribution'), - makeBooleanRaidBuffInput(ActionId.fromSpellId(48396), 'moonkinAura', TristateEffect.TristateEffectImproved), -], 'Haste %'); - -export const HealthBuff = InputHelpers.makeMultiIconInput([ - makeTristateRaidBuffInput(ActionId.fromSpellId(47440), ActionId.fromSpellId(12861), 'commandingShout'), - makeTristateRaidBuffInput(ActionId.fromSpellId(47982), ActionId.fromSpellId(18696), 'bloodPact'), -], 'Health'); - -export const MP5Buff = InputHelpers.makeMultiIconInput([ - makeTristateIndividualBuffInput(ActionId.fromSpellId(48938), ActionId.fromSpellId(20245), 'blessingOfWisdom'), - makeTristateRaidBuffInput(ActionId.fromSpellId(58774), ActionId.fromSpellId(16206), 'manaSpringTotem'), -], 'MP5'); - -export const MeleeCritBuff = InputHelpers.makeMultiIconInput([ - makeTristateRaidBuffInput(ActionId.fromSpellId(17007), ActionId.fromSpellId(34300), 'leaderOfThePack'), - makeBooleanRaidBuffInput(ActionId.fromSpellId(29801), 'rampage'), -], 'Melee Crit'); - -export const MeleeHasteBuff = InputHelpers.makeMultiIconInput([ - makeBooleanRaidBuffInput(ActionId.fromSpellId(55610), 'icyTalons'), - makeTristateRaidBuffInput(ActionId.fromSpellId(65990), ActionId.fromSpellId(29193), 'windfuryTotem'), -], 'Melee Haste'); - -export const ReplenishmentBuff = InputHelpers.makeMultiIconInput([ - makeBooleanIndividualBuffInput(ActionId.fromSpellId(48160), 'vampiricTouch'), - makeBooleanIndividualBuffInput(ActionId.fromSpellId(31878), 'judgementsOfTheWise'), - makeBooleanIndividualBuffInput(ActionId.fromSpellId(53292), 'huntingParty'), - makeBooleanIndividualBuffInput(ActionId.fromSpellId(54118), 'improvedSoulLeech'), - makeBooleanIndividualBuffInput(ActionId.fromSpellId(44561), 'enduringWinter'), -], 'Replen', 2); - -export const SpellCritBuff = InputHelpers.makeMultiIconInput([ - makeTristateRaidBuffInput(ActionId.fromSpellId(24907), ActionId.fromSpellId(48396), 'moonkinAura'), - makeBooleanRaidBuffInput(ActionId.fromSpellId(51470), 'elementalOath'), -], 'Spell Crit'); - -export const SpellHasteBuff = withLabel(makeBooleanRaidBuffInput(ActionId.fromSpellId(3738), 'wrathOfAirTotem'), 'Spell Haste'); - -export const SpellPowerBuff = InputHelpers.makeMultiIconInput([ - makeMultistateRaidBuffInput(ActionId.fromSpellId(47240), 2000, 'demonicPactSp', 20), - makeBooleanRaidBuffInput(ActionId.fromSpellId(57722), 'totemOfWrath'), - makeBooleanRaidBuffInput(ActionId.fromSpellId(58656), 'flametongueTotem'), -], 'Spell Power'); - -export const Bloodlust = withLabel(makeBooleanRaidBuffInput(ActionId.fromSpellId(2825), 'bloodlust'), 'Lust'); - -export const DefensiveCooldownBuff = InputHelpers.makeMultiIconInput([ - makeMultistateIndividualBuffInput(ActionId.fromSpellId(6940), 11, 'handOfSacrifices'), - makeMultistateIndividualBuffInput(ActionId.fromSpellId(53530), 11, 'divineGuardians'), - makeMultistateIndividualBuffInput(ActionId.fromSpellId(33206), 11, 'painSuppressions'), - makeMultistateIndividualBuffInput(ActionId.fromSpellId(47788), 11, 'guardianSpirits'), -], 'Defensive CDs'); - -// Misc Buffs -export const StrengthOfWrynn = makeBooleanRaidBuffInput(ActionId.fromSpellId(73827), 'strengthOfWrynn'); -export const RetributionAura = makeBooleanRaidBuffInput(ActionId.fromSpellId(54043), 'retributionAura'); -export const BraidedEterniumChain = makeBooleanPartyBuffInput(ActionId.fromSpellId(31025), 'braidedEterniumChain'); -export const ChainOfTheTwilightOwl = makeBooleanPartyBuffInput(ActionId.fromSpellId(31035), 'chainOfTheTwilightOwl'); -export const HeroicPresence = makeBooleanPartyBuffInput(ActionId.fromSpellId(6562), 'heroicPresence'); -export const EyeOfTheNight = makeBooleanPartyBuffInput(ActionId.fromSpellId(31033), 'eyeOfTheNight'); -export const Thorns = makeTristateRaidBuffInput(ActionId.fromSpellId(53307), ActionId.fromSpellId(16840), 'thorns'); -export const ManaTideTotem = makeMultistatePartyBuffInput(ActionId.fromSpellId(16190), 5, 'manaTideTotems'); -export const Innervate = makeMultistateIndividualBuffInput(ActionId.fromSpellId(29166), 11, 'innervates'); -export const PowerInfusion = makeMultistateIndividualBuffInput(ActionId.fromSpellId(10060), 11, 'powerInfusions'); -export const FocusMagic = makeBooleanIndividualBuffInput(ActionId.fromSpellId(54648), 'focusMagic'); -export const TricksOfTheTrade = makeMultistateIndividualBuffInput(ActionId.fromSpellId(57933), 20, 'tricksOfTheTrades'); -export const UnholyFrenzy = makeMultistateIndividualBuffInput(ActionId.fromSpellId(49016), 11, 'unholyFrenzy'); -export const RevitalizeRejuvination = makeMultistateMultiplierIndividualBuffInput(ActionId.fromSpellId(26982), 101, 10, 'revitalizeRejuvination'); -export const RevitalizeWildGrowth = makeMultistateMultiplierIndividualBuffInput(ActionId.fromSpellId(53251), 101, 10, 'revitalizeWildGrowth'); - -// Debuffs - -export const MajorArmorDebuff = InputHelpers.makeMultiIconInput([ - makeBooleanDebuffInput(ActionId.fromSpellId(47467), 'sunderArmor'), - makeBooleanDebuffInput(ActionId.fromSpellId(8647), 'exposeArmor'), - makeBooleanDebuffInput(ActionId.fromSpellId(55754), 'acidSpit'), -], 'Major ArP'); - -export const MinorArmorDebuff = InputHelpers.makeMultiIconInput([ - makeTristateDebuffInput(ActionId.fromSpellId(770), ActionId.fromSpellId(33602), 'faerieFire'), - makeTristateDebuffInput(ActionId.fromSpellId(50511), ActionId.fromSpellId(18180), 'curseOfWeakness'), - makeBooleanDebuffInput(ActionId.fromSpellId(56631), 'sting'), - makeBooleanDebuffInput(ActionId.fromSpellId(53598), 'sporeCloud'), -], 'Minor ArP'); - -export const AttackPowerDebuff = InputHelpers.makeMultiIconInput([ - makeBooleanDebuffInput(ActionId.fromSpellId(26016), 'vindication'), - makeTristateDebuffInput(ActionId.fromSpellId(47437), ActionId.fromSpellId(12879), 'demoralizingShout'), - makeTristateDebuffInput(ActionId.fromSpellId(48560), ActionId.fromSpellId(16862), 'demoralizingRoar'), - makeTristateDebuffInput(ActionId.fromSpellId(50511), ActionId.fromSpellId(18180), 'curseOfWeakness'), - makeBooleanDebuffInput(ActionId.fromSpellId(55487), 'demoralizingScreech'), -], 'Atk Pwr'); - -export const BleedDebuff = InputHelpers.makeMultiIconInput([ - makeBooleanDebuffInput(ActionId.fromSpellId(48564), 'mangle'), - makeBooleanDebuffInput(ActionId.fromSpellId(46855), 'trauma'), - makeBooleanDebuffInput(ActionId.fromSpellId(57393), 'stampede'), -], 'Bleed'); - -export const CritDebuff = InputHelpers.makeMultiIconInput([ - makeBooleanDebuffInput(ActionId.fromSpellId(30706), 'totemOfWrath'), - makeBooleanDebuffInput(ActionId.fromSpellId(20337), 'heartOfTheCrusader'), - makeBooleanDebuffInput(ActionId.fromSpellId(58410), 'masterPoisoner'), -], 'Crit'); - -export const MeleeAttackSpeedDebuff = InputHelpers.makeMultiIconInput([ - makeTristateDebuffInput(ActionId.fromSpellId(47502), ActionId.fromSpellId(12666), 'thunderClap'), - makeTristateDebuffInput(ActionId.fromSpellId(55095), ActionId.fromSpellId(51456), 'frostFever'), - makeBooleanDebuffInput(ActionId.fromSpellId(53696), 'judgementsOfTheJust'), - makeBooleanDebuffInput(ActionId.fromSpellId(48485), 'infectedWounds'), -], 'Atk Speed'); - -export const MeleeHitDebuff = InputHelpers.makeMultiIconInput([ - makeBooleanDebuffInput(ActionId.fromSpellId(65855), 'insectSwarm'), - makeBooleanDebuffInput(ActionId.fromSpellId(3043), 'scorpidSting'), -], 'Miss'); - -export const PhysicalDamageDebuff = InputHelpers.makeMultiIconInput([ - makeBooleanDebuffInput(ActionId.fromSpellId(29859), 'bloodFrenzy'), - makeBooleanDebuffInput(ActionId.fromSpellId(58413), 'savageCombat'), -], 'Phys Vuln'); - -export const SpellCritDebuff = InputHelpers.makeMultiIconInput([ - makeBooleanDebuffInput(ActionId.fromSpellId(17803), 'shadowMastery'), - makeBooleanDebuffInput(ActionId.fromSpellId(12873), 'improvedScorch'), - makeBooleanDebuffInput(ActionId.fromSpellId(28593), 'wintersChill'), -], 'Spell Crit'); - -export const SpellHitDebuff = InputHelpers.makeMultiIconInput([ - makeBooleanDebuffInput(ActionId.fromSpellId(33198), 'misery'), - makeBooleanDebuffInput(ActionId.fromSpellId(33602), 'faerieFire', TristateEffect.TristateEffectImproved), -], 'Spell Hit'); - -export const SpellDamageDebuff = InputHelpers.makeMultiIconInput([ - makeBooleanDebuffInput(ActionId.fromSpellId(51161), 'ebonPlaguebringer'), - makeBooleanDebuffInput(ActionId.fromSpellId(48511), 'earthAndMoon'), - makeBooleanDebuffInput(ActionId.fromSpellId(47865), 'curseOfElements'), -], 'Spell Dmg'); - -export const HuntersMark = withLabel(makeQuadstateDebuffInput(ActionId.fromSpellId(53338), ActionId.fromSpellId(19423), ActionId.fromItemId(42907), 'huntersMark'), 'Mark'); -export const JudgementOfWisdom = withLabel(makeBooleanDebuffInput(ActionId.fromSpellId(53408), 'judgementOfWisdom'), 'JoW'); -export const JudgementOfLight = makeBooleanDebuffInput(ActionId.fromSpellId(20271), 'judgementOfLight'); -export const ShatteringThrow = makeMultistateIndividualBuffInput(ActionId.fromSpellId(64382), 20, 'shatteringThrows'); -export const GiftOfArthas = makeBooleanDebuffInput(ActionId.fromSpellId(11374), 'giftOfArthas'); -export const CrystalYield = makeBooleanDebuffInput(ActionId.fromSpellId(15235), 'crystalYield'); - -// Consumes -export const ThermalSapper = makeBooleanConsumeInput(ActionId.fromItemId(42641), 'thermalSapper'); -export const ExplosiveDecoy = makeBooleanConsumeInput(ActionId.fromItemId(40536), 'explosiveDecoy'); - -export const SpicedMammothTreats = makeBooleanConsumeInput(ActionId.fromItemId(43005), 'petFood', PetFood.PetFoodSpicedMammothTreats); -export const PetScrollOfAgilityV = makeBooleanConsumeInput(ActionId.fromItemId(27498), 'petScrollOfAgility', 5); -export const PetScrollOfStrengthV = makeBooleanConsumeInput(ActionId.fromItemId(27503), 'petScrollOfStrength', 5); - -function withLabel(config: InputHelpers.TypedIconPickerConfig, label: string): InputHelpers.TypedIconPickerConfig { +export function withLabel(config: IconInputConfig, label: string): IconInputConfig { config.label = label; return config; } -function makeBooleanRaidBuffInput(id: ActionId, fieldName: keyof RaidBuffs, value?: number): InputHelpers.TypedIconPickerConfig, boolean> { - return InputHelpers.makeBooleanIconInput({ - getModObject: (player: Player) => player.getRaid()!, - getValue: (raid: Raid) => raid.getBuffs(), - setValue: (eventID: EventID, raid: Raid, newVal: RaidBuffs) => raid.setBuffs(eventID, newVal), - changeEmitter: (raid: Raid) => raid.buffsChangeEmitter, - }, id, fieldName, value); +interface BooleanInputConfig { + actionId: ActionId + fieldName: keyof T + value?: number + faction?: Faction } -function makeBooleanPartyBuffInput(id: ActionId, fieldName: keyof PartyBuffs, value?: number): InputHelpers.TypedIconPickerConfig, boolean> { + +export function makeBooleanRaidBuffInput(config: BooleanInputConfig): InputHelpers.TypedIconPickerConfig, boolean> { + return InputHelpers.makeBooleanIconInput>({ + getModObject: (player: Player) => player, + showWhen: (player: Player) => + (!config.faction || config.faction == player.getFaction()), + getValue: (player: Player) => player.getRaid()!.getBuffs(), + setValue: (eventID: EventID, player: Player, newVal: RaidBuffs) => player.getRaid()!.setBuffs(eventID, newVal), + changeEmitter: (player: Player) => TypedEvent.onAny([player.getRaid()!.buffsChangeEmitter, player.raceChangeEmitter]), + }, config.actionId, config.fieldName, config.value); +} +export function makeBooleanPartyBuffInput(config: BooleanInputConfig): InputHelpers.TypedIconPickerConfig, boolean> { return InputHelpers.makeBooleanIconInput({ - getModObject: (player: Player) => player.getParty()!, + getModObject: (player: Player) => player.getParty()!, getValue: (party: Party) => party.getBuffs(), setValue: (eventID: EventID, party: Party, newVal: PartyBuffs) => party.setBuffs(eventID, newVal), changeEmitter: (party: Party) => party.buffsChangeEmitter, - }, id, fieldName, value); -} -function makeBooleanIndividualBuffInput(id: ActionId, fieldName: keyof IndividualBuffs, value?: number): InputHelpers.TypedIconPickerConfig, boolean> { - return InputHelpers.makeBooleanIconInput>({ - getModObject: (player: Player) => player, - getValue: (player: Player) => player.getBuffs(), - setValue: (eventID: EventID, player: Player, newVal: IndividualBuffs) => player.setBuffs(eventID, newVal), - changeEmitter: (player: Player) => player.buffsChangeEmitter, - }, id, fieldName, value); -} -function makeBooleanConsumeInput(id: ActionId, fieldName: keyof Consumes, value?: number): InputHelpers.TypedIconPickerConfig, boolean> { - return InputHelpers.makeBooleanIconInput>({ - getModObject: (player: Player) => player, - getValue: (player: Player) => player.getConsumes(), - setValue: (eventID: EventID, player: Player, newVal: Consumes) => player.setConsumes(eventID, newVal), - changeEmitter: (player: Player) => player.consumesChangeEmitter, - }, id, fieldName, value); -} -function makeBooleanDebuffInput(id: ActionId, fieldName: keyof Debuffs, value?: number): InputHelpers.TypedIconPickerConfig, boolean> { - return InputHelpers.makeBooleanIconInput({ - getModObject: (player: Player) => player.getRaid()!, + }, config.actionId, config.fieldName, config.value); +} + +export function makeBooleanIndividualBuffInput(config: BooleanInputConfig): InputHelpers.TypedIconPickerConfig, boolean> { + return InputHelpers.makeBooleanIconInput>({ + getModObject: (player: Player) => player, + showWhen: (player: Player) => + (!config.faction || config.faction == player.getFaction()), + getValue: (player: Player) => player.getBuffs(), + setValue: (eventID: EventID, player: Player, newVal: IndividualBuffs) => player.setBuffs(eventID, newVal), + changeEmitter: (player: Player) => TypedEvent.onAny([player.buffsChangeEmitter, player.raceChangeEmitter]), + }, config.actionId, config.fieldName, config.value); +} + +export function makeBooleanConsumeInput(config: BooleanInputConfig): InputHelpers.TypedIconPickerConfig, boolean> { + return InputHelpers.makeBooleanIconInput>({ + getModObject: (player: Player) => player, + getValue: (player: Player) => player.getConsumes(), + setValue: (eventID: EventID, player: Player, newVal: Consumes) => player.setConsumes(eventID, newVal), + changeEmitter: (player: Player) => TypedEvent.onAny([player.consumesChangeEmitter]) + }, config.actionId, config.fieldName, config.value); +} +export function makeBooleanDebuffInput(config: BooleanInputConfig): InputHelpers.TypedIconPickerConfig, boolean> { + return InputHelpers.makeBooleanIconInput>({ + getModObject: (player: Player) => player, + getValue: (player: Player) => player.getRaid()!.getDebuffs(), + setValue: (eventID: EventID, player: Player, newVal: Debuffs) => player.getRaid()!.setDebuffs(eventID, newVal), + changeEmitter: (player: Player) => TypedEvent.onAny([player.getRaid()!.debuffsChangeEmitter]), + }, config.actionId, config.fieldName, config.value); +} + +interface TristateInputConfig { + actionId: ActionId + impId: ActionId + fieldName: keyof T + faction?: Faction +} + +export function makeTristateRaidBuffInput(config: TristateInputConfig): InputHelpers.TypedIconPickerConfig, number> { + return InputHelpers.makeTristateIconInput>({ + getModObject: (player: Player) => player, + showWhen: (player: Player) => + (!config.faction || config.faction == player.getFaction()), + getValue: (player: Player) => player.getRaid()!.getBuffs(), + setValue: (eventID: EventID, player: Player, newVal: RaidBuffs) => player.getRaid()!.setBuffs(eventID, newVal), + changeEmitter: (player: Player) => TypedEvent.onAny([player.getRaid()!.buffsChangeEmitter, player.raceChangeEmitter]), + }, config.actionId, config.impId, config.fieldName); +} + +export function makeTristateIndividualBuffInput(config: TristateInputConfig): InputHelpers.TypedIconPickerConfig, number> { + return InputHelpers.makeTristateIconInput>({ + getModObject: (player: Player) => player, + showWhen: (player: Player) => + (!config.faction || config.faction == player.getFaction()), + getValue: (player: Player) => player.getBuffs(), + setValue: (eventID: EventID, player: Player, newVal: IndividualBuffs) => player.setBuffs(eventID, newVal), + changeEmitter: (player: Player) => TypedEvent.onAny([player.buffsChangeEmitter, player.raceChangeEmitter]) + }, config.actionId, config.impId, config.fieldName); +} + +export function makeTristateDebuffInput(config: TristateInputConfig): InputHelpers.TypedIconPickerConfig, number> { + return InputHelpers.makeTristateIconInput({ + getModObject: (player: Player) => player.getRaid()!, getValue: (raid: Raid) => raid.getDebuffs(), setValue: (eventID: EventID, raid: Raid, newVal: Debuffs) => raid.setDebuffs(eventID, newVal), changeEmitter: (raid: Raid) => raid.debuffsChangeEmitter, - }, id, fieldName, value); + }, config.actionId, config.impId, config.fieldName); } -function makeTristateRaidBuffInput(id: ActionId, impId: ActionId, fieldName: keyof RaidBuffs): InputHelpers.TypedIconPickerConfig, number> { - return InputHelpers.makeTristateIconInput({ - getModObject: (player: Player) => player.getRaid()!, - getValue: (raid: Raid) => raid.getBuffs(), - setValue: (eventID: EventID, raid: Raid, newVal: RaidBuffs) => raid.setBuffs(eventID, newVal), - changeEmitter: (raid: Raid) => raid.buffsChangeEmitter, - }, id, impId, fieldName); +interface QuadStateInputConfig { + actionId: ActionId + impId: ActionId + impId2: ActionId + fieldName: keyof T + faction?: Faction } -function makeTristateIndividualBuffInput(id: ActionId, impId: ActionId, fieldName: keyof IndividualBuffs): InputHelpers.TypedIconPickerConfig, number> { - return InputHelpers.makeTristateIconInput>({ - getModObject: (player: Player) => player, - getValue: (player: Player) => player.getBuffs(), - setValue: (eventID: EventID, player: Player, newVal: IndividualBuffs) => player.setBuffs(eventID, newVal), - changeEmitter: (player: Player) => player.buffsChangeEmitter, - }, id, impId, fieldName); -} -function makeTristateDebuffInput(id: ActionId, impId: ActionId, fieldName: keyof Debuffs): InputHelpers.TypedIconPickerConfig, number> { - return InputHelpers.makeTristateIconInput({ - getModObject: (player: Player) => player.getRaid()!, - getValue: (raid: Raid) => raid.getDebuffs(), - setValue: (eventID: EventID, raid: Raid, newVal: Debuffs) => raid.setDebuffs(eventID, newVal), - changeEmitter: (raid: Raid) => raid.debuffsChangeEmitter, - }, id, impId, fieldName); -} -function makeQuadstateDebuffInput(id: ActionId, impId: ActionId, impId2: ActionId, fieldName: keyof Debuffs): InputHelpers.TypedIconPickerConfig, number> { + +export function makeQuadstateDebuffInput(config: QuadStateInputConfig): InputHelpers.TypedIconPickerConfig, number> { return InputHelpers.makeQuadstateIconInput({ - getModObject: (player: Player) => player.getRaid()!, + getModObject: (player: Player) => player.getRaid()!, getValue: (raid: Raid) => raid.getDebuffs(), setValue: (eventID: EventID, raid: Raid, newVal: Debuffs) => raid.setDebuffs(eventID, newVal), changeEmitter: (raid: Raid) => raid.debuffsChangeEmitter, - }, id, impId, impId2, fieldName); + }, config.actionId, config.impId, config.impId2, config.fieldName); } -function makeMultistateRaidBuffInput(id: ActionId, numStates: number, fieldName: keyof RaidBuffs, multiplier?: number): InputHelpers.TypedIconPickerConfig, number> { - return InputHelpers.makeMultistateIconInput({ - getModObject: (player: Player) => player.getRaid()!, - getValue: (raid: Raid) => raid.getBuffs(), - setValue: (eventID: EventID, raid: Raid, newVal: RaidBuffs) => raid.setBuffs(eventID, newVal), - changeEmitter: (raid: Raid) => raid.buffsChangeEmitter, - }, id, numStates, fieldName, multiplier); + +interface MultiStateInputConfig { + actionId: ActionId + numStates: number + fieldName: keyof T + multiplier?: number + faction?: Faction +} + +export function makeMultistateRaidBuffInput(config: MultiStateInputConfig): InputHelpers.TypedIconPickerConfig, number> { + return InputHelpers.makeMultistateIconInput>({ + getModObject: (player: Player) => player, + showWhen: (player: Player) => + (!config.faction || config.faction == player.getFaction()), + getValue: (player: Player) => player.getRaid()!.getBuffs(), + setValue: (eventID: EventID, player: Player, newVal: RaidBuffs) => player.getRaid()!.setBuffs(eventID, newVal), + changeEmitter: (player: Player) => TypedEvent.onAny([player.getRaid()!.buffsChangeEmitter, player.raceChangeEmitter]), + }, config.actionId, config.numStates, config.fieldName, config.multiplier); } -function makeMultistatePartyBuffInput(id: ActionId, numStates: number, fieldName: keyof PartyBuffs): InputHelpers.TypedIconPickerConfig, number> { +export function makeMultistatePartyBuffInput(actionId: ActionId, numStates: number, fieldName: keyof PartyBuffs): InputHelpers.TypedIconPickerConfig, number> { return InputHelpers.makeMultistateIconInput({ - getModObject: (player: Player) => player.getParty()!, + getModObject: (player: Player) => player.getParty()!, getValue: (party: Party) => party.getBuffs(), setValue: (eventID: EventID, party: Party, newVal: PartyBuffs) => party.setBuffs(eventID, newVal), changeEmitter: (party: Party) => party.buffsChangeEmitter, - }, id, numStates, fieldName); -} -function makeMultistateIndividualBuffInput(id: ActionId, numStates: number, fieldName: keyof IndividualBuffs): InputHelpers.TypedIconPickerConfig, number> { - return InputHelpers.makeMultistateIconInput>({ - getModObject: (player: Player) => player, - getValue: (player: Player) => player.getBuffs(), - setValue: (eventID: EventID, player: Player, newVal: IndividualBuffs) => player.setBuffs(eventID, newVal), - changeEmitter: (player: Player) => player.buffsChangeEmitter, - }, id, numStates, fieldName); -} -function makeMultistateMultiplierIndividualBuffInput(id: ActionId, numStates: number, multiplier: number, fieldName: keyof IndividualBuffs): InputHelpers.TypedIconPickerConfig, number> { - return InputHelpers.makeMultistateIconInput>({ - getModObject: (player: Player) => player, - getValue: (player: Player) => player.getBuffs(), - setValue: (eventID: EventID, player: Player, newVal: IndividualBuffs) => player.setBuffs(eventID, newVal), - changeEmitter: (player: Player) => player.buffsChangeEmitter, - }, id, numStates, fieldName, multiplier); -} - - -////////////////////////////////////////////////////////////////////// -// Custom buffs that don't fit into any of the helper functions above. -////////////////////////////////////////////////////////////////////// - -function makePotionInputFactory(consumesFieldName: keyof Consumes): (options: Array, tooltip?: string) => InputHelpers.TypedIconEnumPickerConfig, Potions> { - return makeConsumeInputFactory({ - consumesFieldName: consumesFieldName, - allOptions: [ - { actionId: ActionId.fromItemId(33447), value: Potions.RunicHealingPotion }, - { actionId: ActionId.fromItemId(41166), value: Potions.RunicHealingInjector }, - { actionId: ActionId.fromItemId(33448), value: Potions.RunicManaPotion }, - { actionId: ActionId.fromItemId(42545), value: Potions.RunicManaInjector }, - { actionId: ActionId.fromItemId(40093), value: Potions.IndestructiblePotion }, - { actionId: ActionId.fromItemId(40211), value: Potions.PotionOfSpeed }, - { actionId: ActionId.fromItemId(40212), value: Potions.PotionOfWildMagic }, - - { actionId: ActionId.fromItemId(22839), value: Potions.DestructionPotion }, - { actionId: ActionId.fromItemId(22838), value: Potions.HastePotion }, - { actionId: ActionId.fromItemId(13442), value: Potions.MightyRagePotion }, - { actionId: ActionId.fromItemId(22832), value: Potions.SuperManaPotion }, - { actionId: ActionId.fromItemId(31677), value: Potions.FelManaPotion }, - { actionId: ActionId.fromItemId(22828), value: Potions.InsaneStrengthPotion }, - { actionId: ActionId.fromItemId(22849), value: Potions.IronshieldPotion }, - { actionId: ActionId.fromItemId(22837), value: Potions.HeroicPotion }, - ] as Array, Potions>>, - }); -} -export const makePotionsInput = makePotionInputFactory('defaultPotion'); -export const makePrepopPotionsInput = makePotionInputFactory('prepopPotion'); - -export const makeConjuredInput = makeConsumeInputFactory({ - consumesFieldName: 'defaultConjured', - allOptions: [ - { actionId: ActionId.fromItemId(12662), value: Conjured.ConjuredDarkRune }, - { actionId: ActionId.fromItemId(22788), value: Conjured.ConjuredFlameCap }, - { actionId: ActionId.fromItemId(22105), value: Conjured.ConjuredHealthstone }, - { actionId: ActionId.fromItemId(7676), value: Conjured.ConjuredRogueThistleTea }, - ] as Array, Conjured>> -}); - -export const makeFlasksInput = makeConsumeInputFactory({ - consumesFieldName: 'flask', - allOptions: [ - { actionId: ActionId.fromItemId(46376), value: Flask.FlaskOfTheFrostWyrm }, - { actionId: ActionId.fromItemId(46377), value: Flask.FlaskOfEndlessRage }, - { actionId: ActionId.fromItemId(46378), value: Flask.FlaskOfPureMojo }, - { actionId: ActionId.fromItemId(46379), value: Flask.FlaskOfStoneblood }, - { actionId: ActionId.fromItemId(40079), value: Flask.LesserFlaskOfToughness }, - { actionId: ActionId.fromItemId(44939), value: Flask.LesserFlaskOfResistance }, - { actionId: ActionId.fromItemId(22861), value: Flask.FlaskOfBlindingLight }, - { actionId: ActionId.fromItemId(22853), value: Flask.FlaskOfMightyRestoration }, - { actionId: ActionId.fromItemId(22866), value: Flask.FlaskOfPureDeath }, - { actionId: ActionId.fromItemId(22854), value: Flask.FlaskOfRelentlessAssault }, - { actionId: ActionId.fromItemId(13512), value: Flask.FlaskOfSupremePower }, - { actionId: ActionId.fromItemId(22851), value: Flask.FlaskOfFortification }, - { actionId: ActionId.fromItemId(33208), value: Flask.FlaskOfChromaticWonder }, - ] as Array, Flask>>, - onSet: (eventID: EventID, player: Player, newValue: Flask) => { - if (newValue) { - const newConsumes = player.getConsumes(); - newConsumes.battleElixir = BattleElixir.BattleElixirUnknown; - newConsumes.guardianElixir = GuardianElixir.GuardianElixirUnknown; - player.setConsumes(eventID, newConsumes); - } - } -}); - -export const makeBattleElixirsInput = makeConsumeInputFactory({ - consumesFieldName: 'battleElixir', - allOptions: [ - { actionId: ActionId.fromItemId(44325), value: BattleElixir.ElixirOfAccuracy }, - { actionId: ActionId.fromItemId(44330), value: BattleElixir.ElixirOfArmorPiercing }, - { actionId: ActionId.fromItemId(44327), value: BattleElixir.ElixirOfDeadlyStrikes }, - { actionId: ActionId.fromItemId(44329), value: BattleElixir.ElixirOfExpertise }, - { actionId: ActionId.fromItemId(44331), value: BattleElixir.ElixirOfLightningSpeed }, - { actionId: ActionId.fromItemId(39666), value: BattleElixir.ElixirOfMightyAgility }, - { actionId: ActionId.fromItemId(40073), value: BattleElixir.ElixirOfMightyStrength }, - { actionId: ActionId.fromItemId(40076), value: BattleElixir.GurusElixir }, - { actionId: ActionId.fromItemId(40070), value: BattleElixir.SpellpowerElixir }, - { actionId: ActionId.fromItemId(40068), value: BattleElixir.WrathElixir }, - { actionId: ActionId.fromItemId(28103), value: BattleElixir.AdeptsElixir }, - { actionId: ActionId.fromItemId(9224), value: BattleElixir.ElixirOfDemonslaying }, - { actionId: ActionId.fromItemId(22831), value: BattleElixir.ElixirOfMajorAgility }, - { actionId: ActionId.fromItemId(22833), value: BattleElixir.ElixirOfMajorFirePower }, - { actionId: ActionId.fromItemId(22827), value: BattleElixir.ElixirOfMajorFrostPower }, - { actionId: ActionId.fromItemId(22835), value: BattleElixir.ElixirOfMajorShadowPower }, - { actionId: ActionId.fromItemId(22824), value: BattleElixir.ElixirOfMajorStrength }, - { actionId: ActionId.fromItemId(28104), value: BattleElixir.ElixirOfMastery }, - { actionId: ActionId.fromItemId(13452), value: BattleElixir.ElixirOfTheMongoose }, - { actionId: ActionId.fromItemId(31679), value: BattleElixir.FelStrengthElixir }, - { actionId: ActionId.fromItemId(13454), value: BattleElixir.GreaterArcaneElixir }, - ] as Array, BattleElixir>>, - onSet: (eventID: EventID, player: Player, newValue: BattleElixir) => { - if (newValue) { - const newConsumes = player.getConsumes(); - newConsumes.flask = Flask.FlaskUnknown; - player.setConsumes(eventID, newConsumes); - } - } -}); - -export const makeGuardianElixirsInput = makeConsumeInputFactory({ - consumesFieldName: 'guardianElixir', - allOptions: [ - { actionId: ActionId.fromItemId(44328), value: GuardianElixir.ElixirOfMightyDefense }, - { actionId: ActionId.fromItemId(40078), value: GuardianElixir.ElixirOfMightyFortitude }, - { actionId: ActionId.fromItemId(40109), value: GuardianElixir.ElixirOfMightyMageblood }, - { actionId: ActionId.fromItemId(44332), value: GuardianElixir.ElixirOfMightyThoughts }, - { actionId: ActionId.fromItemId(40097), value: GuardianElixir.ElixirOfProtection }, - { actionId: ActionId.fromItemId(40072), value: GuardianElixir.ElixirOfSpirit }, - { actionId: ActionId.fromItemId(9088), value: GuardianElixir.GiftOfArthas }, - { actionId: ActionId.fromItemId(32067), value: GuardianElixir.ElixirOfDraenicWisdom }, - { actionId: ActionId.fromItemId(32068), value: GuardianElixir.ElixirOfIronskin }, - { actionId: ActionId.fromItemId(22834), value: GuardianElixir.ElixirOfMajorDefense }, - { actionId: ActionId.fromItemId(32062), value: GuardianElixir.ElixirOfMajorFortitude }, - { actionId: ActionId.fromItemId(22840), value: GuardianElixir.ElixirOfMajorMageblood }, - ] as Array, GuardianElixir>>, - onSet: (eventID: EventID, player: Player, newValue: GuardianElixir) => { - if (newValue) { - const newConsumes = player.getConsumes(); - newConsumes.flask = Flask.FlaskUnknown; - player.setConsumes(eventID, newConsumes); - } - } -}); - -export const makeFoodInput = makeConsumeInputFactory({ - consumesFieldName: 'food', - allOptions: [ - { actionId: ActionId.fromItemId(43015), value: Food.FoodFishFeast }, - { actionId: ActionId.fromItemId(34753), value: Food.FoodGreatFeast }, - { actionId: ActionId.fromItemId(42999), value: Food.FoodBlackenedDragonfin }, - { actionId: ActionId.fromItemId(42995), value: Food.FoodHeartyRhino }, - { actionId: ActionId.fromItemId(34754), value: Food.FoodMegaMammothMeal }, - { actionId: ActionId.fromItemId(34756), value: Food.FoodSpicedWormBurger }, - { actionId: ActionId.fromItemId(42994), value: Food.FoodRhinoliciousWormsteak }, - { actionId: ActionId.fromItemId(34769), value: Food.FoodImperialMantaSteak }, - { actionId: ActionId.fromItemId(42996), value: Food.FoodSnapperExtreme }, - { actionId: ActionId.fromItemId(34758), value: Food.FoodMightyRhinoDogs }, - { actionId: ActionId.fromItemId(34767), value: Food.FoodFirecrackerSalmon }, - { actionId: ActionId.fromItemId(42998), value: Food.FoodCuttlesteak }, - { actionId: ActionId.fromItemId(43000), value: Food.FoodDragonfinFilet }, - - { actionId: ActionId.fromItemId(27657), value: Food.FoodBlackenedBasilisk }, - { actionId: ActionId.fromItemId(27664), value: Food.FoodGrilledMudfish }, - { actionId: ActionId.fromItemId(27655), value: Food.FoodRavagerDog }, - { actionId: ActionId.fromItemId(27658), value: Food.FoodRoastedClefthoof }, - { actionId: ActionId.fromItemId(33872), value: Food.FoodSpicyHotTalbuk }, - { actionId: ActionId.fromItemId(33825), value: Food.FoodSkullfishSoup }, - { actionId: ActionId.fromItemId(33052), value: Food.FoodFishermansFeast }, - ] as Array, Food>> -}); - -export const FillerExplosiveInput = makeConsumeInput('fillerExplosive', [ - { actionId: ActionId.fromItemId(41119), value: Explosive.ExplosiveSaroniteBomb }, - { actionId: ActionId.fromItemId(40771), value: Explosive.ExplosiveCobaltFragBomb }, -] as Array, Explosive>>); - -export interface ConsumeInputFactoryArgs { - consumesFieldName: keyof Consumes, - allOptions: Array, T>>, - onSet?: (eventID: EventID, player: Player, newValue: T) => void -} -function makeConsumeInputFactory(args: ConsumeInputFactoryArgs): (options: Array, tooltip?: string) => InputHelpers.TypedIconEnumPickerConfig, T> { - return (options: Array, tooltip?: string) => { - return { - type: 'iconEnum', - tooltip: tooltip, - numColumns: options.length > 5 ? 2 : 1, - values: [ - { value: 0 } as unknown as IconEnumValueConfig, T>, - ].concat(options.map(option => args.allOptions.find(allOption => allOption.value == option)!)), - equals: (a: T, b: T) => a == b, - zeroValue: 0 as T, - changedEvent: (player: Player) => player.consumesChangeEmitter, - getValue: (player: Player) => player.getConsumes()[args.consumesFieldName] as T, - setValue: (eventID: EventID, player: Player, newValue: number) => { - const newConsumes = player.getConsumes(); - (newConsumes[args.consumesFieldName] as number) = newValue; - TypedEvent.freezeAllAndDo(() => { - player.setConsumes(eventID, newConsumes); - if (args.onSet) { - args.onSet(eventID, player, newValue as T); - } - }); - }, - }; - }; -} - -function makeConsumeInput(consumesFieldName: keyof Consumes, allOptions: Array, T>>, onSet?: (eventID: EventID, player: Player, newValue: T) => void): InputHelpers.TypedIconEnumPickerConfig, T> { - const factory = makeConsumeInputFactory({ - consumesFieldName: consumesFieldName, - allOptions: allOptions, - onSet: onSet - }); - return factory(allOptions.map(option => option.value)); -} + }, actionId, numStates, fieldName); +} +export function makeMultistateIndividualBuffInput(config: MultiStateInputConfig): InputHelpers.TypedIconPickerConfig, number> { + return InputHelpers.makeMultistateIconInput>({ + getModObject: (player: Player) => player, + showWhen: (player: Player) => + (!config.faction || config.faction == player.getFaction()), + getValue: (player: Player) => player.getBuffs(), + setValue: (eventID: EventID, player: Player, newVal: IndividualBuffs) => player.setBuffs(eventID, newVal), + changeEmitter: (player: Player) => TypedEvent.onAny([player.buffsChangeEmitter, player.raceChangeEmitter]), + }, config.actionId, config.numStates, config.fieldName, config.multiplier); +} + +export function makeMultistateMultiplierIndividualBuffInput(actionId: ActionId, numStates: number, multiplier: number, fieldName: keyof IndividualBuffs): InputHelpers.TypedIconPickerConfig, number> { + return InputHelpers.makeMultistateIconInput>({ + getModObject: (player: Player) => player, + getValue: (player: Player) => player.getBuffs(), + setValue: (eventID: EventID, player: Player, newVal: IndividualBuffs) => player.setBuffs(eventID, newVal), + changeEmitter: (player: Player) => player.buffsChangeEmitter, + }, actionId, numStates, fieldName, multiplier); +} + +export function makeMultistateMultiplierDebuffInput(actionId: ActionId, numStates: number, multiplier: number, fieldName: keyof Debuffs): InputHelpers.TypedIconPickerConfig, number> { + return InputHelpers.makeMultistateIconInput({ + getModObject: (player: Player) => player.getRaid()!, + getValue: (raid: Raid) => raid.getDebuffs(), + setValue: (eventID: EventID, raid: Raid, newVal: Debuffs) => raid.setDebuffs(eventID, newVal), + changeEmitter: (raid: Raid) => raid.debuffsChangeEmitter, + }, actionId, numStates, fieldName, multiplier); +} + +// interface EnumInputConfig { +// fieldName: keyof Message +// values: Array> +// direction?: IconEnumPickerDirection +// numColumns?: number +// faction?: Faction +// } + +// export function makeEnumIndividualBuffInput(config: EnumInputConfig, IndividualBuffs, number>): InputHelpers.TypedIconEnumPickerConfig, number> { +// return InputHelpers.makeEnumIconInput, number>({ +// getModObject: (player: Player) => player, +// showWhen: (player: Player) => +// (!config.faction || config.faction == player.getFaction()), +// getValue: (player: Player) => player.getBuffs(), +// setValue: (eventID: EventID, player: Player, newVal: IndividualBuffs) => player.setBuffs(eventID, newVal), +// changeEmitter: (player: Player) => TypedEvent.onAny([player.buffsChangeEmitter, player.raceChangeEmitter]), +// }, config.fieldName, config.values, config.numColumns, config.direction || IconEnumPickerDirection.Vertical) +// }; diff --git a/ui/core/components/icon_picker.tsx b/ui/core/components/icon_picker.tsx index 428092fb0d..e402016450 100644 --- a/ui/core/components/icon_picker.tsx +++ b/ui/core/components/icon_picker.tsx @@ -1,9 +1,11 @@ +// eslint-disable-next-line unused-imports/no-unused-imports +import { element, ref } from 'tsx-vanilla'; + import { ActionId } from '../proto_utils/action_id.js'; import { TypedEvent } from '../typed_event.js'; import { isRightClick } from '../utils.js'; import { Input, InputConfig } from './input.js'; -import { element, ref } from 'tsx-vanilla'; // Data for creating an icon-based input component. // @@ -11,7 +13,7 @@ import { element, ref } from 'tsx-vanilla'; // ModObject is the object being modified (Sim, Player, or Target). // ValueType is either number or boolean. export interface IconPickerConfig extends InputConfig { - id: ActionId; + actionId: ActionId; // The number of possible 'states' this icon can have. Most inputs will use 2 // for a bi-state icon (on or off). 0 indicates an unlimited number of states. @@ -78,7 +80,7 @@ export class IconPicker extends Input= 3 && this.config.improvedId) { this.config.improvedId.fillAndSet(this.improvedAnchor, true, true); @@ -117,7 +119,7 @@ export class IconPicker extends Input { + handleRightClick = (_event: UIEvent) => { if (this.currentValue > 0) { this.currentValue--; } else { // roll over @@ -151,13 +153,13 @@ export class IconPicker extends Input; @@ -37,66 +33,47 @@ export class ConsumesPicker extends Component { private buildPotionsPicker() { let fragment = document.createElement('fragment'); fragment.innerHTML = ` -
- -
-
+
+ +
+
-
-
+
+
`; - this.rootElem.appendChild(fragment.children[0] as HTMLElement); - - const prepopPotionOptions = this.simUI.splitRelevantOptions([ - // This list is smaller because some potions don't make sense to use as prepot. - // E.g. healing/mana potions. - { item: Potions.IndestructiblePotion, stats: [Stat.StatArmor] }, - { item: Potions.InsaneStrengthPotion, stats: [Stat.StatStrength] }, - { item: Potions.HeroicPotion, stats: [Stat.StatStamina] }, - { item: Potions.PotionOfSpeed, stats: [Stat.StatMeleeHaste, Stat.StatSpellHaste] }, - { item: Potions.PotionOfWildMagic, stats: [Stat.StatMeleeCrit, Stat.StatSpellCrit, Stat.StatSpellPower] }, - ]); - if (prepopPotionOptions.length) { - const elem = this.rootElem.querySelector('.consumes-prepot') as HTMLElement; - new IconEnumPicker( - elem, - this.simUI.player, - IconInputs.makePrepopPotionsInput(prepopPotionOptions, 'Prepop Potion (1s before combat)') - ); - } - - const potionOptions = this.simUI.splitRelevantOptions([ - { item: Potions.RunicHealingPotion, stats: [Stat.StatStamina] }, - { item: Potions.RunicHealingInjector, stats: [Stat.StatStamina] }, - { item: Potions.RunicManaPotion, stats: [Stat.StatIntellect] }, - { item: Potions.RunicManaInjector, stats: [Stat.StatIntellect] }, - { item: Potions.IndestructiblePotion, stats: [Stat.StatArmor] }, - { item: Potions.InsaneStrengthPotion, stats: [Stat.StatStrength] }, - { item: Potions.HeroicPotion, stats: [Stat.StatStamina] }, - { item: Potions.PotionOfSpeed, stats: [Stat.StatMeleeHaste, Stat.StatSpellHaste] }, - { item: Potions.PotionOfWildMagic, stats: [Stat.StatMeleeCrit, Stat.StatSpellCrit, Stat.StatSpellPower] }, - ]); - if (potionOptions.length) { - const elem = this.rootElem.querySelector('.consumes-potions') as HTMLElement; - new IconEnumPicker( - elem, - this.simUI.player, - IconInputs.makePotionsInput(potionOptions, 'Combat Potion') - ); - } - - const conjuredOptions = this.simUI.splitRelevantOptions([ - this.simUI.player.getClass() == Class.ClassRogue ? { item: Conjured.ConjuredRogueThistleTea, stats: [] } : null, - { item: Conjured.ConjuredHealthstone, stats: [Stat.StatStamina] }, - { item: Conjured.ConjuredDarkRune, stats: [Stat.StatIntellect] }, - { item: Conjured.ConjuredFlameCap, stats: [] }, - ]); - if (conjuredOptions.length) { - const elem = this.rootElem.querySelector('.consumes-conjured') as HTMLElement; - new IconEnumPicker(elem, this.simUI.player, IconInputs.makeConjuredInput(conjuredOptions)); - } + const rowElem = this.rootElem.appendChild(fragment.children[0] as HTMLElement); + const prePotsElem = this.rootElem.querySelector('.consumes-prepot') as HTMLElement; + const potionsElem = this.rootElem.querySelector('.consumes-potions') as HTMLElement; + const conjuredElem = this.rootElem.querySelector('.consumes-conjured') as HTMLElement; + + this.buildPickers({ + // GearChangeEmitter for ConjuredMinorRecombobulator + changeEmitters: [], + containerElem: rowElem, + options: [ + { + getConfig: () => ConsumablesInputs.makePrepopPotionsInput( + relevantStatOptions(ConsumablesInputs.PRE_POTIONS_CONFIG, this.simUI), + 'Combat Potion', + ), + parentElem: prePotsElem, + }, + { + getConfig: () => ConsumablesInputs.makePotionsInput( + relevantStatOptions(ConsumablesInputs.POTIONS_CONFIG, this.simUI) + ), + parentElem: potionsElem, + }, + { + getConfig: () => ConsumablesInputs.makeConjuredInput( + relevantStatOptions(ConsumablesInputs.CONJURED_CONFIG, this.simUI) + ), + parentElem: conjuredElem, + } + ], + }) } private buildElixirsPicker() { @@ -105,78 +82,43 @@ export class ConsumesPicker extends Component {
-
- or -
-
-
+
+ or +
+
+
`; - this.rootElem.appendChild(fragment.children[0] as HTMLElement); - - const flaskOptions = this.simUI.splitRelevantOptions([ - { item: Flask.FlaskOfTheFrostWyrm, stats: [Stat.StatSpellPower] }, - { item: Flask.FlaskOfEndlessRage, stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower] }, - { item: Flask.FlaskOfPureMojo, stats: [Stat.StatMP5] }, - { item: Flask.FlaskOfStoneblood, stats: [Stat.StatStamina] }, - { item: Flask.LesserFlaskOfToughness, stats: [Stat.StatResilience] }, - { item: Flask.LesserFlaskOfResistance, stats: [Stat.StatArcaneResistance, Stat.StatFireResistance, Stat.StatFrostResistance, Stat.StatNatureResistance, Stat.StatShadowResistance] }, - ]); - if (flaskOptions.length) { - const elem = this.rootElem.querySelector('.consumes-flasks') as HTMLElement; - new IconEnumPicker( - elem, - this.simUI.player, - IconInputs.makeFlasksInput(flaskOptions, 'Flask') - ); - } - - const battleElixirOptions = this.simUI.splitRelevantOptions([ - { item: BattleElixir.ElixirOfAccuracy, stats: [Stat.StatMeleeHit, Stat.StatSpellHit] }, - { item: BattleElixir.ElixirOfArmorPiercing, stats: [Stat.StatArmorPenetration] }, - { item: BattleElixir.ElixirOfDeadlyStrikes, stats: [Stat.StatMeleeCrit, Stat.StatSpellCrit] }, - { item: BattleElixir.ElixirOfExpertise, stats: [Stat.StatExpertise] }, - { item: BattleElixir.ElixirOfLightningSpeed, stats: [Stat.StatMeleeHaste, Stat.StatSpellHaste] }, - { item: BattleElixir.ElixirOfMightyAgility, stats: [Stat.StatAgility] }, - { item: BattleElixir.ElixirOfMightyStrength, stats: [Stat.StatStrength] }, - { item: BattleElixir.GurusElixir, stats: [Stat.StatStamina, Stat.StatAgility, Stat.StatStrength, Stat.StatSpirit, Stat.StatIntellect] }, - { item: BattleElixir.SpellpowerElixir, stats: [Stat.StatSpellPower] }, - { item: BattleElixir.WrathElixir, stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower] }, - ]); - - const battleElixirsContainer = this.rootElem.querySelector('.consumes-battle-elixirs') as HTMLElement; - if (battleElixirOptions.length) { - new IconEnumPicker( - battleElixirsContainer, - this.simUI.player, - IconInputs.makeBattleElixirsInput(battleElixirOptions, 'Battle Elixir') - ); - } else { - battleElixirsContainer.remove(); - } - - const guardianElixirOptions = this.simUI.splitRelevantOptions([ - { item: GuardianElixir.ElixirOfMightyDefense, stats: [Stat.StatDefense] }, - { item: GuardianElixir.ElixirOfMightyFortitude, stats: [Stat.StatStamina] }, - { item: GuardianElixir.ElixirOfMightyMageblood, stats: [Stat.StatMP5] }, - { item: GuardianElixir.ElixirOfMightyThoughts, stats: [Stat.StatIntellect] }, - { item: GuardianElixir.ElixirOfProtection, stats: [Stat.StatArmor] }, - { item: GuardianElixir.ElixirOfSpirit, stats: [Stat.StatSpirit] }, - { item: GuardianElixir.GiftOfArthas, stats: [Stat.StatStamina] }, - ]); - - const guardianElixirsContainer = this.rootElem.querySelector('.consumes-guardian-elixirs') as HTMLElement; - if (guardianElixirOptions.length) { - const guardianElixirsContainer = this.rootElem.querySelector('.consumes-guardian-elixirs') as HTMLElement; - new IconEnumPicker( - guardianElixirsContainer, - this.simUI.player, - IconInputs.makeGuardianElixirsInput(guardianElixirOptions, 'Guardian Elixir') - ); - } else { - guardianElixirsContainer.remove(); - } + const rowElem = this.rootElem.appendChild(fragment.children[0] as HTMLElement); + const flasksElem = this.rootElem.querySelector('.consumes-flasks') as HTMLElement; + const battleElixirsElem = this.rootElem.querySelector('.consumes-battle-elixirs') as HTMLElement; + const guardianElixirsElem = this.rootElem.querySelector('.consumes-guardian-elixirs') as HTMLElement; + + this.buildPickers({ + changeEmitters: [], + containerElem: rowElem, + options: [ + { + getConfig: () => ConsumablesInputs.makeFlasksInput( + relevantStatOptions(ConsumablesInputs.FLASKS_CONFIG, this.simUI) + ), + parentElem: flasksElem, + }, + { + getConfig: () => ConsumablesInputs.makeBattleElixirsInput( + relevantStatOptions(ConsumablesInputs.BATTLE_ELIXIRS_CONFIG, this.simUI) + ), + parentElem: battleElixirsElem, + }, + { + getConfig: () => ConsumablesInputs.makeGuardianElixirsInput( + relevantStatOptions(ConsumablesInputs.GUARDIAN_ELIXIRS_CONFIG, this.simUI) + ), + parentElem: guardianElixirsElem, + } + ], + }) } private buildFoodPicker() { @@ -190,27 +132,21 @@ export class ConsumesPicker extends Component { `; - this.rootElem.appendChild(fragment.children[0] as HTMLElement); - - const foodOptions = this.simUI.splitRelevantOptions([ - { item: Food.FoodFishFeast, stats: [Stat.StatStamina, Stat.StatAttackPower, Stat.StatRangedAttackPower, Stat.StatSpellPower] }, - { item: Food.FoodGreatFeast, stats: [Stat.StatStamina, Stat.StatAttackPower, Stat.StatRangedAttackPower, Stat.StatSpellPower] }, - { item: Food.FoodBlackenedDragonfin, stats: [Stat.StatAgility] }, - { item: Food.FoodDragonfinFilet, stats: [Stat.StatStrength] }, - { item: Food.FoodCuttlesteak, stats: [Stat.StatSpirit] }, - { item: Food.FoodMegaMammothMeal, stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower] }, - { item: Food.FoodHeartyRhino, stats: [Stat.StatArmorPenetration] }, - { item: Food.FoodRhinoliciousWormsteak, stats: [Stat.StatExpertise] }, - { item: Food.FoodFirecrackerSalmon, stats: [Stat.StatSpellPower] }, - { item: Food.FoodSnapperExtreme, stats: [Stat.StatMeleeHit, Stat.StatSpellHit] }, - { item: Food.FoodSpicedWormBurger, stats: [Stat.StatMeleeCrit, Stat.StatSpellCrit] }, - { item: Food.FoodImperialMantaSteak, stats: [Stat.StatMeleeHaste, Stat.StatSpellHaste] }, - { item: Food.FoodMightyRhinoDogs, stats: [Stat.StatMP5] }, - ]); - if (foodOptions.length) { - const elem = this.rootElem.querySelector('.consumes-food') as HTMLElement; - new IconEnumPicker(elem, this.simUI.player, IconInputs.makeFoodInput(foodOptions)); - } + const rowElem = this.rootElem.appendChild(fragment.children[0] as HTMLElement); + const foodsElem = this.rootElem.querySelector('.consumes-food') as HTMLElement; + + this.buildPickers({ + changeEmitters: [], + containerElem: rowElem, + options: [ + { + getConfig: () => ConsumablesInputs.makeFoodInput( + relevantStatOptions(ConsumablesInputs.FOOD_CONFIG, this.simUI), + ), + parentElem: foodsElem, + }, + ], + }) } private buildEngPicker() { @@ -218,26 +154,40 @@ export class ConsumesPicker extends Component { fragment.innerHTML = `
-
+
+
+
+
+
`; - this.rootElem.appendChild(fragment.children[0] as HTMLElement); - - const tradeConsumesElem = this.rootElem.querySelector('.consumes-trade') as HTMLElement; - - buildIconInput(tradeConsumesElem, this.simUI.player, IconInputs.ThermalSapper); - buildIconInput(tradeConsumesElem, this.simUI.player, IconInputs.ExplosiveDecoy); - buildIconInput(tradeConsumesElem, this.simUI.player, IconInputs.FillerExplosiveInput); - - const updateProfession = () => { - if (this.simUI.player.hasProfession(Profession.Engineering)) - tradeConsumesElem.parentElement!.classList.remove('hide'); - else - tradeConsumesElem.parentElement!.classList.add('hide'); - }; - this.simUI.player.professionChangeEmitter.on(updateProfession); - updateProfession(); + const rowElem = this.rootElem.appendChild(fragment.children[0] as HTMLElement); + const sapperElem = this.rootElem.querySelector('.consumes-sapper') as HTMLElement; + const decoyElem = this.rootElem.querySelector('.consumes-decoy') as HTMLElement; + const explosivesElem = this.rootElem.querySelector('.consumes-explosives') as HTMLElement; + + this.buildPickers({ + changeEmitters: [this.simUI.player.professionChangeEmitter], + containerElem: rowElem, + options: [ + { + getConfig: () => ConsumablesInputs.ThermalSapper, + parentElem: sapperElem, + }, + { + getConfig: () => ConsumablesInputs.ExplosiveDecoy, + parentElem: decoyElem, + }, + { + getConfig: () => ConsumablesInputs.makeExplosivesInput( + relevantStatOptions(ConsumablesInputs.EXPLOSIVES_CONFIG, this.simUI), + 'Explosives', + ), + parentElem: explosivesElem, + } + ], + }) } private buildPetPicker() { @@ -246,14 +196,53 @@ export class ConsumesPicker extends Component { fragment.innerHTML = `
-
+
+
+
`; this.rootElem.appendChild(fragment.children[0] as HTMLElement); - const petConsumesElem = this.rootElem.querySelector('.consumes-pet') as HTMLElement; + this.simUI.individualConfig.petConsumeInputs.map(iconInput => buildIconInput(petConsumesElem, this.simUI.player, iconInput)); } } + + private buildPickers({containerElem, changeEmitters, options}: { + containerElem: HTMLElement, + changeEmitters: TypedEvent[], + options: { + getConfig: () => TypedIconPickerConfig, boolean> | TypedIconEnumPickerConfig, number>, + parentElem: HTMLElement, + }[], + }) { + const buildPickers = () => { + const anyPickersShown = options.map(optionSet => { + optionSet.parentElem.innerHTML = ''; + const config = optionSet.getConfig(); + + let isShown: boolean; + if (config.type == 'icon') { + isShown = !config.showWhen || config.showWhen(this.simUI.player); + } else { + isShown = + (!config.showWhen || config.showWhen(this.simUI.player)) && + config.values.filter(value => !value.showWhen || value.showWhen(this.simUI.player)).length > 1; + } + + if (isShown) buildIconInput(optionSet.parentElem, this.simUI.player, config); + + return isShown; + }).filter(isShown => isShown).length > 0; + + if (anyPickersShown) + containerElem.classList.remove('hide'); + else + containerElem.classList.add('hide'); + }; + + TypedEvent.onAny(changeEmitters).on(buildPickers) + buildPickers() + } } diff --git a/ui/core/components/individual_sim_ui/settings_tab.ts b/ui/core/components/individual_sim_ui/settings_tab.ts index f313fbdf81..e5898adcca 100644 --- a/ui/core/components/individual_sim_ui/settings_tab.ts +++ b/ui/core/components/individual_sim_ui/settings_tab.ts @@ -1,4 +1,5 @@ -import { IndividualSimUI, InputSection, StatOption } from "../../individual_sim_ui"; +import { Encounter } from '../../encounter'; +import { IndividualSimUI, InputSection } from "../../individual_sim_ui"; import { Consumes, Debuffs, @@ -9,34 +10,30 @@ import { Profession, RaidBuffs, Spec, - Stat } from "../../proto/common"; -import { ActionId } from "../../proto_utils/action_id"; +import { SavedEncounter, SavedSettings } from "../../proto/ui"; import { professionNames, raceNames } from "../../proto_utils/names"; import { specToEligibleRaces } from "../../proto_utils/utils"; -import { Encounter } from '../../encounter'; -import { SavedEncounter, SavedSettings } from "../../proto/ui"; import { EventID, TypedEvent } from "../../typed_event"; import { getEnumValues } from "../../utils"; -import { Player } from "../../player"; +import { BooleanPicker } from "../boolean_picker"; import { ContentBlock } from "../content_block"; import { EncounterPicker } from '../encounter_picker.js'; -import { SavedDataManager } from "../saved_data_manager"; -import { SimTab } from "../sim_tab"; -import { NumberPicker } from "../number_picker"; -import { BooleanPicker } from "../boolean_picker"; import { EnumPicker } from "../enum_picker"; import { Input } from "../input"; +import { relevantStatOptions } from "../inputs/stat_options"; +import { ItemSwapPicker } from "../item_swap_picker"; import { MultiIconPicker } from "../multi_icon_picker"; -import { IconPickerConfig } from "../icon_picker"; -import { TypedIconPickerConfig } from "../input_helpers"; +import { NumberPicker } from "../number_picker"; +import { SavedDataManager } from "../saved_data_manager"; +import { SimTab } from "../sim_tab"; import { ConsumesPicker } from "./consumes_picker"; import * as IconInputs from '../icon_inputs.js'; +import * as BuffDebuffInputs from '../inputs/buffs_debuffs'; import * as Tooltips from '../../constants/tooltips.js'; -import { ItemSwapPicker } from "../item_swap_picker"; export class SettingsTab extends SimTab { protected simUI: IndividualSimUI; @@ -215,73 +212,16 @@ export class SettingsTab extends SimTab { header: { title: 'Raid Buffs', tooltip: Tooltips.BUFFS_SECTION } }); - const buffOptions = this.simUI.splitRelevantOptions([ - { item: IconInputs.AllStatsBuff, stats: [] }, - { item: IconInputs.AllStatsPercentBuff, stats: [] }, - { item: IconInputs.HealthBuff, stats: [Stat.StatHealth] }, - { item: IconInputs.ArmorBuff, stats: [Stat.StatArmor] }, - { item: IconInputs.StaminaBuff, stats: [Stat.StatStamina] }, - { item: IconInputs.StrengthAndAgilityBuff, stats: [Stat.StatStrength, Stat.StatAgility] }, - { item: IconInputs.IntellectBuff, stats: [Stat.StatIntellect] }, - { item: IconInputs.SpiritBuff, stats: [Stat.StatSpirit] }, - { item: IconInputs.AttackPowerBuff, stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower] }, - { item: IconInputs.AttackPowerPercentBuff, stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower] }, - { item: IconInputs.MeleeCritBuff, stats: [Stat.StatMeleeCrit] }, - { item: IconInputs.MeleeHasteBuff, stats: [Stat.StatMeleeHaste] }, - { item: IconInputs.SpellPowerBuff, stats: [Stat.StatSpellPower] }, - { item: IconInputs.SpellCritBuff, stats: [Stat.StatSpellCrit] }, - { item: IconInputs.HastePercentBuff, stats: [Stat.StatMeleeHaste, Stat.StatSpellHaste] }, - { item: IconInputs.DamagePercentBuff, stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower, Stat.StatSpellPower] }, - { item: IconInputs.DamageReductionPercentBuff, stats: [Stat.StatArmor] }, - { item: IconInputs.ResistanceBuff, stats: [Stat.StatNatureResistance, Stat.StatShadowResistance, Stat.StatFrostResistance] }, - { item: IconInputs.DefensiveCooldownBuff, stats: [Stat.StatArmor] }, - { item: IconInputs.MP5Buff, stats: [Stat.StatMP5] }, - { item: IconInputs.ReplenishmentBuff, stats: [Stat.StatMP5] }, - ]); - + const buffOptions = relevantStatOptions(BuffDebuffInputs.RAID_BUFFS_CONFIG, this.simUI); this.configureIconSection( contentBlock.bodyElement, - buffOptions.map(multiIconInput => new MultiIconPicker(contentBlock.bodyElement, this.simUI.player, multiIconInput, this.simUI)) + buffOptions.map(options => options.picker && new options.picker(contentBlock.bodyElement, this.simUI.player, options.config as any, this.simUI)), ); - const otherBuffOptions = this.simUI.splitRelevantOptions([ - { item: IconInputs.Bloodlust, stats: [Stat.StatMeleeHaste, Stat.StatSpellHaste], inline: true, }, - { item: IconInputs.SpellHasteBuff, stats: [Stat.StatSpellHaste] }, - ] as Array, any>>>); - otherBuffOptions.forEach(iconInput => IconInputs.buildIconInput(contentBlock.bodyElement, this.simUI.player, iconInput)); - - const revitalizeBuffOptions = this.simUI.splitRelevantOptions([ - { item: IconInputs.RevitalizeRejuvination, stats: [] }, - { item: IconInputs.RevitalizeWildGrowth, stats: [] }, - ] as Array, any>>>); - if (revitalizeBuffOptions.length > 0) { - new MultiIconPicker(contentBlock.bodyElement, this.simUI.player, { - inputs: revitalizeBuffOptions, - numColumns: 1, - label: 'Revit', - categoryId: ActionId.fromSpellId(48545), - }, this.simUI); - } - - const miscBuffOptions = this.simUI.splitRelevantOptions([ - { item: IconInputs.StrengthOfWrynn, stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower, Stat.StatSpellPower] }, - { item: IconInputs.HeroicPresence, stats: [Stat.StatMeleeHit, Stat.StatSpellHit] }, - { item: IconInputs.BraidedEterniumChain, stats: [Stat.StatMeleeCrit, Stat.StatSpellCrit] }, - { item: IconInputs.ChainOfTheTwilightOwl, stats: [Stat.StatSpellCrit, Stat.StatMeleeCrit] }, - { item: IconInputs.FocusMagic, stats: [Stat.StatSpellCrit] }, - { item: IconInputs.EyeOfTheNight, stats: [Stat.StatSpellPower] }, - { item: IconInputs.Thorns, stats: [Stat.StatArmor] }, - { item: IconInputs.RetributionAura, stats: [Stat.StatArmor] }, - { item: IconInputs.ManaTideTotem, stats: [Stat.StatMP5] }, - { item: IconInputs.Innervate, stats: [Stat.StatMP5] }, - { item: IconInputs.PowerInfusion, stats: [Stat.StatMP5, Stat.StatSpellPower] }, - { item: IconInputs.TricksOfTheTrade, stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower, Stat.StatSpellPower] }, - { item: IconInputs.UnholyFrenzy, stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower] }, - ] as Array, any>>>); + const miscBuffOptions = relevantStatOptions(BuffDebuffInputs.RAID_BUFFS_MISC_CONFIG, this.simUI); if (miscBuffOptions.length > 0) { new MultiIconPicker(contentBlock.bodyElement, this.simUI.player, { - inputs: miscBuffOptions, - numColumns: 3, + inputs: miscBuffOptions.map(option => option.config), label: 'Misc', }, this.simUI); } @@ -292,41 +232,16 @@ export class SettingsTab extends SimTab { header: { title: 'Debuffs', tooltip: Tooltips.DEBUFFS_SECTION } }); - const debuffOptions = this.simUI.splitRelevantOptions([ - { item: IconInputs.MajorArmorDebuff, stats: [Stat.StatArmorPenetration] }, - { item: IconInputs.MinorArmorDebuff, stats: [Stat.StatArmorPenetration] }, - { item: IconInputs.PhysicalDamageDebuff, stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower] }, - { item: IconInputs.BleedDebuff, stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower] }, - { item: IconInputs.SpellDamageDebuff, stats: [Stat.StatSpellPower] }, - { item: IconInputs.SpellHitDebuff, stats: [Stat.StatSpellHit] }, - { item: IconInputs.SpellCritDebuff, stats: [Stat.StatSpellCrit] }, - { item: IconInputs.CritDebuff, stats: [Stat.StatMeleeCrit, Stat.StatSpellCrit] }, - { item: IconInputs.AttackPowerDebuff, stats: [Stat.StatArmor] }, - { item: IconInputs.MeleeAttackSpeedDebuff, stats: [Stat.StatArmor] }, - { item: IconInputs.MeleeHitDebuff, stats: [Stat.StatDodge] }, - ]); - + const debuffOptions = relevantStatOptions(BuffDebuffInputs.DEBUFFS_CONFIG, this.simUI); this.configureIconSection( contentBlock.bodyElement, - debuffOptions.map(multiIconInput => new MultiIconPicker(contentBlock.bodyElement, this.simUI.player, multiIconInput, this.simUI)) + debuffOptions.map(options => options.picker && new options.picker(contentBlock.bodyElement, this.simUI.player, options.config as any, this.simUI)) ); - const otherDebuffOptions = this.simUI.splitRelevantOptions([ - { item: IconInputs.JudgementOfWisdom, stats: [Stat.StatMP5, Stat.StatIntellect] }, - { item: IconInputs.HuntersMark, stats: [Stat.StatRangedAttackPower] }, - ] as Array, any>>>); - otherDebuffOptions.forEach(iconInput => IconInputs.buildIconInput(contentBlock.bodyElement, this.simUI.player, iconInput)); - - const miscDebuffOptions = this.simUI.splitRelevantOptions([ - { item: IconInputs.JudgementOfLight, stats: [Stat.StatStamina] }, - { item: IconInputs.ShatteringThrow, stats: [Stat.StatArmorPenetration] }, - { item: IconInputs.GiftOfArthas, stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower] }, - { item: IconInputs.CrystalYield, stats: [Stat.StatArmorPenetration] }, - ] as Array, any>>>); - if (miscDebuffOptions.length > 0) { + const miscDebuffOptions = relevantStatOptions(BuffDebuffInputs.DEBUFFS_MISC_CONFIG, this.simUI) + if (miscDebuffOptions.length) { new MultiIconPicker(contentBlock.bodyElement, this.simUI.player, { - inputs: miscDebuffOptions, - numColumns: 3, + inputs: miscDebuffOptions.map(options => options.config), label: 'Misc', }, this.simUI); } diff --git a/ui/core/components/input_helpers.ts b/ui/core/components/input_helpers.ts index 4ad78bfd18..7346d67434 100644 --- a/ui/core/components/input_helpers.ts +++ b/ui/core/components/input_helpers.ts @@ -10,11 +10,12 @@ import { BooleanPickerConfig } from './boolean_picker.js'; import { NumberPickerConfig } from './number_picker.js'; import { MultiIconPickerConfig } from './multi_icon_picker.js'; -export function makeMultiIconInput(inputs: Array>, label: string, numColumns?: number): MultiIconPickerConfig { +export function makeMultiIconInput(inputs: Array>, label: string, categoryId?: ActionId): MultiIconPickerConfig { return { inputs: inputs, - numColumns: numColumns || 1, label: label, + categoryId: categoryId, + showWhen: (p) => inputs.filter(i => !i.showWhen || i.showWhen(p as ModObject)).length > 0 }; } @@ -221,7 +222,7 @@ export interface PlayerEnumInputConfig { changeEmitter?: (player: Player) => TypedEvent, } // T is unused, but kept to have the same interface as the icon enum inputs. -export function makeSpecOptionsEnumInput(config: PlayerEnumInputConfig>): TypedEnumPickerConfig> { +export function makeSpecOptionsEnumInput(config: PlayerEnumInputConfig>): TypedEnumPickerConfig> { return makeWrappedEnumInput>({ label: config.label, labelTooltip: config.labelTooltip, @@ -239,7 +240,7 @@ export function makeSpecOptionsEnumInput(config: Playe }); } // T is unused, but kept to have the same interface as the icon enum inputs. -export function makeRotationEnumInput(config: PlayerEnumInputConfig>): TypedEnumPickerConfig> { +export function makeRotationEnumInput(config: PlayerEnumInputConfig>): TypedEnumPickerConfig> { return makeWrappedEnumInput>({ label: config.label, labelTooltip: config.labelTooltip, @@ -272,7 +273,7 @@ function makeWrappedIconInput(config: Wrapp const getModObject = config.getModObject; return { type: 'icon', - id: config.id, + actionId: config.actionId, states: config.states, changedEvent: (player: Player) => config.changedEvent(getModObject(player)), getValue: (player: Player) => config.getValue(getModObject(player)), @@ -288,14 +289,15 @@ interface WrappedTypedInputConfig { changeEmitter: (modObj: ModObject) => TypedEvent, extraCssClasses?: Array, + showWhen?: (obj: ModObject) => boolean, getFieldValue?: (modObj: ModObject) => T, setFieldValue?: (eventID: EventID, modObj: ModObject, newValue: T) => void, } -export function makeBooleanIconInput(config: WrappedTypedInputConfig, id: ActionId, fieldName: keyof Message, value?: number): TypedIconPickerConfig, boolean> { +export function makeBooleanIconInput(config: WrappedTypedInputConfig, actionId: ActionId, fieldName: keyof Message, value?: number): TypedIconPickerConfig, boolean> { return makeWrappedIconInput({ getModObject: config.getModObject, - id: id, + actionId: actionId, states: 2, changedEvent: config.changeEmitter, getValue: config.getFieldValue || ((modObj: ModObject) => value ? (config.getValue(modObj)[fieldName] as unknown as number) == value : (config.getValue(modObj)[fieldName] as unknown as boolean)), @@ -333,10 +335,10 @@ export function makeSpecOptionsBooleanIconInput(config: P }, config.id, config.fieldName, config.value); } -function makeNumberIconInput(config: WrappedTypedInputConfig, id: ActionId, fieldName: keyof Message, multiplier?: number): TypedIconPickerConfig, number> { +function makeNumberIconInput(config: WrappedTypedInputConfig, actionId: ActionId, fieldName: keyof Message, multiplier?: number): TypedIconPickerConfig, number> { return makeWrappedIconInput({ getModObject: config.getModObject, - id: id, + actionId: actionId, states: 0, // Must be assigned externally. changedEvent: config.changeEmitter, getValue: (modObj: ModObject) => config.getValue(modObj)[fieldName] as unknown as number, diff --git a/ui/core/components/inputs/buffs_debuffs.ts b/ui/core/components/inputs/buffs_debuffs.ts new file mode 100644 index 0000000000..e6f4d6e3d1 --- /dev/null +++ b/ui/core/components/inputs/buffs_debuffs.ts @@ -0,0 +1,549 @@ +import { Stat, TristateEffect } from "../../proto/common"; +import { ActionId } from "../../proto_utils/action_id"; + +import { + makeBooleanDebuffInput, + makeBooleanIndividualBuffInput, + makeBooleanPartyBuffInput, + makeBooleanRaidBuffInput, + makeMultistateIndividualBuffInput, + makeMultistateMultiplierIndividualBuffInput, + makeMultistatePartyBuffInput, + makeMultistateRaidBuffInput, + makeQuadstateDebuffInput, + makeTristateDebuffInput, + makeTristateIndividualBuffInput, + makeTristateRaidBuffInput, + withLabel, +} from "../icon_inputs"; +import { IconPicker } from "../icon_picker"; +import { MultiIconPicker } from "../multi_icon_picker"; + +import { IconPickerStatOption, PickerStatOptions } from "./stat_options"; + +import * as InputHelpers from '../input_helpers'; + +/////////////////////////////////////////////////////////////////////////// +// RAID BUFFS +/////////////////////////////////////////////////////////////////////////// + +export const AllStatsBuff = InputHelpers.makeMultiIconInput([ + makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(48470), impId: ActionId.fromSpellId(17051), fieldName: 'giftOfTheWild'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromItemId(49634), fieldName: 'drumsOfTheWild'}), +], 'Stats'); + +export const AllStatsPercentBuff = InputHelpers.makeMultiIconInput([ + makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(25898), fieldName: 'blessingOfKings'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromItemId(49633), fieldName: 'drumsOfForgottenKings'}), + makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(25899), fieldName: 'blessingOfSanctuary'}), +], 'Stats %'); + +export const ArmorBuff = InputHelpers.makeMultiIconInput([ + makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(48942), impId: ActionId.fromSpellId(20140), fieldName: 'devotionAura'}), + makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(58753), impId: ActionId.fromSpellId(16293), fieldName: 'stoneskinTotem'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromItemId(43468), fieldName: 'scrollOfProtection'}), +], 'Armor'); + +export const AttackPowerBuff = InputHelpers.makeMultiIconInput([ + makeTristateIndividualBuffInput({actionId: ActionId.fromSpellId(48934), impId: ActionId.fromSpellId(20045), fieldName: 'blessingOfMight'}), + makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(47436), impId: ActionId.fromSpellId(12861), fieldName: 'battleShout'}), +], 'AP'); + +export const AttackPowerPercentBuff = InputHelpers.makeMultiIconInput([ + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(53138), fieldName: 'abominationsMight'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(30809), fieldName: 'unleashedRage'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(19506), fieldName: 'trueshotAura'}), +], 'Atk Pwr %'); + +export const Bloodlust = withLabel( + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(2825), fieldName: 'bloodlust'}), + 'Lust', +); + +export const DamagePercentBuff = InputHelpers.makeMultiIconInput([ + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(31869), fieldName: 'sanctifiedRetribution'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(31583), fieldName: 'arcaneEmpowerment'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(34460), fieldName: 'ferociousInspiration'}), +], 'Dmg %'); + +export const DamageReductionPercentBuff = InputHelpers.makeMultiIconInput([ + makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(57472), fieldName: 'renewedHope'}), + makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(25899), fieldName: 'blessingOfSanctuary'}), + makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(50720), fieldName: 'vigilance'}), +], 'Mit %'); + +export const DefensiveCooldownBuff = InputHelpers.makeMultiIconInput([ + makeMultistateIndividualBuffInput({actionId: ActionId.fromSpellId(6940), numStates: 11, fieldName: 'handOfSacrifices'}), + makeMultistateIndividualBuffInput({actionId: ActionId.fromSpellId(53530), numStates: 11, fieldName: 'divineGuardians'}), + makeMultistateIndividualBuffInput({actionId: ActionId.fromSpellId(33206), numStates: 11, fieldName: 'painSuppressions'}), + makeMultistateIndividualBuffInput({actionId: ActionId.fromSpellId(47788), numStates: 11, fieldName: 'guardianSpirits'}), +], 'Defensive CDs'); + +export const HastePercentBuff = InputHelpers.makeMultiIconInput([ + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(53648), fieldName: 'swiftRetribution'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(48396), fieldName: 'moonkinAura', value: TristateEffect.TristateEffectImproved}), +], 'Haste %'); + +export const HealthBuff = InputHelpers.makeMultiIconInput([ + makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(47440), impId: ActionId.fromSpellId(12861), fieldName: 'commandingShout'}), + makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(47982), impId: ActionId.fromSpellId(18696), fieldName: 'bloodPact'}), +], 'Health'); + +export const IntellectBuff = InputHelpers.makeMultiIconInput([ + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(43002), fieldName: 'arcaneBrilliance'}), + makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(57567), impId: ActionId.fromSpellId(54038), fieldName: 'felIntelligence'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromItemId(37092), fieldName: 'scrollOfIntellect'}), +], 'Int'); + +export const MeleeCritBuff = InputHelpers.makeMultiIconInput([ + makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(17007), impId: ActionId.fromSpellId(34300), fieldName: 'leaderOfThePack'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(29801), fieldName: 'rampage'}), +], 'Melee Crit'); + +export const MeleeHasteBuff = InputHelpers.makeMultiIconInput([ + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(55610), fieldName: 'icyTalons'}), + makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(65990), impId: ActionId.fromSpellId(29193), fieldName: 'windfuryTotem'}), +], 'Melee Haste'); + +export const MP5Buff = InputHelpers.makeMultiIconInput([ + makeTristateIndividualBuffInput({actionId: ActionId.fromSpellId(48938), impId: ActionId.fromSpellId(20245), fieldName: 'blessingOfWisdom'}), + makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(58774), impId: ActionId.fromSpellId(16206), fieldName: 'manaSpringTotem'}), +], 'MP5'); + +export const ReplenishmentBuff = InputHelpers.makeMultiIconInput([ + makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(48160), fieldName: 'vampiricTouch'}), + makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(31878), fieldName: 'judgementsOfTheWise'}), + makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(53292), fieldName: 'huntingParty'}), + makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(54118), fieldName: 'improvedSoulLeech'}), + makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(44561), fieldName: 'enduringWinter'}), +], 'Replen'); + +export const ResistanceBuff = InputHelpers.makeMultiIconInput([ + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(48170), fieldName: 'shadowProtection'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(58749), fieldName: 'natureResistanceTotem'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(49071), fieldName: 'aspectOfTheWild'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(48945), fieldName: 'frostResistanceAura'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(58745), fieldName: 'frostResistanceTotem'}), +], 'Resistances'); + +export const RevitalizeBuff = InputHelpers.makeMultiIconInput([ + makeMultistateMultiplierIndividualBuffInput(ActionId.fromSpellId(26982), 101, 10, 'revitalizeRejuvination'), + makeMultistateMultiplierIndividualBuffInput(ActionId.fromSpellId(53251), 101, 10, 'revitalizeWildGrowth'), +], 'Revit', ActionId.fromSpellId(48545)) + +export const SpellCritBuff = InputHelpers.makeMultiIconInput([ + makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(24907), impId: ActionId.fromSpellId(48396), fieldName: 'moonkinAura'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(51470), fieldName: 'elementalOath'}), +], 'Spell Crit'); + +export const SpellHasteBuff = withLabel( + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(3738), fieldName: 'wrathOfAirTotem'}), + 'Spell Haste', +); + +export const SpellPowerBuff = InputHelpers.makeMultiIconInput([ + makeMultistateRaidBuffInput({actionId: ActionId.fromSpellId(47240), numStates: 2000, fieldName: 'demonicPactSp', multiplier: 20}), + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(57722), fieldName: 'totemOfWrath'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(58656), fieldName: 'flametongueTotem'}), +], 'Spell Power'); + +export const SpiritBuff = InputHelpers.makeMultiIconInput([ + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(48073), fieldName: 'divineSpirit'}), + makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(57567), impId: ActionId.fromSpellId(54038), fieldName: 'felIntelligence'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromItemId(37098), fieldName: 'scrollOfSpirit'}), +], 'Spirit'); + +export const StaminaBuff = InputHelpers.makeMultiIconInput([ + makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(48161), impId: ActionId.fromSpellId(14767), fieldName: 'powerWordFortitude'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromItemId(37094), fieldName: 'scrollOfStamina'}), +], 'Stamina'); + +export const StrengthAndAgilityBuff = InputHelpers.makeMultiIconInput([ + makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(58643), impId: ActionId.fromSpellId(52456), fieldName: 'strengthOfEarthTotem'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(57623), fieldName: 'hornOfWinter'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromItemId(43464), fieldName: 'scrollOfAgility'}), + makeBooleanRaidBuffInput({actionId: ActionId.fromItemId(43466), fieldName: 'scrollOfStrength'}), +], 'Str/Agi'); + +// Misc Buffs +export const StrengthOfWrynn = makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(73827), fieldName: 'strengthOfWrynn'}); +export const RetributionAura = makeBooleanRaidBuffInput({actionId: ActionId.fromSpellId(54043), fieldName: 'retributionAura'}); +export const BraidedEterniumChain = makeBooleanPartyBuffInput({actionId: ActionId.fromSpellId(31025), fieldName: 'braidedEterniumChain'}); +export const ChainOfTheTwilightOwl = makeBooleanPartyBuffInput({actionId: ActionId.fromSpellId(31035), fieldName: 'chainOfTheTwilightOwl'}); +export const HeroicPresence = makeBooleanPartyBuffInput({actionId: ActionId.fromSpellId(6562), fieldName: 'heroicPresence'}); +export const EyeOfTheNight = makeBooleanPartyBuffInput({actionId: ActionId.fromSpellId(31033), fieldName: 'eyeOfTheNight'}); +export const Thorns = makeTristateRaidBuffInput({actionId: ActionId.fromSpellId(53307), impId: ActionId.fromSpellId(16840), fieldName: 'thorns'}); +export const ManaTideTotem = makeMultistatePartyBuffInput(ActionId.fromSpellId(16190), 5, 'manaTideTotems'); +export const Innervate = makeMultistateIndividualBuffInput({actionId: ActionId.fromSpellId(29166), numStates: 11, fieldName: 'innervates'}); +export const PowerInfusion = makeMultistateIndividualBuffInput({actionId: ActionId.fromSpellId(10060), numStates: 11, fieldName: 'powerInfusions'}); +export const FocusMagic = makeBooleanIndividualBuffInput({actionId: ActionId.fromSpellId(54648), fieldName: 'focusMagic'}); +export const TricksOfTheTrade = makeMultistateIndividualBuffInput({actionId: ActionId.fromSpellId(57933), numStates: 20, fieldName: 'tricksOfTheTrades'}); +export const UnholyFrenzy = makeMultistateIndividualBuffInput({actionId: ActionId.fromSpellId(49016), numStates: 11, fieldName: 'unholyFrenzy'}); + +/////////////////////////////////////////////////////////////////////////// +// DEBUFFS +/////////////////////////////////////////////////////////////////////////// + +export const MajorArmorDebuff = InputHelpers.makeMultiIconInput([ + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(47467), fieldName: 'sunderArmor'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(8647), fieldName: 'exposeArmor'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(55754), fieldName: 'acidSpit'}), +], 'Major ArP'); + +export const MinorArmorDebuff = InputHelpers.makeMultiIconInput([ + makeTristateDebuffInput({actionId: ActionId.fromSpellId(770), impId: ActionId.fromSpellId(33602), fieldName: 'faerieFire'}), + makeTristateDebuffInput({actionId: ActionId.fromSpellId(50511), impId: ActionId.fromSpellId(18180), fieldName: 'curseOfWeakness'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(56631), fieldName: 'sting'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(53598), fieldName: 'sporeCloud'}), +], 'Minor ArP'); + +export const AttackPowerDebuff = InputHelpers.makeMultiIconInput([ + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(26016), fieldName: 'vindication'}), + makeTristateDebuffInput({actionId: ActionId.fromSpellId(47437), impId: ActionId.fromSpellId(12879), fieldName: 'demoralizingShout'}), + makeTristateDebuffInput({actionId: ActionId.fromSpellId(48560), impId: ActionId.fromSpellId(16862), fieldName: 'demoralizingRoar'}), + makeTristateDebuffInput({actionId: ActionId.fromSpellId(50511), impId: ActionId.fromSpellId(18180), fieldName: 'curseOfWeakness'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(55487), fieldName: 'demoralizingScreech'}), +], 'Atk Pwr'); + +export const BleedDebuff = InputHelpers.makeMultiIconInput([ + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(48564), fieldName: 'mangle'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(46855), fieldName: 'trauma'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(57393), fieldName: 'stampede'}), +], 'Bleed'); + +export const CritDebuff = InputHelpers.makeMultiIconInput([ + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(30706), fieldName: 'totemOfWrath'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(20337), fieldName: 'heartOfTheCrusader'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(58410), fieldName: 'masterPoisoner'}), +], 'Crit'); + +export const MeleeAttackSpeedDebuff = InputHelpers.makeMultiIconInput([ + makeTristateDebuffInput({actionId: ActionId.fromSpellId(47502), impId: ActionId.fromSpellId(12666), fieldName: 'thunderClap'}), + makeTristateDebuffInput({actionId: ActionId.fromSpellId(55095), impId: ActionId.fromSpellId(51456), fieldName: 'frostFever'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(53696), fieldName: 'judgementsOfTheJust'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(48485), fieldName: 'infectedWounds'}), +], 'Atk Speed'); + +export const MeleeHitDebuff = InputHelpers.makeMultiIconInput([ + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(65855), fieldName: 'insectSwarm'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(3043), fieldName: 'scorpidSting'}), +], 'Miss'); + +export const PhysicalDamageDebuff = InputHelpers.makeMultiIconInput([ + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(29859), fieldName: 'bloodFrenzy'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(58413), fieldName: 'savageCombat'}), +], 'Phys Vuln'); + +export const SpellCritDebuff = InputHelpers.makeMultiIconInput([ + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(17803), fieldName: 'shadowMastery'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(12873), fieldName: 'improvedScorch'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(28593), fieldName: 'wintersChill'}), +], 'Spell Crit'); + +export const SpellHitDebuff = InputHelpers.makeMultiIconInput([ + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(33198), fieldName: 'misery'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(33602), fieldName: 'faerieFire', value: TristateEffect.TristateEffectImproved}), +], 'Spell Hit'); + +export const SpellDamageDebuff = InputHelpers.makeMultiIconInput([ + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(51161), fieldName: 'ebonPlaguebringer'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(48511), fieldName: 'earthAndMoon'}), + makeBooleanDebuffInput({actionId: ActionId.fromSpellId(47865), fieldName: 'curseOfElements'}), +], 'Spell Dmg'); + +export const HuntersMark = withLabel( + makeQuadstateDebuffInput({actionId: ActionId.fromSpellId(53338), impId: ActionId.fromSpellId(19423), impId2: ActionId.fromItemId(42907), fieldName: 'huntersMark'}), + 'Mark', +); +export const JudgementOfWisdom = withLabel(makeBooleanDebuffInput({actionId: ActionId.fromSpellId(53408), fieldName: 'judgementOfWisdom'}), 'JoW'); +export const JudgementOfLight = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(20271), fieldName: 'judgementOfLight'}); +export const ShatteringThrow = makeMultistateIndividualBuffInput({actionId: ActionId.fromSpellId(64382), numStates: 20, fieldName: 'shatteringThrows'}); +export const GiftOfArthas = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(11374), fieldName: 'giftOfArthas'}); +export const CrystalYield = makeBooleanDebuffInput({actionId: ActionId.fromSpellId(15235), fieldName: 'crystalYield'}); + +/////////////////////////////////////////////////////////////////////////// +// CONFIGS +/////////////////////////////////////////////////////////////////////////// + +export const RAID_BUFFS_CONFIG = [ + // Standard buffs + { + config: AllStatsBuff, + picker: MultiIconPicker, + stats: [], + }, + { + config: AllStatsPercentBuff, + picker: MultiIconPicker, + stats: [], + }, + { + config: HealthBuff, + picker: MultiIconPicker, + stats: [Stat.StatHealth], + }, + { + config: ArmorBuff, + picker: MultiIconPicker, + stats: [Stat.StatArmor], + }, + { + config: StaminaBuff, + picker: MultiIconPicker, + stats: [Stat.StatStamina], + }, + { + config: StrengthAndAgilityBuff, + picker: MultiIconPicker, + stats: [Stat.StatStrength, Stat.StatAgility], + }, + { + config: IntellectBuff, + picker: MultiIconPicker, + stats: [Stat.StatIntellect], + }, + { + config: SpiritBuff, + picker: MultiIconPicker, + stats: [Stat.StatSpirit], + }, + { + config: AttackPowerBuff, + picker: MultiIconPicker, + stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower], + }, + { + config: AttackPowerPercentBuff, + picker: MultiIconPicker, + stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower], + }, + { + config: MeleeCritBuff, + picker: MultiIconPicker, + stats: [Stat.StatMeleeCrit], + }, + { + config: MeleeHasteBuff, + picker: MultiIconPicker, + stats: [Stat.StatMeleeHaste], + }, + { + config: SpellPowerBuff, + picker: MultiIconPicker, + stats: [Stat.StatSpellPower], + }, + { + config: SpellCritBuff, + picker: MultiIconPicker, + stats: [Stat.StatSpellCrit], + }, + { + config: HastePercentBuff, + picker: MultiIconPicker, + stats: [Stat.StatMeleeHaste, Stat.StatSpellHaste], + }, + { + config: DamagePercentBuff, + picker: MultiIconPicker, + stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower, Stat.StatSpellPower], + }, + { + config: DamageReductionPercentBuff, + picker: MultiIconPicker, + stats: [Stat.StatArmor], + }, + { + config: ResistanceBuff, + picker: MultiIconPicker, + stats: [Stat.StatNatureResistance, Stat.StatShadowResistance, Stat.StatFrostResistance] + }, + { + config: DefensiveCooldownBuff, + picker: MultiIconPicker, + stats: [Stat.StatArmor] + }, + { + config: MP5Buff, + picker: MultiIconPicker, + stats: [Stat.StatMP5] + }, + { + config: ReplenishmentBuff, + picker: MultiIconPicker, + stats: [Stat.StatMP5] + }, + { + config: Bloodlust, + picker: IconPicker, + stats: [Stat.StatMeleeHaste, Stat.StatSpellHaste], + }, + { + config: SpellHasteBuff, + picker: IconPicker, + stats: [Stat.StatSpellHaste], + }, + { + config: RevitalizeBuff, + picker: MultiIconPicker, + stats: [], + }, +] as PickerStatOptions[] + +export const RAID_BUFFS_MISC_CONFIG = [ + { + config: StrengthOfWrynn, + picker: IconPicker, + stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower, Stat.StatSpellPower], + }, + { + config: HeroicPresence, + picker: IconPicker, + stats: [Stat.StatMeleeHit, Stat.StatSpellHit], + }, + { + config: BraidedEterniumChain, + picker: IconPicker, + stats: [Stat.StatMeleeCrit, Stat.StatSpellCrit], + }, + { + config: ChainOfTheTwilightOwl, + picker: IconPicker, + stats: [Stat.StatSpellCrit, Stat.StatMeleeCrit], + }, + { + config: FocusMagic, + picker: IconPicker, + stats: [Stat.StatSpellCrit], + }, + { + config: EyeOfTheNight, + picker: IconPicker, + stats: [Stat.StatSpellPower], + }, + { + config: Thorns, + picker: IconPicker, + stats: [Stat.StatArmor], + }, + { + config: RetributionAura, + picker: IconPicker, + stats: [Stat.StatArmor], + }, + { + config: ManaTideTotem, + picker: IconPicker, + stats: [Stat.StatMP5], + }, + { + config: Innervate, + picker: IconPicker, + stats: [Stat.StatMP5], + }, + { + config: PowerInfusion, + picker: IconPicker, + stats: [Stat.StatMP5, Stat.StatSpellPower], + }, + { + config: TricksOfTheTrade, + picker: IconPicker, + stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower, Stat.StatSpellPower], + }, + { + config: UnholyFrenzy, + picker: IconPicker, + stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower], + }, +] as IconPickerStatOption[] + +export const DEBUFFS_CONFIG = [ + { + config: MajorArmorDebuff, + picker: MultiIconPicker, + stats: [Stat.StatArmorPenetration] + }, + { + config: MinorArmorDebuff, + picker: MultiIconPicker, + stats: [Stat.StatArmorPenetration] + }, + { + config: PhysicalDamageDebuff, + picker: MultiIconPicker, + stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower] + }, + { + config: BleedDebuff, + picker: MultiIconPicker, + stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower] + }, + { + config: SpellDamageDebuff, + picker: MultiIconPicker, + stats: [Stat.StatSpellPower] + }, + { + config: SpellHitDebuff, + picker: MultiIconPicker, + stats: [Stat.StatSpellHit] + }, + { + config: SpellCritDebuff, + picker: MultiIconPicker, + stats: [Stat.StatSpellCrit] + }, + { + config: CritDebuff, + picker: MultiIconPicker, + stats: [Stat.StatMeleeCrit, Stat.StatSpellCrit] + }, + { + config: AttackPowerDebuff, + picker: MultiIconPicker, + stats: [Stat.StatArmor] + }, + { + config: MeleeAttackSpeedDebuff, + picker: MultiIconPicker, + stats: [Stat.StatArmor] + }, + { + config: MeleeHitDebuff, + picker: MultiIconPicker, + stats: [Stat.StatDodge] + }, + { + config: JudgementOfWisdom, + picker: IconPicker, + stats: [Stat.StatMP5, Stat.StatIntellect] + }, + { + config: HuntersMark, + picker: IconPicker, + stats: [Stat.StatRangedAttackPower] + }, +] as PickerStatOptions[]; + +export const DEBUFFS_MISC_CONFIG = [ + { + config: JudgementOfLight, + picker: IconPicker, + stats: [Stat.StatStamina], + }, + { + config: ShatteringThrow, + picker: IconPicker, + stats: [Stat.StatArmorPenetration], + }, + { + config: GiftOfArthas, + picker: IconPicker, + stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower], + }, + { + config: CrystalYield, + picker: IconPicker, + stats: [Stat.StatArmorPenetration], + }, +] as IconPickerStatOption[]; diff --git a/ui/core/components/inputs/consumables.ts b/ui/core/components/inputs/consumables.ts new file mode 100644 index 0000000000..c2e1a6f8a9 --- /dev/null +++ b/ui/core/components/inputs/consumables.ts @@ -0,0 +1,341 @@ +import { Player } from "../../player"; +import { + BattleElixir, + Class, + Conjured, + Consumes, + Explosive, + Flask, + Food, + GuardianElixir, + PetFood, + Potions, + Spec, + Stat, +} from "../../proto/common"; +import { ActionId } from "../../proto_utils/action_id"; +import { EventID, TypedEvent } from "../../typed_event"; + +import { IconEnumValueConfig } from "../icon_enum_picker"; +import { makeBooleanConsumeInput } from "../icon_inputs"; + +import { ActionInputConfig, ItemStatOption } from "./stat_options"; + +import * as InputHelpers from '../input_helpers'; + +export interface ConsumableInputConfig extends ActionInputConfig { + value: T, +} + +export interface ConsumableStatOption extends ItemStatOption { + config: ConsumableInputConfig +} + +export interface ConsumeInputFactoryArgs { + consumesFieldName: keyof Consumes, + // Additional callback if logic besides syncing consumes is required + onSet?: (eventactionId: EventID, player: Player, newValue: T) => void + showWhen?: (player: Player) => boolean +} + +function makeConsumeInputFactory(args: ConsumeInputFactoryArgs): (options: ConsumableStatOption[], tooltip?: string) => InputHelpers.TypedIconEnumPickerConfig, T> { + return (options: ConsumableStatOption[], tooltip?: string) => { + return { + type: 'iconEnum', + tooltip: tooltip, + numColumns: options.length > 5 ? 2 : 1, + values: [ + { value: 0 } as unknown as IconEnumValueConfig, T>, + ].concat(options.map(option => { + const rtn = { + actionId: option.config.actionId, + showWhen: (player: Player) => + (!option.config.showWhen || option.config.showWhen(player)) && + (option.config.faction || player.getFaction()) == player.getFaction() + } as IconEnumValueConfig, T>; + if (option.config.value) rtn.value = option.config.value + + return rtn + })), + equals: (a: T, b: T) => a == b, + zeroValue: 0 as T, + changedEvent: (player: Player) => TypedEvent.onAny([player.consumesChangeEmitter, player.gearChangeEmitter]), + showWhen: (player: Player) => !args.showWhen || args.showWhen(player), + getValue: (player: Player) => player.getConsumes()[args.consumesFieldName] as T, + setValue: (eventID: EventID, player: Player, newValue: number) => { + const newConsumes = player.getConsumes(); + + if (newConsumes[args.consumesFieldName] === newValue){ + return; + } + + (newConsumes[args.consumesFieldName] as number) = newValue; + TypedEvent.freezeAllAndDo(() => { + player.setConsumes(eventID, newConsumes); + if (args.onSet) { + args.onSet(eventID, player, newValue as T); + } + }); + }, + }; + }; +} + + +/////////////////////////////////////////////////////////////////////////// +// CONJURED +/////////////////////////////////////////////////////////////////////////// + +export const ConjuredDarkRune = { actionId: ActionId.fromItemId(12662), value: Conjured.ConjuredDarkRune }; +export const ConjuredFlameCap = { actionId: ActionId.fromItemId(22788), value: Conjured.ConjuredFlameCap }; +export const ConjuredHealthstone = { actionId: ActionId.fromItemId(22105), value: Conjured.ConjuredHealthstone }; +export const ConjuredRogueThistleTea = { + actionId: ActionId.fromItemId(7676), + value: Conjured.ConjuredRogueThistleTea, + showWhen: (player: Player) => player.getClass() == Class.ClassRogue +}; + +export const CONJURED_CONFIG = [ + { config: ConjuredRogueThistleTea, stats: [] }, + { config: ConjuredHealthstone, stats: [Stat.StatStamina] }, + { config: ConjuredDarkRune, stats: [Stat.StatIntellect] }, + { config: ConjuredFlameCap, stats: [] }, +] as ConsumableStatOption[] + +export const makeConjuredInput = makeConsumeInputFactory({consumesFieldName: 'defaultConjured'}); + +/////////////////////////////////////////////////////////////////////////// +// EXPLOSIVES +/////////////////////////////////////////////////////////////////////////// + +export const ExplosiveSaroniteBomb = { actionId: ActionId.fromItemId(41119), value: Explosive.ExplosiveSaroniteBomb }; +export const ExplosiveCobaltFragBomb = { actionId: ActionId.fromItemId(40771), value: Explosive.ExplosiveCobaltFragBomb }; + +export const EXPLOSIVES_CONFIG = [ + { config: ExplosiveSaroniteBomb, stats: [] }, + { config: ExplosiveCobaltFragBomb, stats: [] }, +] as ConsumableStatOption[]; + +export const makeExplosivesInput = makeConsumeInputFactory({consumesFieldName: 'fillerExplosive'}); + +export const ThermalSapper = makeBooleanConsumeInput({actionId: ActionId.fromItemId(42641), fieldName: 'thermalSapper'}); +export const ExplosiveDecoy = makeBooleanConsumeInput({actionId: ActionId.fromItemId(40536), fieldName: 'explosiveDecoy'}); + +/////////////////////////////////////////////////////////////////////////// +// FLASKS + ELIXIRS +/////////////////////////////////////////////////////////////////////////// + +// Flasks +export const FlaskOfTheFrostWyrm = { actionId: ActionId.fromItemId(46376), value: Flask.FlaskOfTheFrostWyrm }; +export const FlaskOfEndlessRage = { actionId: ActionId.fromItemId(46377), value: Flask.FlaskOfEndlessRage }; +export const FlaskOfPureMojo = { actionId: ActionId.fromItemId(46378), value: Flask.FlaskOfPureMojo }; +export const FlaskOfStoneblood = { actionId: ActionId.fromItemId(46379), value: Flask.FlaskOfStoneblood }; +export const LesserFlaskOfToughness = { actionId: ActionId.fromItemId(40079), value: Flask.LesserFlaskOfToughness }; +export const LesserFlaskOfResistance = { actionId: ActionId.fromItemId(44939), value: Flask.LesserFlaskOfResistance }; +export const FlaskOfBlindingLight = { actionId: ActionId.fromItemId(22861), value: Flask.FlaskOfBlindingLight }; +export const FlaskOfMightyRestoration = { actionId: ActionId.fromItemId(22853), value: Flask.FlaskOfMightyRestoration }; +export const FlaskOfPureDeath = { actionId: ActionId.fromItemId(22866), value: Flask.FlaskOfPureDeath }; +export const FlaskOfRelentlessAssault = { actionId: ActionId.fromItemId(22854), value: Flask.FlaskOfRelentlessAssault }; +export const FlaskOfSupremePower = { actionId: ActionId.fromItemId(13512), value: Flask.FlaskOfSupremePower }; +export const FlaskOfFortification = { actionId: ActionId.fromItemId(22851), value: Flask.FlaskOfFortification }; +export const FlaskOfChromaticWonder = { actionId: ActionId.fromItemId(33208), value: Flask.FlaskOfChromaticWonder }; + +export const FLASKS_CONFIG = [ + { config: FlaskOfTheFrostWyrm, stats: [Stat.StatSpellPower] }, + { config: FlaskOfEndlessRage, stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower] }, + { config: FlaskOfPureMojo, stats: [Stat.StatMP5] }, + { config: FlaskOfStoneblood, stats: [Stat.StatStamina] }, + { config: LesserFlaskOfToughness, stats: [Stat.StatResilience] }, + { config: LesserFlaskOfResistance, stats: [Stat.StatArcaneResistance, Stat.StatFireResistance, Stat.StatFrostResistance, Stat.StatNatureResistance, Stat.StatShadowResistance] }, +] as ConsumableStatOption[]; + +export const makeFlasksInput = makeConsumeInputFactory({ + consumesFieldName: 'flask', + onSet: (eventID: EventID, player: Player, newValue: Flask) => { + if (newValue) { + const newConsumes = player.getConsumes(); + newConsumes.battleElixir = BattleElixir.BattleElixirUnknown; + newConsumes.guardianElixir = GuardianElixir.GuardianElixirUnknown; + player.setConsumes(eventID, newConsumes); + } + } +}); + +// Battle Elixirs +export const ElixirOfAccuracy = { actionId: ActionId.fromItemId(44325), value: BattleElixir.ElixirOfAccuracy }; +export const ElixirOfArmorPiercing = { actionId: ActionId.fromItemId(44330), value: BattleElixir.ElixirOfArmorPiercing }; +export const ElixirOfDeadlyStrikes = { actionId: ActionId.fromItemId(44327), value: BattleElixir.ElixirOfDeadlyStrikes }; +export const ElixirOfExpertise = { actionId: ActionId.fromItemId(44329), value: BattleElixir.ElixirOfExpertise }; +export const ElixirOfLightningSpeed = { actionId: ActionId.fromItemId(44331), value: BattleElixir.ElixirOfLightningSpeed }; +export const ElixirOfMightyAgility = { actionId: ActionId.fromItemId(39666), value: BattleElixir.ElixirOfMightyAgility }; +export const ElixirOfMightyStrength = { actionId: ActionId.fromItemId(40073), value: BattleElixir.ElixirOfMightyStrength }; +export const GurusElixir = { actionId: ActionId.fromItemId(40076), value: BattleElixir.GurusElixir }; +export const SpellpowerElixir = { actionId: ActionId.fromItemId(40070), value: BattleElixir.SpellpowerElixir }; +export const WrathElixir = { actionId: ActionId.fromItemId(40068), value: BattleElixir.WrathElixir }; +export const AdeptsElixir = { actionId: ActionId.fromItemId(28103), value: BattleElixir.AdeptsElixir }; +export const ElixirOfDemonslaying = { actionId: ActionId.fromItemId(9224), value: BattleElixir.ElixirOfDemonslaying }; +export const ElixirOfMajorAgility = { actionId: ActionId.fromItemId(22831), value: BattleElixir.ElixirOfMajorAgility }; +export const ElixirOfMajorFirePower = { actionId: ActionId.fromItemId(22833), value: BattleElixir.ElixirOfMajorFirePower }; +export const ElixirOfMajorFrostPower = { actionId: ActionId.fromItemId(22827), value: BattleElixir.ElixirOfMajorFrostPower }; +export const ElixirOfMajorShadowPower = { actionId: ActionId.fromItemId(22835), value: BattleElixir.ElixirOfMajorShadowPower }; +export const ElixirOfMajorStrength = { actionId: ActionId.fromItemId(22824), value: BattleElixir.ElixirOfMajorStrength }; +export const ElixirOfMastery = { actionId: ActionId.fromItemId(28104), value: BattleElixir.ElixirOfMastery }; +export const ElixirOfTheMongoose = { actionId: ActionId.fromItemId(13452), value: BattleElixir.ElixirOfTheMongoose }; +export const FelStrengthElixir = { actionId: ActionId.fromItemId(31679), value: BattleElixir.FelStrengthElixir }; +export const GreaterArcaneElixir = { actionId: ActionId.fromItemId(13454), value: BattleElixir.GreaterArcaneElixir }; + +export const BATTLE_ELIXIRS_CONFIG = [ + { config: ElixirOfAccuracy, stats: [Stat.StatMeleeHit, Stat.StatSpellHit] }, + { config: ElixirOfArmorPiercing, stats: [Stat.StatArmorPenetration] }, + { config: ElixirOfDeadlyStrikes, stats: [Stat.StatMeleeCrit, Stat.StatSpellCrit] }, + { config: ElixirOfExpertise, stats: [Stat.StatExpertise] }, + { config: ElixirOfLightningSpeed, stats: [Stat.StatMeleeHaste, Stat.StatSpellHaste] }, + { config: ElixirOfMightyAgility, stats: [Stat.StatAgility] }, + { config: ElixirOfMightyStrength, stats: [Stat.StatStrength] }, + { config: GurusElixir, stats: [Stat.StatStamina, Stat.StatAgility, Stat.StatStrength, Stat.StatSpirit, Stat.StatIntellect] }, + { config: SpellpowerElixir, stats: [Stat.StatSpellPower] }, + { config: WrathElixir, stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower] }, +] as ConsumableStatOption[]; + +export const makeBattleElixirsInput = makeConsumeInputFactory({ + consumesFieldName: 'battleElixir', + onSet: (eventID: EventID, player: Player, newValue: BattleElixir) => { + if (newValue) { + const newConsumes = player.getConsumes(); + newConsumes.flask = Flask.FlaskUnknown; + player.setConsumes(eventID, newConsumes); + } + } +}); + +// Guardian Elixirs +export const ElixirOfMightyDefense = { actionId: ActionId.fromItemId(44328), value: GuardianElixir.ElixirOfMightyDefense }; +export const ElixirOfMightyFortitude = { actionId: ActionId.fromItemId(40078), value: GuardianElixir.ElixirOfMightyFortitude }; +export const ElixirOfMightyMageblood = { actionId: ActionId.fromItemId(40109), value: GuardianElixir.ElixirOfMightyMageblood }; +export const ElixirOfMightyThoughts = { actionId: ActionId.fromItemId(44332), value: GuardianElixir.ElixirOfMightyThoughts }; +export const ElixirOfProtection = { actionId: ActionId.fromItemId(40097), value: GuardianElixir.ElixirOfProtection }; +export const ElixirOfSpirit = { actionId: ActionId.fromItemId(40072), value: GuardianElixir.ElixirOfSpirit }; +export const GiftOfArthas = { actionId: ActionId.fromItemId(9088), value: GuardianElixir.GiftOfArthas }; +export const ElixirOfDraenicWisdom = { actionId: ActionId.fromItemId(32067), value: GuardianElixir.ElixirOfDraenicWisdom }; +export const ElixirOfIronskin = { actionId: ActionId.fromItemId(32068), value: GuardianElixir.ElixirOfIronskin }; +export const ElixirOfMajorDefense = { actionId: ActionId.fromItemId(22834), value: GuardianElixir.ElixirOfMajorDefense }; +export const ElixirOfMajorFortitude = { actionId: ActionId.fromItemId(32062), value: GuardianElixir.ElixirOfMajorFortitude }; +export const ElixirOfMajorMageblood = { actionId: ActionId.fromItemId(22840), value: GuardianElixir.ElixirOfMajorMageblood }; + +export const GUARDIAN_ELIXIRS_CONFIG = [ + { config: ElixirOfMightyDefense, stats: [Stat.StatDefense] }, + { config: ElixirOfMightyFortitude, stats: [Stat.StatStamina] }, + { config: ElixirOfMightyMageblood, stats: [Stat.StatMP5] }, + { config: ElixirOfMightyThoughts, stats: [Stat.StatIntellect] }, + { config: ElixirOfProtection, stats: [Stat.StatArmor] }, + { config: ElixirOfSpirit, stats: [Stat.StatSpirit] }, + { config: GiftOfArthas, stats: [Stat.StatStamina] }, +] as ConsumableStatOption[]; + +export const makeGuardianElixirsInput = makeConsumeInputFactory({ + consumesFieldName: 'guardianElixir', + onSet: (eventID: EventID, player: Player, newValue: GuardianElixir) => { + if (newValue) { + const newConsumes = player.getConsumes(); + newConsumes.flask = Flask.FlaskUnknown; + player.setConsumes(eventID, newConsumes); + } + } +}); + +/////////////////////////////////////////////////////////////////////////// +// FOOD +/////////////////////////////////////////////////////////////////////////// + +export const FoodFishFeast = { actionId: ActionId.fromItemId(43015), value: Food.FoodFishFeast }; +export const FoodGreatFeast = { actionId: ActionId.fromItemId(34753), value: Food.FoodGreatFeast }; +export const FoodBlackenedDragonfin = { actionId: ActionId.fromItemId(42999), value: Food.FoodBlackenedDragonfin }; +export const FoodHeartyRhino = { actionId: ActionId.fromItemId(42995), value: Food.FoodHeartyRhino }; +export const FoodMegaMammothMeal = { actionId: ActionId.fromItemId(34754), value: Food.FoodMegaMammothMeal }; +export const FoodSpicedWormBurger = { actionId: ActionId.fromItemId(34756), value: Food.FoodSpicedWormBurger }; +export const FoodRhinoliciousWormsteak = { actionId: ActionId.fromItemId(42994), value: Food.FoodRhinoliciousWormsteak }; +export const FoodImperialMantaSteak = { actionId: ActionId.fromItemId(34769), value: Food.FoodImperialMantaSteak }; +export const FoodSnapperExtreme = { actionId: ActionId.fromItemId(42996), value: Food.FoodSnapperExtreme }; +export const FoodMightyRhinoDogs = { actionId: ActionId.fromItemId(34758), value: Food.FoodMightyRhinoDogs }; +export const FoodFirecrackerSalmon = { actionId: ActionId.fromItemId(34767), value: Food.FoodFirecrackerSalmon }; +export const FoodCuttlesteak = { actionId: ActionId.fromItemId(42998), value: Food.FoodCuttlesteak }; +export const FoodDragonfinFilet = { actionId: ActionId.fromItemId(43000), value: Food.FoodDragonfinFilet }; + +export const FoodBlackenedBasilisk = { actionId: ActionId.fromItemId(27657), value: Food.FoodBlackenedBasilisk }; +export const FoodGrilledMudfish = { actionId: ActionId.fromItemId(27664), value: Food.FoodGrilledMudfish }; +export const FoodRavagerDog = { actionId: ActionId.fromItemId(27655), value: Food.FoodRavagerDog }; +export const FoodRoastedClefthoof = { actionId: ActionId.fromItemId(27658), value: Food.FoodRoastedClefthoof }; +export const FoodSpicyHotTalbuk = { actionId: ActionId.fromItemId(33872), value: Food.FoodSpicyHotTalbuk }; +export const FoodSkullfishSoup = { actionId: ActionId.fromItemId(33825), value: Food.FoodSkullfishSoup }; +export const FoodFishermansFeast = { actionId: ActionId.fromItemId(33052), value: Food.FoodFishermansFeast }; + +export const FOOD_CONFIG = [ + { config: FoodFishFeast, stats: [Stat.StatStamina, Stat.StatAttackPower, Stat.StatRangedAttackPower, Stat.StatSpellPower] }, + { config: FoodGreatFeast, stats: [Stat.StatStamina, Stat.StatAttackPower, Stat.StatRangedAttackPower, Stat.StatSpellPower] }, + { config: FoodBlackenedDragonfin, stats: [Stat.StatAgility] }, + { config: FoodDragonfinFilet, stats: [Stat.StatStrength] }, + { config: FoodCuttlesteak, stats: [Stat.StatSpirit] }, + { config: FoodMegaMammothMeal, stats: [Stat.StatAttackPower, Stat.StatRangedAttackPower] }, + { config: FoodHeartyRhino, stats: [Stat.StatArmorPenetration] }, + { config: FoodRhinoliciousWormsteak, stats: [Stat.StatExpertise] }, + { config: FoodFirecrackerSalmon, stats: [Stat.StatSpellPower] }, + { config: FoodSnapperExtreme, stats: [Stat.StatMeleeHit, Stat.StatSpellHit] }, + { config: FoodSpicedWormBurger, stats: [Stat.StatMeleeCrit, Stat.StatSpellCrit] }, + { config: FoodImperialMantaSteak, stats: [Stat.StatMeleeHaste, Stat.StatSpellHaste] }, + { config: FoodMightyRhinoDogs, stats: [Stat.StatMP5] }, +] as ConsumableStatOption[]; + +export const makeFoodInput = makeConsumeInputFactory({consumesFieldName: 'food'}); + +/////////////////////////////////////////////////////////////////////////// +// PET +/////////////////////////////////////////////////////////////////////////// + +export const SpicedMammothTreats = makeBooleanConsumeInput({actionId: ActionId.fromItemId(43005), fieldName: 'petFood', value: PetFood.PetFoodSpicedMammothTreats}); +export const PetScrollOfAgilityV = makeBooleanConsumeInput({actionId: ActionId.fromItemId(27498), fieldName: 'petScrollOfAgility', value: 5}); +export const PetScrollOfStrengthV = makeBooleanConsumeInput({actionId: ActionId.fromItemId(27503), fieldName: 'petScrollOfStrength', value: 5}); + +/////////////////////////////////////////////////////////////////////////// +// POTIONS +/////////////////////////////////////////////////////////////////////////// + +export const RunicHealingPotion = { actionId: ActionId.fromItemId(33447), value: Potions.RunicHealingPotion }; +export const RunicHealingInjector = { actionId: ActionId.fromItemId(41166), value: Potions.RunicHealingInjector }; +export const RunicManaPotion = { actionId: ActionId.fromItemId(33448), value: Potions.RunicManaPotion }; +export const RunicManaInjector = { actionId: ActionId.fromItemId(42545), value: Potions.RunicManaInjector }; +export const IndestructiblePotion = { actionId: ActionId.fromItemId(40093), value: Potions.IndestructiblePotion }; +export const PotionOfSpeed = { actionId: ActionId.fromItemId(40211), value: Potions.PotionOfSpeed }; +export const PotionOfWildMagic = { actionId: ActionId.fromItemId(40212), value: Potions.PotionOfWildMagic }; + +export const DestructionPotion = { actionId: ActionId.fromItemId(22839), value: Potions.DestructionPotion }; +export const HastePotion = { actionId: ActionId.fromItemId(22838), value: Potions.HastePotion }; +export const MightyRagePotion = { actionId: ActionId.fromItemId(13442), value: Potions.MightyRagePotion }; +export const SuperManaPotion = { actionId: ActionId.fromItemId(22832), value: Potions.SuperManaPotion }; +export const FelManaPotion = { actionId: ActionId.fromItemId(31677), value: Potions.FelManaPotion }; +export const InsaneStrengthPotion = { actionId: ActionId.fromItemId(22828), value: Potions.InsaneStrengthPotion }; +export const IronshieldPotion = { actionId: ActionId.fromItemId(22849), value: Potions.IronshieldPotion }; +export const HeroicPotion = { actionId: ActionId.fromItemId(22837), value: Potions.HeroicPotion }; + +export const POTIONS_CONFIG = [ + { config: RunicHealingPotion, stats: [Stat.StatStamina] }, + { config: RunicHealingInjector, stats: [Stat.StatStamina] }, + { config: RunicManaPotion, stats: [Stat.StatIntellect] }, + { config: RunicManaInjector, stats: [Stat.StatIntellect] }, + { config: IndestructiblePotion, stats: [Stat.StatArmor] }, + { config: InsaneStrengthPotion, stats: [Stat.StatStrength] }, + { config: HeroicPotion, stats: [Stat.StatStamina] }, + { config: PotionOfSpeed, stats: [Stat.StatMeleeHaste, Stat.StatSpellHaste] }, + { config: PotionOfWildMagic, stats: [Stat.StatMeleeCrit, Stat.StatSpellCrit, Stat.StatSpellPower] }, +] as ConsumableStatOption[]; + +export const PRE_POTIONS_CONFIG = [ + { config: IndestructiblePotion, stats: [Stat.StatArmor] }, + { config: InsaneStrengthPotion, stats: [Stat.StatStrength] }, + { config: HeroicPotion, stats: [Stat.StatStamina] }, + { config: PotionOfSpeed, stats: [Stat.StatMeleeHaste, Stat.StatSpellHaste] }, + { config: PotionOfWildMagic, stats: [Stat.StatMeleeCrit, Stat.StatSpellCrit, Stat.StatSpellPower] }, +] as ConsumableStatOption[]; + +export const makePotionsInput = makeConsumeInputFactory({consumesFieldName: 'defaultPotion'}); +export const makePrepopPotionsInput = makeConsumeInputFactory({consumesFieldName: 'prepopPotion'}); + diff --git a/ui/core/components/inputs/stat_options.ts b/ui/core/components/inputs/stat_options.ts new file mode 100644 index 0000000000..1b8448d934 --- /dev/null +++ b/ui/core/components/inputs/stat_options.ts @@ -0,0 +1,60 @@ +import { IndividualSimUI } from "../../individual_sim_ui"; +import { Player } from "../../player"; +import { Faction, Spec, Stat } from "../../proto/common"; +import { ActionId } from "../../proto_utils/action_id"; + +import { IconEnumPicker, IconEnumPickerConfig } from "../icon_enum_picker"; +import { IconPicker, IconPickerConfig } from "../icon_picker"; +import { MultiIconPicker, MultiIconPickerConfig } from "../multi_icon_picker"; + +export interface ActionInputConfig { + actionId: ActionId + value: T + faction?: Faction + showWhen?: (player: Player) => boolean +} + +export interface StatOption { + stats: Array, +} + +export interface ItemStatOption extends StatOption { + config: ActionInputConfig, +} + +export interface PickerStatOption extends StatOption { + config: ConfigType, + picker: PickerType, +} + +export interface IconPickerStatOption extends PickerStatOption< + typeof IconPicker, any>, + IconPickerConfig, any> +> {} + +export interface MultiIconPickerStatOption extends PickerStatOption< + typeof MultiIconPicker>, + MultiIconPickerConfig> +> {} + +export interface IconEnumPickerStatOption extends PickerStatOption< + typeof IconEnumPicker, any>, + IconEnumPickerConfig, any> +> {} + +export type ItemStatOptions = ItemStatOption +export type PickerStatOptions = IconPickerStatOption | MultiIconPickerStatOption | IconEnumPickerStatOption +export type StatOptions | PickerStatOptions> = Array + +export function relevantStatOptions | PickerStatOptions>( + options: StatOptions, + simUI: IndividualSimUI +): StatOptions { + return options + .filter(option => + option.stats.length == 0 || + option.stats.some(stat => simUI.individualConfig.epStats.includes(stat)) || + simUI.individualConfig.includeBuffDebuffInputs.includes(option.config)) + .filter(option => + !simUI.individualConfig.excludeBuffDebuffInputs.includes(option.config)) +} diff --git a/ui/core/components/multi_icon_picker.ts b/ui/core/components/multi_icon_picker.ts index ffc7eb1251..6c95f65183 100644 --- a/ui/core/components/multi_icon_picker.ts +++ b/ui/core/components/multi_icon_picker.ts @@ -1,20 +1,20 @@ +import { Player } from '../player.js'; import { ActionId } from '../proto_utils/action_id.js'; -import { EventID, TypedEvent } from '../typed_event.js'; import { SimUI } from '../sim_ui.js'; +import { TypedEvent } from '../typed_event.js'; import { isRightClick } from '../utils.js'; import { Component } from './component.js'; import { IconPicker, IconPickerConfig } from './icon_picker.js'; -import { Input, InputConfig } from './input.js'; export interface MultiIconPickerItemConfig extends IconPickerConfig { } export interface MultiIconPickerConfig { inputs: Array>, - numColumns: number, label?: string, categoryId?: ActionId, + showWhen?: (obj: Player) => boolean, } // Icon-based UI for a dropdown with multiple icon pickers. @@ -24,7 +24,6 @@ export class MultiIconPicker extends Component { private currentValue: ActionId | null; - private readonly dropdownRootElem: HTMLElement; private readonly buttonElem: HTMLAnchorElement; private readonly dropdownMenu: HTMLElement; @@ -50,7 +49,6 @@ export class MultiIconPicker extends Component { `; - this.dropdownRootElem = this.rootElem.querySelector('.dropdown') as HTMLElement; const labelElem = this.rootElem.querySelector('.multi-icon-picker-label') as HTMLElement; if (config.label) { @@ -81,7 +79,7 @@ export class MultiIconPicker extends Component { this.buildBlankOption(); - this.pickers = config.inputs.map((pickerConfig, i) => { + this.pickers = config.inputs.map((pickerConfig, _i) => { const optionContainer = document.createElement('li'); optionContainer.classList.add('icon-picker-option', 'dropdown-option'); this.dropdownMenu.appendChild(optionContainer); @@ -90,6 +88,14 @@ export class MultiIconPicker extends Component { }); simUI.sim.waitForInit().then(() => this.updateButtonImage()); simUI.changeEmitter.on(() => this.updateButtonImage()); + simUI.changeEmitter.on(() => { + const show = !this.config.showWhen || this.config.showWhen(simUI.sim.raid.getPlayer(0)!); + if (show) { + this.rootElem.classList.remove('hide'); + } else { + this.rootElem.classList.add('hide'); + } + }) } private buildBlankOption() { diff --git a/ui/core/individual_sim_ui.ts b/ui/core/individual_sim_ui.ts index 32c1ed0fe9..11cd1d43f5 100644 --- a/ui/core/individual_sim_ui.ts +++ b/ui/core/individual_sim_ui.ts @@ -574,21 +574,4 @@ export abstract class IndividualSimUI extends SimUI { } }); } - - splitRelevantOptions(options: Array | null>): Array { - return options - .filter(option => option != null) - .filter(option => - this.individualConfig.includeBuffDebuffInputs.includes(option!.item) || - option!.stats.length == 0 || - option!.stats.some(stat => this.individualConfig.epStats.includes(stat))) - .filter(option => - !this.individualConfig.excludeBuffDebuffInputs.includes(option!.item)) - .map(option => option!.item); - } -} - -export interface StatOption { - stats: Array, - item: T, } diff --git a/ui/deathknight/sim.ts b/ui/deathknight/sim.ts index ee214815e2..f7ff25b285 100644 --- a/ui/deathknight/sim.ts +++ b/ui/deathknight/sim.ts @@ -19,7 +19,8 @@ import { Stats } from '../core/proto_utils/stats.js'; import { getSpecIcon } from '../core/proto_utils/utils.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; -import * as IconInputs from '../core/components/icon_inputs.js'; +import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs.js'; +import * as ConsumablesInputs from '../core/components/inputs/consumables.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as DeathKnightInputs from './inputs.js'; @@ -167,17 +168,17 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecDeathknight, { playerIconInputs: [ ], petConsumeInputs: [ - IconInputs.SpicedMammothTreats, + ConsumablesInputs.SpicedMammothTreats, ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ - IconInputs.SpellDamageDebuff, - IconInputs.StaminaBuff, + BuffDebuffInputs.SpellDamageDebuff, + BuffDebuffInputs.StaminaBuff, ], excludeBuffDebuffInputs: [ - IconInputs.AttackPowerDebuff, - IconInputs.DamageReductionPercentBuff, - IconInputs.MeleeAttackSpeedDebuff, + BuffDebuffInputs.AttackPowerDebuff, + BuffDebuffInputs.DamageReductionPercentBuff, + BuffDebuffInputs.MeleeAttackSpeedDebuff, ], // Inputs to include in the 'Other' section on the settings tab. otherInputs: { diff --git a/ui/elemental_shaman/sim.ts b/ui/elemental_shaman/sim.ts index 46e36502e2..1d58a59cfc 100644 --- a/ui/elemental_shaman/sim.ts +++ b/ui/elemental_shaman/sim.ts @@ -19,6 +19,7 @@ import { getSpecIcon, specNames } from '../core/proto_utils/utils.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; import { TypedEvent } from '../core/typed_event.js'; import { TotemsSection } from '../core/components/totem_inputs.js'; + import * as OtherInputs from '../core/components/other_inputs.js'; import * as Mechanics from '../core/constants/mechanics.js'; import * as ShamanInputs from './inputs.js'; diff --git a/ui/enhancement_shaman/sim.ts b/ui/enhancement_shaman/sim.ts index f0451ec3bc..81e748b480 100644 --- a/ui/enhancement_shaman/sim.ts +++ b/ui/enhancement_shaman/sim.ts @@ -19,7 +19,8 @@ import { Stats } from '../core/proto_utils/stats.js'; import { getSpecIcon, specNames } from '../core/proto_utils/utils.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; import { TotemsSection } from '../core/components/totem_inputs.js'; -import * as IconInputs from '../core/components/icon_inputs.js'; + +import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as ShamanInputs from './inputs.js'; @@ -123,13 +124,13 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecEnhancementShaman, { ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ - IconInputs.ReplenishmentBuff, - IconInputs.MP5Buff, - IconInputs.SpellHasteBuff, - IconInputs.SpiritBuff, + BuffDebuffInputs.ReplenishmentBuff, + BuffDebuffInputs.MP5Buff, + BuffDebuffInputs.SpellHasteBuff, + BuffDebuffInputs.SpiritBuff, ], excludeBuffDebuffInputs: [ - IconInputs.BleedDebuff, + BuffDebuffInputs.BleedDebuff, ], // Inputs to include in the 'Other' section on the settings tab. otherInputs: { diff --git a/ui/feral_druid/sim.ts b/ui/feral_druid/sim.ts index b6643b34a0..8212270e46 100644 --- a/ui/feral_druid/sim.ts +++ b/ui/feral_druid/sim.ts @@ -23,7 +23,7 @@ import { FeralDruid_Rotation as DruidRotation, } from '../core/proto/druid.js'; -import * as IconInputs from '../core/components/icon_inputs.js'; +import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as AplUtils from '../core/proto_utils/apl_utils.js'; @@ -136,9 +136,9 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecFeralDruid, { rotationInputs: DruidInputs.FeralDruidRotationConfig, // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ - IconInputs.IntellectBuff, - IconInputs.MP5Buff, - IconInputs.JudgementOfWisdom, + BuffDebuffInputs.IntellectBuff, + BuffDebuffInputs.MP5Buff, + BuffDebuffInputs.JudgementOfWisdom, ], excludeBuffDebuffInputs: [ ], diff --git a/ui/feral_tank_druid/sim.ts b/ui/feral_tank_druid/sim.ts index 8197bfee6d..f10ba30542 100644 --- a/ui/feral_tank_druid/sim.ts +++ b/ui/feral_tank_druid/sim.ts @@ -27,7 +27,7 @@ import { FeralTankDruid_Rotation as DruidRotation, } from '../core/proto/druid.js'; -import * as IconInputs from '../core/components/icon_inputs.js'; +import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as AplUtils from '../core/proto_utils/apl_utils.js'; @@ -156,11 +156,11 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecFeralTankDruid, { rotationInputs: DruidInputs.FeralTankDruidRotationConfig, // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ - IconInputs.HealthBuff, - IconInputs.SpellCritBuff, - IconInputs.SpellCritDebuff, - IconInputs.SpellHitDebuff, - IconInputs.SpellDamageDebuff, + BuffDebuffInputs.HealthBuff, + BuffDebuffInputs.SpellCritBuff, + BuffDebuffInputs.SpellCritDebuff, + BuffDebuffInputs.SpellHitDebuff, + BuffDebuffInputs.SpellDamageDebuff, ], excludeBuffDebuffInputs: [ ], diff --git a/ui/hunter/sim.ts b/ui/hunter/sim.ts index 5d68f55091..a4c13f97be 100644 --- a/ui/hunter/sim.ts +++ b/ui/hunter/sim.ts @@ -36,7 +36,8 @@ import { Hunter_Rotation_RotationType, } from '../core/proto/hunter.js'; -import * as IconInputs from '../core/components/icon_inputs.js'; +import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs.js'; +import * as ConsumablesInputs from '../core/components/inputs/consumables.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as Mechanics from '../core/constants/mechanics.js'; import * as AplUtils from '../core/proto_utils/apl_utils.js'; @@ -216,12 +217,12 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecHunter, { // Inputs to include in the 'Rotation' section on the settings tab. rotationInputs: HunterInputs.HunterRotationConfig, petConsumeInputs: [ - IconInputs.SpicedMammothTreats, + ConsumablesInputs.SpicedMammothTreats, ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ - IconInputs.StaminaBuff, - IconInputs.SpellDamageDebuff, + BuffDebuffInputs.StaminaBuff, + BuffDebuffInputs.SpellDamageDebuff, ], excludeBuffDebuffInputs: [ ], diff --git a/ui/protection_paladin/sim.ts b/ui/protection_paladin/sim.ts index 11b8ae3b70..564458e89a 100644 --- a/ui/protection_paladin/sim.ts +++ b/ui/protection_paladin/sim.ts @@ -23,7 +23,7 @@ import { getSpecIcon } from '../core/proto_utils/utils.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; import { TypedEvent } from '../core/typed_event.js'; -import * as IconInputs from '../core/components/icon_inputs.js'; +import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as Mechanics from '../core/constants/mechanics.js'; import * as AplUtils from '../core/proto_utils/apl_utils.js'; @@ -191,7 +191,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecProtectionPaladin, { ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ - IconInputs.HealthBuff, + BuffDebuffInputs.HealthBuff, ], excludeBuffDebuffInputs: [ ], diff --git a/ui/protection_warrior/sim.ts b/ui/protection_warrior/sim.ts index bd0251091f..3fa53d553f 100644 --- a/ui/protection_warrior/sim.ts +++ b/ui/protection_warrior/sim.ts @@ -26,7 +26,7 @@ import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.j import { ProtectionWarrior_Rotation as ProtectionWarriorRotation } from '../core/proto/warrior.js'; -import * as IconInputs from '../core/components/icon_inputs.js'; +import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as AplUtils from '../core/proto_utils/apl_utils.js'; @@ -167,7 +167,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecProtectionWarrior, { ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ - IconInputs.HealthBuff, + BuffDebuffInputs.HealthBuff, ], excludeBuffDebuffInputs: [ ], diff --git a/ui/raid/settings_tab.ts b/ui/raid/settings_tab.ts index 88f3510c72..72be05296e 100644 --- a/ui/raid/settings_tab.ts +++ b/ui/raid/settings_tab.ts @@ -149,11 +149,11 @@ export class SettingsTab extends SimTab { }); } - private makeBooleanRaidIconBuffInput(parent: HTMLElement, id: ActionId, field: keyof RaidBuffs): IconPicker { + private makeBooleanRaidIconBuffInput(parent: HTMLElement, actionId: ActionId, field: keyof RaidBuffs): IconPicker { const raid = this.simUI.sim.raid; return new IconPicker(parent, raid, { - id: id, + actionId: actionId, states: 2, changedEvent: (raid: Raid) => raid.buffsChangeEmitter, getValue: (raid: Raid) => raid.getBuffs()[field] as unknown as boolean, diff --git a/ui/retribution_paladin/sim.ts b/ui/retribution_paladin/sim.ts index 1959bc5061..e141372fdf 100644 --- a/ui/retribution_paladin/sim.ts +++ b/ui/retribution_paladin/sim.ts @@ -19,7 +19,7 @@ import { getSpecIcon } from '../core/proto_utils/utils.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; import { TypedEvent } from '../core/typed_event.js'; -import * as IconInputs from '../core/components/icon_inputs.js'; +import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as Mechanics from '../core/constants/mechanics.js'; @@ -167,7 +167,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecRetributionPaladin, { ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ - IconInputs.ReplenishmentBuff, + BuffDebuffInputs.ReplenishmentBuff, ], excludeBuffDebuffInputs: [ ], diff --git a/ui/rogue/sim.ts b/ui/rogue/sim.ts index c23d324704..171a1f95b1 100644 --- a/ui/rogue/sim.ts +++ b/ui/rogue/sim.ts @@ -25,7 +25,7 @@ import { Rogue_Options_PoisonImbue, } from '../core/proto/rogue.js'; -import * as IconInputs from '../core/components/icon_inputs.js'; +import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as RogueInputs from './inputs.js'; @@ -253,10 +253,10 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecRogue, { ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ - IconInputs.SpellCritBuff, - IconInputs.SpellCritDebuff, - IconInputs.SpellHitDebuff, - IconInputs.SpellDamageDebuff + BuffDebuffInputs.SpellCritBuff, + BuffDebuffInputs.SpellCritDebuff, + BuffDebuffInputs.SpellHitDebuff, + BuffDebuffInputs.SpellDamageDebuff ], excludeBuffDebuffInputs: [ ], diff --git a/ui/scss/core/components/_icon_picker.scss b/ui/scss/core/components/_icon_picker.scss index ef9c1dfbae..b2d2a00d9c 100644 --- a/ui/scss/core/components/_icon_picker.scss +++ b/ui/scss/core/components/_icon_picker.scss @@ -67,7 +67,7 @@ } label { - margin-left: map.get($spacers, 1); + margin-left: map.get($spacers, 2); } .dropdown-menu { diff --git a/ui/shadow_priest/sim.ts b/ui/shadow_priest/sim.ts index 2c496d09d1..87b3f42b6f 100644 --- a/ui/shadow_priest/sim.ts +++ b/ui/shadow_priest/sim.ts @@ -14,7 +14,8 @@ import { Stats } from '../core/proto_utils/stats.js'; import { Player } from '../core/player.js'; import { getSpecIcon, specNames } from '../core/proto_utils/utils.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; -import * as IconInputs from '../core/components/icon_inputs.js'; + +import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as Mechanics from '../core/constants/mechanics.js'; @@ -99,13 +100,13 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecShadowPriest, { ], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ - IconInputs.ReplenishmentBuff, - IconInputs.MeleeHasteBuff, - IconInputs.MeleeCritBuff, - IconInputs.MP5Buff, - IconInputs.AttackPowerPercentBuff, - IconInputs.AttackPowerBuff, - IconInputs.StaminaBuff, + BuffDebuffInputs.ReplenishmentBuff, + BuffDebuffInputs.MeleeHasteBuff, + BuffDebuffInputs.MeleeCritBuff, + BuffDebuffInputs.MP5Buff, + BuffDebuffInputs.AttackPowerPercentBuff, + BuffDebuffInputs.AttackPowerBuff, + BuffDebuffInputs.StaminaBuff, ], excludeBuffDebuffInputs: [ ], diff --git a/ui/tank_deathknight/sim.ts b/ui/tank_deathknight/sim.ts index 2002043f1c..7575a6b0a9 100644 --- a/ui/tank_deathknight/sim.ts +++ b/ui/tank_deathknight/sim.ts @@ -19,7 +19,7 @@ import { Stats } from '../core/proto_utils/stats.js'; import { getSpecIcon } from '../core/proto_utils/utils.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; -import * as IconInputs from '../core/components/icon_inputs.js'; +import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as DeathKnightInputs from './inputs.js'; @@ -164,7 +164,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecTankDeathknight, { rotationInputs: DeathKnightInputs.TankDeathKnightRotationConfig, // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ - IconInputs.SpellDamageDebuff, + BuffDebuffInputs.SpellDamageDebuff, ], excludeBuffDebuffInputs: [ ], diff --git a/ui/warlock/sim.ts b/ui/warlock/sim.ts index e40c13932e..cddb9a105a 100644 --- a/ui/warlock/sim.ts +++ b/ui/warlock/sim.ts @@ -15,7 +15,9 @@ import { Stats } from '../core/proto_utils/stats.js'; import { Player } from '../core/player.js'; import { getSpecIcon } from '../core/proto_utils/utils.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; -import * as IconInputs from '../core/components/icon_inputs.js'; + +import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs.js'; +import * as ConsumablesInputs from '../core/components/inputs/consumables.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as WarlockInputs from './inputs.js'; import * as Presets from './presets.js'; @@ -96,22 +98,22 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarlock, { // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ - IconInputs.ReplenishmentBuff, - IconInputs.MajorArmorDebuff, - IconInputs.MinorArmorDebuff, - IconInputs.PhysicalDamageDebuff, - IconInputs.MeleeHasteBuff, - IconInputs.MeleeCritBuff, - IconInputs.MP5Buff, - IconInputs.AttackPowerPercentBuff, - IconInputs.AttackPowerBuff, - IconInputs.StrengthAndAgilityBuff, - IconInputs.StaminaBuff, + BuffDebuffInputs.ReplenishmentBuff, + BuffDebuffInputs.MajorArmorDebuff, + BuffDebuffInputs.MinorArmorDebuff, + BuffDebuffInputs.PhysicalDamageDebuff, + BuffDebuffInputs.MeleeHasteBuff, + BuffDebuffInputs.MeleeCritBuff, + BuffDebuffInputs.MP5Buff, + BuffDebuffInputs.AttackPowerPercentBuff, + BuffDebuffInputs.AttackPowerBuff, + BuffDebuffInputs.StrengthAndAgilityBuff, + BuffDebuffInputs.StaminaBuff, ], excludeBuffDebuffInputs: [ ], petConsumeInputs: [ - IconInputs.SpicedMammothTreats, + ConsumablesInputs.SpicedMammothTreats, ], // Inputs to include in the 'Other' section on the settings tab. otherInputs: { diff --git a/ui/warrior/sim.ts b/ui/warrior/sim.ts index d81b417f74..f005c8d56f 100644 --- a/ui/warrior/sim.ts +++ b/ui/warrior/sim.ts @@ -20,12 +20,10 @@ import { Stats } from '../core/proto_utils/stats.js'; import { Player } from '../core/player.js'; import { getSpecIcon } from '../core/proto_utils/utils.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; -import { TypedEvent } from '../core/typed_event.js'; import { Gear } from '../core/proto_utils/gear.js'; import { PhysicalDPSGemOptimizer } from '../core/components/suggest_gems_action.js'; - -import * as IconInputs from '../core/components/icon_inputs.js'; +import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs.js'; import * as OtherInputs from '../core/components/other_inputs.js'; import * as Mechanics from '../core/constants/mechanics.js'; @@ -146,8 +144,8 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarrior, { // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ // just for Bryntroll - IconInputs.SpellDamageDebuff, - IconInputs.SpellHitDebuff, + BuffDebuffInputs.SpellDamageDebuff, + BuffDebuffInputs.SpellHitDebuff, ], excludeBuffDebuffInputs: [ ],