diff --git a/.prettierrc.js b/.prettierrc.js index 5c2b98fcb9..39a1eb2fc1 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,5 +1,5 @@ /** - * @type {import("prettier").Options} + * @type {import("prettier").Config} */ module.exports = { printWidth: 160, diff --git a/ui/core/components/character_stats.tsx b/ui/core/components/character_stats.tsx index 63b70f20b5..bd74470f05 100644 --- a/ui/core/components/character_stats.tsx +++ b/ui/core/components/character_stats.tsx @@ -1,18 +1,15 @@ -import { Stat, Class, PseudoStat, Spec } from '..//proto/common.js'; -import { TristateEffect } from '..//proto/common.js' +import { Popover, Tooltip } from 'bootstrap'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { element, fragment } from 'tsx-vanilla'; + +import { Player } from '..//player.js'; +import { Class, PseudoStat, Stat, TristateEffect } from '..//proto/common.js'; import { getClassStatName, statOrder } from '..//proto_utils/names.js'; import { Stats } from '..//proto_utils/stats.js'; -import { Player } from '..//player.js'; import { EventID, TypedEvent } from '..//typed_event.js'; - import * as Mechanics from '../constants/mechanics.js'; - -import { NumberPicker } from './number_picker'; import { Component } from './component.js'; - -import { Popover, Tooltip } from 'bootstrap'; - -import { element, fragment } from 'tsx-vanilla'; +import { NumberPicker } from './number_picker'; export type StatMods = { talents: Stats }; @@ -41,17 +38,14 @@ export class CharacterStats extends Component { this.valueElems = []; this.stats.forEach(stat => { - let statName = getClassStatName(stat, player.getClass()); + const statName = getClassStatName(stat, player.getClass()); const row = ( - - {statName} - - {this.bonusStatsLink(stat)} - - ); + + {statName} + {this.bonusStatsLink(stat)} + + ); table.appendChild(row); @@ -59,14 +53,13 @@ export class CharacterStats extends Component { this.valueElems.push(valueElem); }); - if(this.shouldShowMeleeCritCap(player)) { + if (this.shouldShowMeleeCritCap(player)) { const row = ( - - Melee Crit Cap - - ); + + Melee Crit Cap + + + ); table.appendChild(row); this.meleeCritCapValueElem = row.getElementsByClassName('character-stats-table-value')[0] as HTMLTableCellElement; @@ -83,9 +76,11 @@ export class CharacterStats extends Component { private updateStats(player: Player) { const playerStats = player.getCurrentStats(); - const statMods = this.modifyDisplayStats ? this.modifyDisplayStats(this.player) : { - talents: new Stats(), - }; + const statMods = this.modifyDisplayStats + ? this.modifyDisplayStats(this.player) + : { + talents: new Stats(), + }; const baseStats = Stats.fromProto(playerStats.baseStats); const gearStats = Stats.fromProto(playerStats.gearStats); @@ -104,19 +99,16 @@ export class CharacterStats extends Component { const finalStats = Stats.fromProto(playerStats.finalStats).add(statMods.talents).add(debuffStats); this.stats.forEach((stat, idx) => { - let valueElem = ( - + const valueElem = ( + {`${this.statDisplayString(finalStats, finalStats, stat)} `} - ) + ); this.valueElems[idx].querySelector('.stat-value-link')?.remove(); this.valueElems[idx].prepend(valueElem); - let bonusStatValue = bonusStats.getStat(stat); + const bonusStatValue = bonusStats.getStat(stat); if (bonusStatValue == 0) { valueElem.classList.add('text-white'); @@ -126,62 +118,60 @@ export class CharacterStats extends Component { valueElem.classList.add('text-danger'); } - let tooltipContent = -
-
- Base: - {this.statDisplayString(baseStats, baseDelta, stat)} -
-
- Gear: - {this.statDisplayString(gearStats, gearDelta, stat)} -
-
- Talents: - {this.statDisplayString(talentsStats, talentsDelta, stat)} -
-
- Buffs: - {this.statDisplayString(buffsStats, buffsDelta, stat)} -
-
- Consumes: - {this.statDisplayString(consumesStats, consumesDelta, stat)} -
- {debuffStats.getStat(stat) != 0 && -
- Debuffs: - {this.statDisplayString(debuffStats, debuffStats, stat)} -
- } - {bonusStatValue != 0 && -
- Bonus: - {this.statDisplayString(bonusStats, bonusStats, stat)} -
- } -
- Total: - {this.statDisplayString(finalStats, finalStats, stat)} + const tooltipContent = ( +
+
+ Base: + {this.statDisplayString(baseStats, baseDelta, stat)} +
+
+ Gear: + {this.statDisplayString(gearStats, gearDelta, stat)} +
+
+ Talents: + {this.statDisplayString(talentsStats, talentsDelta, stat)} +
+
+ Buffs: + {this.statDisplayString(buffsStats, buffsDelta, stat)} +
+
+ Consumes: + {this.statDisplayString(consumesStats, consumesDelta, stat)} +
+ {debuffStats.getStat(stat) != 0 && ( +
+ Debuffs: + {this.statDisplayString(debuffStats, debuffStats, stat)} +
+ )} + {bonusStatValue != 0 && ( +
+ Bonus: + {this.statDisplayString(bonusStats, bonusStats, stat)} +
+ )} +
+ Total: + {this.statDisplayString(finalStats, finalStats, stat)} +
-
; + ); Tooltip.getOrCreateInstance(valueElem, { title: tooltipContent, html: true, }); }); - if(this.meleeCritCapValueElem) { + if (this.meleeCritCapValueElem) { const meleeCritCapInfo = player.getMeleeCritCapInfo(); const valueElem = ( - + {`${this.meleeCritCapDisplayString(player, finalStats)} `} - ) + ); const capDelta = meleeCritCapInfo.playerCritCapDelta; if (capDelta == 0) { @@ -196,43 +186,43 @@ export class CharacterStats extends Component { this.meleeCritCapValueElem.prepend(valueElem); const tooltipContent = ( -
-
- Glancing: - {`${meleeCritCapInfo.glancing.toFixed(2)}%`} +
+
+ Glancing: + {`${meleeCritCapInfo.glancing.toFixed(2)}%`} +
+
+ Suppression: + {`${meleeCritCapInfo.suppression.toFixed(2)}%`} +
+
+ To Hit Cap: + {`${meleeCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`} +
+
+ To Exp Cap: + {`${meleeCritCapInfo.remainingExpertiseCap.toFixed(2)}%`} +
+
+ Debuffs: + {`${meleeCritCapInfo.debuffCrit.toFixed(2)}%`} +
+ {meleeCritCapInfo.specSpecificOffset != 0 && ( +
+ Spec Offsets: + {`${meleeCritCapInfo.specSpecificOffset.toFixed(2)}%`} +
+ )} +
+ Final Crit Cap: + {`${meleeCritCapInfo.baseCritCap.toFixed(2)}%`} +
+
+
+ Can Raise By: + {`${(meleeCritCapInfo.remainingExpertiseCap + meleeCritCapInfo.remainingMeleeHitCap).toFixed(2)}%`} +
-
- Suppression: - {`${meleeCritCapInfo.suppression.toFixed(2)}%`} -
-
- To Hit Cap: - {`${meleeCritCapInfo.remainingMeleeHitCap.toFixed(2)}%`} -
-
- To Exp Cap: - {`${meleeCritCapInfo.remainingExpertiseCap.toFixed(2)}%`} -
-
- Debuffs: - {`${meleeCritCapInfo.debuffCrit.toFixed(2)}%`} -
- {meleeCritCapInfo.specSpecificOffset != 0 && -
- Spec Offsets: - {`${meleeCritCapInfo.specSpecificOffset.toFixed(2)}%`} -
- } -
- Final Crit Cap: - {`${meleeCritCapInfo.baseCritCap.toFixed(2)}%`} -
-
-
- Can Raise By: - {`${(meleeCritCapInfo.remainingExpertiseCap + meleeCritCapInfo.remainingMeleeHitCap).toFixed(2)}%`} -
-
); Tooltip.getOrCreateInstance(valueElem, { @@ -258,7 +248,7 @@ export class CharacterStats extends Component { } else if (stat == Stat.StatMeleeCrit || stat == Stat.StatSpellCrit) { displayStr += ` (${(rawValue / Mechanics.SPELL_CRIT_RATING_PER_CRIT_CHANCE).toFixed(2)}%)`; } else if (stat == Stat.StatMeleeHaste) { - if ([Class.ClassDruid, Class.ClassShaman, Class.ClassPaladin, Class.ClassDeathknight].includes(this.player.getClass())) { + if ([Class.ClassDruid, Class.ClassShaman, Class.ClassPaladin, Class.ClassDeathKnight].includes(this.player.getClass())) { displayStr += ` (${(rawValue / Mechanics.SPECIAL_MELEE_HASTE_RATING_PER_HASTE_PERCENT).toFixed(2)}%)`; } else { displayStr += ` (${(rawValue / Mechanics.HASTE_RATING_PER_HASTE_PERCENT).toFixed(2)}%)`; @@ -277,7 +267,11 @@ export class CharacterStats extends Component { } else if (stat == Stat.StatBlock) { // TODO: Figure out how to display these differently for the components than the final value //displayStr += ` (${(rawValue / Mechanics.BLOCK_RATING_PER_BLOCK_CHANCE).toFixed(2)}%)`; - displayStr += ` (${((rawValue / Mechanics.BLOCK_RATING_PER_BLOCK_CHANCE) + (Mechanics.MISS_DODGE_PARRY_BLOCK_CRIT_CHANCE_PER_DEFENSE * Math.floor(stats.getStat(Stat.StatDefense) / Mechanics.DEFENSE_RATING_PER_DEFENSE)) + 5.00).toFixed(2)}%)`; + displayStr += ` (${( + rawValue / Mechanics.BLOCK_RATING_PER_BLOCK_CHANCE + + Mechanics.MISS_DODGE_PARRY_BLOCK_CRIT_CHANCE_PER_DEFENSE * Math.floor(stats.getStat(Stat.StatDefense) / Mechanics.DEFENSE_RATING_PER_DEFENSE) + + 5.0 + ).toFixed(2)}%)`; } else if (stat == Stat.StatDodge) { //displayStr += ` (${(rawValue / Mechanics.DODGE_RATING_PER_DODGE_CHANCE).toFixed(2)}%)`; displayStr += ` (${(stats.getPseudoStat(PseudoStat.PseudoStatDodge) * 100).toFixed(2)}%)`; @@ -310,22 +304,17 @@ export class CharacterStats extends Component { } private bonusStatsLink(stat: Stat): HTMLElement { - let statName = getClassStatName(stat, this.player.getClass()); - - let link = ( - + const statName = getClassStatName(stat, this.player.getClass()); + + const link = ( + ); let popover: Popover | null = null; - let picker = new NumberPicker(null, this.player, { + const picker = new NumberPicker(null, this.player, { label: `Bonus ${statName}`, extraCssClasses: ['mb-0'], changedEvent: (player: Player) => player.bonusStatsChangeEmitter, @@ -342,7 +331,7 @@ export class CharacterStats extends Component { placement: 'right', fallbackPlacement: ['left'], sanitize: false, - html:true, + html: true, content: picker.rootElem, }); @@ -350,19 +339,13 @@ export class CharacterStats extends Component { } private shouldShowMeleeCritCap(player: Player): boolean { - return [ - Spec.SpecDeathknight, - Spec.SpecEnhancementShaman, - Spec.SpecRetributionPaladin, - Spec.SpecRogue, - Spec.SpecWarrior - ].includes(player.spec); + return player.getPlayerSpec().isMeleeDpsSpec; } private meleeCritCapDisplayString(player: Player, finalStats: Stats): string { const playerCritCapDelta = player.getMeleeCritCap(); - if(playerCritCapDelta === 0.0) { + if (playerCritCapDelta === 0.0) { return 'Exact'; } diff --git a/ui/core/components/individual_sim_ui/apl_values.ts b/ui/core/components/individual_sim_ui/apl_values.ts index 85cecb6e28..ba8947db3e 100644 --- a/ui/core/components/individual_sim_ui/apl_values.ts +++ b/ui/core/components/individual_sim_ui/apl_values.ts @@ -1,103 +1,92 @@ -import { - Class, - Spec, -} from '../../proto/common.js'; - -import { - ShamanTotems_TotemType as TotemType, -} from '../../proto/shaman.js'; - +import { Player } from '../../player.js'; import { APLValue, APLValueAnd, - APLValueOr, - APLValueNot, + APLValueAuraICDIsReadyWithReactionTime, + APLValueAuraInternalCooldown, + APLValueAuraIsActive, + APLValueAuraIsActiveWithReactionTime, + APLValueAuraNumStacks, + APLValueAuraRemainingTime, + APLValueAuraShouldRefresh, + APLValueAutoTimeToNext, + APLValueBossSpellIsCasting, + APLValueBossSpellTimeToReady, + APLValueCatExcessEnergy, + APLValueCatNewSavageRoarDuration, + APLValueChannelClipDelay, APLValueCompare, APLValueCompare_ComparisonOperator as ComparisonOperator, - APLValueMath, - APLValueMath_MathOperator as MathOperator, - APLValueMax, - APLValueMin, APLValueConst, - APLValueCurrentTime, - APLValueCurrentTimePercent, - APLValueRemainingTime, - APLValueRemainingTimePercent, - APLValueIsExecutePhase, - APLValueIsExecutePhase_ExecutePhaseThreshold as ExecutePhaseThreshold, + APLValueCurrentComboPoints, + APLValueCurrentEnergy, APLValueCurrentHealth, APLValueCurrentHealthPercent, APLValueCurrentMana, APLValueCurrentManaPercent, + APLValueCurrentNonDeathRuneCount, APLValueCurrentRage, - APLValueCurrentEnergy, - APLValueCurrentComboPoints, - APLValueCurrentRunicPower, + APLValueCurrentRuneActive, APLValueCurrentRuneCount, APLValueCurrentRuneDeath, - APLValueCurrentRuneActive, - APLValueCurrentNonDeathRuneCount, - APLValueRuneSlotCooldown, - APLValueRuneGrace, - APLValueRuneSlotGrace, + APLValueCurrentRunicPower, + APLValueCurrentTime, + APLValueCurrentTimePercent, + APLValueDotIsActive, + APLValueDotRemainingTime, + APLValueFrontOfTarget, APLValueGCDIsReady, APLValueGCDTimeToReady, - APLValueAutoTimeToNext, + APLValueIsExecutePhase, + APLValueIsExecutePhase_ExecutePhaseThreshold as ExecutePhaseThreshold, + APLValueMath, + APLValueMath_MathOperator as MathOperator, + APLValueMax, + APLValueMin, + APLValueNextRuneCooldown, + APLValueNot, + APLValueNumberTargets, + APLValueOr, + APLValueRemainingTime, + APLValueRemainingTimePercent, + APLValueRuneCooldown, + APLValueRuneGrace, + APLValueRuneSlotCooldown, + APLValueRuneSlotGrace, + APLValueSequenceIsComplete, + APLValueSequenceIsReady, + APLValueSequenceTimeToReady, APLValueSpellCanCast, - APLValueSpellIsReady, - APLValueSpellTimeToReady, APLValueSpellCastTime, - APLValueSpellTravelTime, - APLValueSpellCPM, - APLValueSpellIsChanneling, APLValueSpellChanneledTicks, + APLValueSpellCPM, APLValueSpellCurrentCost, - APLValueChannelClipDelay, - APLValueFrontOfTarget, - APLValueAuraIsActive, - APLValueAuraIsActiveWithReactionTime, - APLValueAuraRemainingTime, - APLValueAuraNumStacks, - APLValueAuraInternalCooldown, - APLValueAuraICDIsReadyWithReactionTime, - APLValueAuraShouldRefresh, - APLValueDotIsActive, - APLValueDotRemainingTime, - APLValueSequenceIsComplete, - APLValueSequenceIsReady, - APLValueSequenceTimeToReady, - APLValueRuneCooldown, - APLValueNextRuneCooldown, - APLValueNumberTargets, + APLValueSpellIsChanneling, + APLValueSpellIsReady, + APLValueSpellTimeToReady, + APLValueSpellTravelTime, APLValueTotemRemainingTime, - APLValueCatExcessEnergy, APLValueWarlockShouldRecastDrainSoul, APLValueWarlockShouldRefreshCorruption, - APLValueCatNewSavageRoarDuration, - APLValueBossSpellTimeToReady, - APLValueBossSpellIsCasting, } from '../../proto/apl.js'; - +import { Class, Spec } from '../../proto/common.js'; +import { ShamanTotems_TotemType as TotemType } from '../../proto/shaman.js'; import { EventID } from '../../typed_event.js'; -import { Input, InputConfig } from '../input.js'; -import { Player } from '../../player.js'; import { TextDropdownPicker, TextDropdownValueConfig } from '../dropdown_picker.js'; +import { Input, InputConfig } from '../input.js'; import { ListItemPickerConfig, ListPicker } from '../list_picker.js'; - import * as AplHelpers from './apl_helpers.js'; -export interface APLValuePickerConfig extends InputConfig, APLValue | undefined> { -} +export interface APLValuePickerConfig extends InputConfig, APLValue | undefined> {} export type APLValueKind = APLValue['value']['oneofKind']; -export type APLValueImplStruct = Extract; +export type APLValueImplStruct = Extract; type APLValueImplTypesUnion = { [f in NonNullable]: f extends keyof APLValueImplStruct ? APLValueImplStruct[f] : never; }; -export type APLValueImplType = APLValueImplTypesUnion[NonNullable]|undefined; +export type APLValueImplType = APLValueImplTypesUnion[NonNullable] | undefined; export class APLValuePicker extends Input, APLValue | undefined> { - private kindPicker: TextDropdownPicker, APLValueKind>; private currentKind: APLValueKind; @@ -108,23 +97,28 @@ export class APLValuePicker extends Input, APLValue | undefined> { const isPrepull = this.rootElem.closest('.apl-prepull-action-picker') != null; - const allValueKinds = (Object.keys(valueKindFactories) as Array>) - .filter(valueKind => valueKindFactories[valueKind].includeIf?.(player, isPrepull) ?? true); + const allValueKinds = (Object.keys(valueKindFactories) as Array>).filter( + valueKind => valueKindFactories[valueKind].includeIf?.(player, isPrepull) ?? true, + ); this.kindPicker = new TextDropdownPicker(this.rootElem, player, { defaultLabel: 'No Condition', - values: [{ - value: undefined, - label: '', - } as TextDropdownValueConfig].concat(allValueKinds.map(kind => { - const factory = valueKindFactories[kind]; - return { - value: kind, - label: factory.label, - submenu: factory.submenu, - tooltip: factory.fullDescription ? `

${factory.shortDescription}

${factory.fullDescription}` : factory.shortDescription, - }; - })), + values: [ + { + value: undefined, + label: '', + } as TextDropdownValueConfig, + ].concat( + allValueKinds.map(kind => { + const factory = valueKindFactories[kind]; + return { + value: kind, + label: factory.label, + submenu: factory.submenu, + tooltip: factory.fullDescription ? `

${factory.shortDescription}

${factory.fullDescription}` : factory.shortDescription, + }; + }), + ), equals: (a, b) => a == b, changedEvent: (player: Player) => player.rotationChangeEmitter, getValue: (_player: Player) => this.getSourceValue()?.value.oneofKind, @@ -149,7 +143,9 @@ export class APLValuePicker extends Input, APLValue | undefined> { if (sourceValue.value.oneofKind == 'or') { (newSourceValue.value as APLValueImplStruct<'and'>).and.vals = sourceValue.value.or.vals; } else { - (newSourceValue.value as APLValueImplStruct<'and'>).and.vals = [this.makeAPLValue(oldKind, this.valuePicker.getInputValue())]; + (newSourceValue.value as APLValueImplStruct<'and'>).and.vals = [ + this.makeAPLValue(oldKind, this.valuePicker.getInputValue()), + ]; } } else if (newKind == 'or') { if (sourceValue.value.oneofKind == 'and') { @@ -161,13 +157,17 @@ export class APLValuePicker extends Input, APLValue | undefined> { if (sourceValue.value.oneofKind == 'max') { (newSourceValue.value as APLValueImplStruct<'min'>).min.vals = sourceValue.value.max.vals; } else { - (newSourceValue.value as APLValueImplStruct<'min'>).min.vals = [this.makeAPLValue(oldKind, this.valuePicker.getInputValue())]; + (newSourceValue.value as APLValueImplStruct<'min'>).min.vals = [ + this.makeAPLValue(oldKind, this.valuePicker.getInputValue()), + ]; } } else if (newKind == 'max') { if (sourceValue.value.oneofKind == 'min') { (newSourceValue.value as APLValueImplStruct<'max'>).max.vals = sourceValue.value.min.vals; } else { - (newSourceValue.value as APLValueImplStruct<'max'>).max.vals = [this.makeAPLValue(oldKind, this.valuePicker.getInputValue())]; + (newSourceValue.value as APLValueImplStruct<'max'>).max.vals = [ + this.makeAPLValue(oldKind, this.valuePicker.getInputValue()), + ]; } } else if (sourceValue.value.oneofKind == 'and' && sourceValue.value.and.vals?.[0]?.value.oneofKind == newKind) { newSourceValue = sourceValue.value.and.vals[0]; @@ -212,15 +212,15 @@ export class APLValuePicker extends Input, APLValue | undefined> { return APLValue.create({ value: { oneofKind: kind, - ...((() => { + ...(() => { const val: any = {}; if (kind && this.valuePicker) { val[kind] = this.valuePicker.getInputValue(); } return val; - })()), + })(), }, - }) + }); } } @@ -239,7 +239,7 @@ export class APLValuePicker extends Input, APLValue | undefined> { } const obj: any = { oneofKind: kind }; obj[kind] = implVal; - return APLValue.create({value: obj}); + return APLValue.create({ value: obj }); } private updateValuePicker(newKind: APLValueKind) { @@ -279,32 +279,33 @@ export class APLValuePicker extends Input, APLValue | undefined> { } type ValueKindConfig = { - label: string, - submenu?: Array, - shortDescription: string, - fullDescription?: string, - newValue: () => T, - includeIf?: (player: Player, isPrepull: boolean) => boolean, - factory: (parent: HTMLElement, player: Player, config: InputConfig, T>) => Input, T>, + label: string; + submenu?: Array; + shortDescription: string; + fullDescription?: string; + newValue: () => T; + includeIf?: (player: Player, isPrepull: boolean) => boolean; + factory: (parent: HTMLElement, player: Player, config: InputConfig, T>) => Input, T>; }; function comparisonOperatorFieldConfig(field: string): AplHelpers.APLPickerBuilderFieldConfig { return { field: field, newValue: () => ComparisonOperator.OpEq, - factory: (parent, player, config) => new TextDropdownPicker(parent, player, { - ...config, - defaultLabel: 'None', - equals: (a, b) => a == b, - values: [ - { value: ComparisonOperator.OpEq, label: '==' }, - { value: ComparisonOperator.OpNe, label: '!=' }, - { value: ComparisonOperator.OpGe, label: '>=' }, - { value: ComparisonOperator.OpGt, label: '>' }, - { value: ComparisonOperator.OpLe, label: '<=' }, - { value: ComparisonOperator.OpLt, label: '<' }, - ], - }), + factory: (parent, player, config) => + new TextDropdownPicker(parent, player, { + ...config, + defaultLabel: 'None', + equals: (a, b) => a == b, + values: [ + { value: ComparisonOperator.OpEq, label: '==' }, + { value: ComparisonOperator.OpNe, label: '!=' }, + { value: ComparisonOperator.OpGe, label: '>=' }, + { value: ComparisonOperator.OpGt, label: '>' }, + { value: ComparisonOperator.OpLe, label: '<=' }, + { value: ComparisonOperator.OpLt, label: '<' }, + ], + }), }; } @@ -312,17 +313,18 @@ function mathOperatorFieldConfig(field: string): AplHelpers.APLPickerBuilderFiel return { field: field, newValue: () => MathOperator.OpAdd, - factory: (parent, player, config) => new TextDropdownPicker(parent, player, { - ...config, - defaultLabel: 'None', - equals: (a, b) => a == b, - values: [ - { value: MathOperator.OpAdd, label: '+' }, - { value: MathOperator.OpSub, label: '-' }, - { value: MathOperator.OpMul, label: '*' }, - { value: MathOperator.OpDiv, label: '/' }, - ], - }), + factory: (parent, player, config) => + new TextDropdownPicker(parent, player, { + ...config, + defaultLabel: 'None', + equals: (a, b) => a == b, + values: [ + { value: MathOperator.OpAdd, label: '+' }, + { value: MathOperator.OpSub, label: '-' }, + { value: MathOperator.OpMul, label: '*' }, + { value: MathOperator.OpDiv, label: '/' }, + ], + }), }; } @@ -330,16 +332,17 @@ function executePhaseThresholdFieldConfig(field: string): AplHelpers.APLPickerBu return { field: field, newValue: () => ExecutePhaseThreshold.E20, - factory: (parent, player, config) => new TextDropdownPicker(parent, player, { - ...config, - defaultLabel: 'None', - equals: (a, b) => a == b, - values: [ - { value: ExecutePhaseThreshold.E20, label: '20%' }, - { value: ExecutePhaseThreshold.E25, label: '25%' }, - { value: ExecutePhaseThreshold.E35, label: '35%' }, - ], - }), + factory: (parent, player, config) => + new TextDropdownPicker(parent, player, { + ...config, + defaultLabel: 'None', + equals: (a, b) => a == b, + values: [ + { value: ExecutePhaseThreshold.E20, label: '20%' }, + { value: ExecutePhaseThreshold.E25, label: '25%' }, + { value: ExecutePhaseThreshold.E35, label: '35%' }, + ], + }), }; } @@ -347,21 +350,25 @@ function totemTypeFieldConfig(field: string): AplHelpers.APLPickerBuilderFieldCo return { field: field, newValue: () => TotemType.Water, - factory: (parent, player, config) => new TextDropdownPicker(parent, player, { - ...config, - defaultLabel: 'None', - equals: (a, b) => a == b, - values: [ - { value: TotemType.Earth, label: 'Earth' }, - { value: TotemType.Air, label: 'Air' }, - { value: TotemType.Fire, label: 'Fire' }, - { value: TotemType.Water, label: 'Water' }, - ], - }), + factory: (parent, player, config) => + new TextDropdownPicker(parent, player, { + ...config, + defaultLabel: 'None', + equals: (a, b) => a == b, + values: [ + { value: TotemType.Earth, label: 'Earth' }, + { value: TotemType.Air, label: 'Air' }, + { value: TotemType.Fire, label: 'Fire' }, + { value: TotemType.Water, label: 'Water' }, + ], + }), }; } -export function valueFieldConfig(field: string, options?: Partial>): AplHelpers.APLPickerBuilderFieldConfig { +export function valueFieldConfig( + field: string, + options?: Partial>, +): AplHelpers.APLPickerBuilderFieldConfig { return { field: field, newValue: APLValue.create, @@ -374,34 +381,44 @@ export function valueListFieldConfig(field: string): AplHelpers.APLPickerBuilder return { field: field, newValue: () => [], - factory: (parent, player, config) => new ListPicker, APLValue | undefined>(parent, player, { - ...config, - // Override setValue to replace undefined elements with default messages. - setValue: (eventID: EventID, player: Player, newValue: Array) => { - config.setValue(eventID, player, newValue.map(val => val || APLValue.create())); - }, - itemLabel: 'Value', - newItem: APLValue.create, - copyItem: (oldValue: APLValue | undefined) => oldValue ? APLValue.clone(oldValue) : oldValue, - newItemPicker: (parent: HTMLElement, listPicker: ListPicker, APLValue | undefined>, index: number, config: ListItemPickerConfig, APLValue | undefined>) => new APLValuePicker(parent, player, config), - allowedActions: ['create', 'delete'], - actions: { - create: { - useIcon: true, + factory: (parent, player, config) => + new ListPicker, APLValue | undefined>(parent, player, { + ...config, + // Override setValue to replace undefined elements with default messages. + setValue: (eventID: EventID, player: Player, newValue: Array) => { + config.setValue( + eventID, + player, + newValue.map(val => val || APLValue.create()), + ); }, - }, - }), + itemLabel: 'Value', + newItem: APLValue.create, + copyItem: (oldValue: APLValue | undefined) => (oldValue ? APLValue.clone(oldValue) : oldValue), + newItemPicker: ( + parent: HTMLElement, + listPicker: ListPicker, APLValue | undefined>, + index: number, + config: ListItemPickerConfig, APLValue | undefined>, + ) => new APLValuePicker(parent, player, config), + allowedActions: ['create', 'delete'], + actions: { + create: { + useIcon: true, + }, + }, + }), }; } function inputBuilder(config: { - label: string, - submenu?: Array, - shortDescription: string, - fullDescription?: string, - newValue: () => T, - includeIf?: (player: Player, isPrepull: boolean) => boolean, - fields: Array>, + label: string; + submenu?: Array; + shortDescription: string; + fullDescription?: string; + newValue: () => T; + includeIf?: (player: Player, isPrepull: boolean) => boolean; + fields: Array>; }): ValueKindConfig { return { label: config.label, @@ -414,9 +431,9 @@ function inputBuilder(config: { }; } -const valueKindFactories: {[f in NonNullable]: ValueKindConfig} = { +const valueKindFactories: { [f in NonNullable]: ValueKindConfig } = { // Operators - 'const': inputBuilder({ + const: inputBuilder({ label: 'Const', shortDescription: 'A fixed value.', fullDescription: ` @@ -430,124 +447,103 @@ const valueKindFactories: {[f in NonNullable]: ValueKindConfig `, newValue: APLValueConst.create, - fields: [ - AplHelpers.stringFieldConfig('val'), - ], + fields: [AplHelpers.stringFieldConfig('val')], }), - 'cmp': inputBuilder({ + cmp: inputBuilder({ label: 'Compare', submenu: ['Logic'], shortDescription: 'Compares two values.', newValue: APLValueCompare.create, - fields: [ - valueFieldConfig('lhs'), - comparisonOperatorFieldConfig('op'), - valueFieldConfig('rhs'), - ], + fields: [valueFieldConfig('lhs'), comparisonOperatorFieldConfig('op'), valueFieldConfig('rhs')], }), - 'math': inputBuilder({ + math: inputBuilder({ label: 'Math', submenu: ['Logic'], shortDescription: 'Do basic math on two values.', newValue: APLValueMath.create, - fields: [ - valueFieldConfig('lhs'), - mathOperatorFieldConfig('op'), - valueFieldConfig('rhs'), - ], + fields: [valueFieldConfig('lhs'), mathOperatorFieldConfig('op'), valueFieldConfig('rhs')], }), - 'max': inputBuilder({ + max: inputBuilder({ label: 'Max', submenu: ['Logic'], shortDescription: 'Returns the largest value among the subvalues.', newValue: APLValueMax.create, - fields: [ - valueListFieldConfig('vals'), - ], + fields: [valueListFieldConfig('vals')], }), - 'min': inputBuilder({ + min: inputBuilder({ label: 'Min', submenu: ['Logic'], shortDescription: 'Returns the smallest value among the subvalues.', newValue: APLValueMin.create, - fields: [ - valueListFieldConfig('vals'), - ], + fields: [valueListFieldConfig('vals')], }), - 'and': inputBuilder({ + and: inputBuilder({ label: 'All of', submenu: ['Logic'], shortDescription: 'Returns True if all of the sub-values are True, otherwise False', newValue: APLValueAnd.create, - fields: [ - valueListFieldConfig('vals'), - ], + fields: [valueListFieldConfig('vals')], }), - 'or': inputBuilder({ + or: inputBuilder({ label: 'Any of', submenu: ['Logic'], shortDescription: 'Returns True if any of the sub-values are True, otherwise False', newValue: APLValueOr.create, - fields: [ - valueListFieldConfig('vals'), - ], + fields: [valueListFieldConfig('vals')], }), - 'not': inputBuilder({ + not: inputBuilder({ label: 'Not', submenu: ['Logic'], shortDescription: 'Returns the opposite of the inner value, i.e. True if the value is False and vice-versa.', newValue: APLValueNot.create, - fields: [ - valueFieldConfig('val'), - ], + fields: [valueFieldConfig('val')], }), // Encounter - 'currentTime': inputBuilder({ + currentTime: inputBuilder({ label: 'Current Time', submenu: ['Encounter'], shortDescription: 'Elapsed time of the current sim iteration.', newValue: APLValueCurrentTime.create, fields: [], }), - 'currentTimePercent': inputBuilder({ + currentTimePercent: inputBuilder({ label: 'Current Time (%)', submenu: ['Encounter'], shortDescription: 'Elapsed time of the current sim iteration, as a percentage.', newValue: APLValueCurrentTimePercent.create, fields: [], }), - 'remainingTime': inputBuilder({ + remainingTime: inputBuilder({ label: 'Remaining Time', submenu: ['Encounter'], shortDescription: 'Elapsed time of the remaining sim iteration.', newValue: APLValueRemainingTime.create, fields: [], }), - 'remainingTimePercent': inputBuilder({ + remainingTimePercent: inputBuilder({ label: 'Remaining Time (%)', submenu: ['Encounter'], shortDescription: 'Elapsed time of the remaining sim iteration, as a percentage.', newValue: APLValueRemainingTimePercent.create, fields: [], }), - 'isExecutePhase': inputBuilder({ + isExecutePhase: inputBuilder({ label: 'Is Execute Phase', submenu: ['Encounter'], - shortDescription: 'True if the encounter is in Execute Phase, meaning the target\'s health is less than the given threshold, otherwise False.', + shortDescription: + "True if the encounter is in Execute Phase, meaning the target's health is less than the given threshold, otherwise False.", newValue: APLValueIsExecutePhase.create, - fields: [ - executePhaseThresholdFieldConfig('threshold'), - ], + fields: [executePhaseThresholdFieldConfig('threshold')], }), - 'numberTargets': inputBuilder({ + numberTargets: inputBuilder({ label: 'Number of Targets', submenu: ['Encounter'], shortDescription: 'Count of targets in the current encounter', newValue: APLValueNumberTargets.create, fields: [], }), - 'frontOfTarget': inputBuilder({ + frontOfTarget: inputBuilder({ label: 'Front of Target', submenu: ['Encounter'], shortDescription: 'True if facing from of target', @@ -556,7 +552,7 @@ const valueKindFactories: {[f in NonNullable]: ValueKindConfig]: ValueKindConfig, isPrepull: boolean) => player.getClass() == Class.ClassDeathknight, + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassDeathKnight, fields: [], }), // Resources Rune - 'currentRuneCount': inputBuilder({ + currentRuneCount: inputBuilder({ label: 'Num Runes', submenu: ['Resources', 'Runes'], shortDescription: 'Amount of currently available Runes of certain type including Death.', newValue: APLValueCurrentRuneCount.create, - includeIf: (player: Player, isPrepull: boolean) => player.getClass() == Class.ClassDeathknight, - fields: [ - AplHelpers.runeTypeFieldConfig('runeType', true), - ], + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassDeathKnight, + fields: [AplHelpers.runeTypeFieldConfig('runeType', true)], }), - 'currentNonDeathRuneCount': inputBuilder({ + currentNonDeathRuneCount: inputBuilder({ label: 'Num Non Death Runes', submenu: ['Resources', 'Runes'], shortDescription: 'Amount of currently available Runes of certain type ignoring Death', newValue: APLValueCurrentNonDeathRuneCount.create, - includeIf: (player: Player, isPrepull: boolean) => player.getClass() == Class.ClassDeathknight, - fields: [ - AplHelpers.runeTypeFieldConfig('runeType', false), - ], + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassDeathKnight, + fields: [AplHelpers.runeTypeFieldConfig('runeType', false)], }), - 'currentRuneActive': inputBuilder({ + currentRuneActive: inputBuilder({ label: 'Rune Is Ready', submenu: ['Resources', 'Runes'], shortDescription: 'Is the rune of a certain slot currently available.', newValue: APLValueCurrentRuneActive.create, - includeIf: (player: Player, isPrepull: boolean) => player.getClass() == Class.ClassDeathknight, - fields: [ - AplHelpers.runeSlotFieldConfig('runeSlot'), - ], + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassDeathKnight, + fields: [AplHelpers.runeSlotFieldConfig('runeSlot')], }), - 'currentRuneDeath': inputBuilder({ + currentRuneDeath: inputBuilder({ label: 'Rune Is Death', submenu: ['Resources', 'Runes'], shortDescription: 'Is the rune of a certain slot currently converted to Death.', newValue: APLValueCurrentRuneDeath.create, - includeIf: (player: Player, isPrepull: boolean) => player.getClass() == Class.ClassDeathknight, - fields: [ - AplHelpers.runeSlotFieldConfig('runeSlot'), - ], + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassDeathKnight, + fields: [AplHelpers.runeSlotFieldConfig('runeSlot')], }), - 'runeCooldown': inputBuilder({ + runeCooldown: inputBuilder({ label: 'Rune Cooldown', submenu: ['Resources', 'Runes'], shortDescription: 'Amount of time until a rune of certain type is ready to use.
NOTE: Returns 0 if there is a rune available', newValue: APLValueRuneCooldown.create, - includeIf: (player: Player, isPrepull: boolean) => player.getClass() == Class.ClassDeathknight, - fields: [ - AplHelpers.runeTypeFieldConfig('runeType', false), - ], + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassDeathKnight, + fields: [AplHelpers.runeTypeFieldConfig('runeType', false)], }), - 'nextRuneCooldown': inputBuilder({ + nextRuneCooldown: inputBuilder({ label: 'Next Rune Cooldown', submenu: ['Resources', 'Runes'], shortDescription: 'Amount of time until a 2nd rune of certain type is ready to use.
NOTE: Returns 0 if there are 2 runes available', newValue: APLValueNextRuneCooldown.create, - includeIf: (player: Player, isPrepull: boolean) => player.getClass() == Class.ClassDeathknight, - fields: [ - AplHelpers.runeTypeFieldConfig('runeType', false), - ], + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassDeathKnight, + fields: [AplHelpers.runeTypeFieldConfig('runeType', false)], }), - 'runeSlotCooldown': inputBuilder({ + runeSlotCooldown: inputBuilder({ label: 'Rune Slot Cooldown', submenu: ['Resources', 'Runes'], shortDescription: 'Amount of time until a rune of certain slot is ready to use.
NOTE: Returns 0 if rune is ready', newValue: APLValueRuneSlotCooldown.create, - includeIf: (player: Player, isPrepull: boolean) => player.getClass() == Class.ClassDeathknight, - fields: [ - AplHelpers.runeSlotFieldConfig('runeSlot'), - ], + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassDeathKnight, + fields: [AplHelpers.runeSlotFieldConfig('runeSlot')], }), - 'runeGrace': inputBuilder({ + runeGrace: inputBuilder({ label: 'Rune Grace Period', submenu: ['Resources', 'Runes'], shortDescription: 'Amount of rune grace period available for certain rune type.', newValue: APLValueRuneGrace.create, - includeIf: (player: Player, isPrepull: boolean) => player.getClass() == Class.ClassDeathknight, - fields: [ - AplHelpers.runeTypeFieldConfig('runeType', false), - ], + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassDeathKnight, + fields: [AplHelpers.runeTypeFieldConfig('runeType', false)], }), - 'runeSlotGrace': inputBuilder({ + runeSlotGrace: inputBuilder({ label: 'Rune Slot Grace Period', submenu: ['Resources', 'Runes'], shortDescription: 'Amount of rune grace period available for certain rune slot.', newValue: APLValueRuneSlotGrace.create, - includeIf: (player: Player, isPrepull: boolean) => player.getClass() == Class.ClassDeathknight, - fields: [ - AplHelpers.runeSlotFieldConfig('runeSlot'), - ], + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassDeathKnight, + fields: [AplHelpers.runeSlotFieldConfig('runeSlot')], }), // GCD - 'gcdIsReady': inputBuilder({ + gcdIsReady: inputBuilder({ label: 'GCD Is Ready', submenu: ['GCD'], shortDescription: 'True if the GCD is not on cooldown, otherwise False.', newValue: APLValueGCDIsReady.create, fields: [], }), - 'gcdTimeToReady': inputBuilder({ + gcdTimeToReady: inputBuilder({ label: 'GCD Time To Ready', submenu: ['GCD'], shortDescription: 'Amount of time remaining before the GCD comes off cooldown, or 0 if it is not on cooldown.', @@ -749,7 +720,7 @@ const valueKindFactories: {[f in NonNullable]: ValueKindConfig0 if autoattacks are not engaged.', @@ -758,16 +729,14 @@ const valueKindFactories: {[f in NonNullable]: ValueKindConfigTrue if all requirements for casting the spell are currently met, otherwise False.', @@ -775,144 +744,111 @@ const valueKindFactories: {[f in NonNullable]: ValueKindConfigThe Cast Spell action does not need to be conditioned on this, because it applies this check automatically.

`, newValue: APLValueSpellCanCast.create, - fields: [ - AplHelpers.actionIdFieldConfig('spellId', 'castable_spells', ''), - ], + fields: [AplHelpers.actionIdFieldConfig('spellId', 'castable_spells', '')], }), - 'spellIsReady': inputBuilder({ + spellIsReady: inputBuilder({ label: 'Is Ready', submenu: ['Spell'], shortDescription: 'True if the spell is not on cooldown, otherwise False.', newValue: APLValueSpellIsReady.create, - fields: [ - AplHelpers.actionIdFieldConfig('spellId', 'castable_spells', ''), - ], + fields: [AplHelpers.actionIdFieldConfig('spellId', 'castable_spells', '')], }), - 'spellTimeToReady': inputBuilder({ + spellTimeToReady: inputBuilder({ label: 'Time To Ready', submenu: ['Spell'], shortDescription: 'Amount of time remaining before the spell comes off cooldown, or 0 if it is not on cooldown.', newValue: APLValueSpellTimeToReady.create, - fields: [ - AplHelpers.actionIdFieldConfig('spellId', 'castable_spells', ''), - ], + fields: [AplHelpers.actionIdFieldConfig('spellId', 'castable_spells', '')], }), - 'spellCastTime': inputBuilder({ + spellCastTime: inputBuilder({ label: 'Cast Time', submenu: ['Spell'], shortDescription: 'Amount of time to cast the spell including any haste and spell cast time adjustments.', newValue: APLValueSpellCastTime.create, - fields: [ - AplHelpers.actionIdFieldConfig('spellId', 'castable_spells', ''), - ], + fields: [AplHelpers.actionIdFieldConfig('spellId', 'castable_spells', '')], }), - 'spellTravelTime': inputBuilder({ + spellTravelTime: inputBuilder({ label: 'Travel Time', submenu: ['Spell'], shortDescription: 'Amount of time for the spell to travel to the target.', newValue: APLValueSpellTravelTime.create, - fields: [ - AplHelpers.actionIdFieldConfig('spellId', 'castable_spells', ''), - ], + fields: [AplHelpers.actionIdFieldConfig('spellId', 'castable_spells', '')], }), - 'spellCpm': inputBuilder({ + spellCpm: inputBuilder({ label: 'CPM', submenu: ['Spell'], shortDescription: 'Casts Per Minute for the spell so far in the current iteration.', newValue: APLValueSpellCPM.create, - fields: [ - AplHelpers.actionIdFieldConfig('spellId', 'castable_spells', ''), - ], + fields: [AplHelpers.actionIdFieldConfig('spellId', 'castable_spells', '')], }), - 'spellIsChanneling': inputBuilder({ + spellIsChanneling: inputBuilder({ label: 'Is Channeling', submenu: ['Spell'], shortDescription: 'True if this spell is currently being channeled, otherwise False.', newValue: APLValueSpellIsChanneling.create, - fields: [ - AplHelpers.actionIdFieldConfig('spellId', 'channel_spells', ''), - ], + fields: [AplHelpers.actionIdFieldConfig('spellId', 'channel_spells', '')], }), - 'spellChanneledTicks': inputBuilder({ + spellChanneledTicks: inputBuilder({ label: 'Channeled Ticks', submenu: ['Spell'], shortDescription: 'The number of completed ticks in the current channel of this spell, or 0 if the spell is not being channeled.', newValue: APLValueSpellChanneledTicks.create, - fields: [ - AplHelpers.actionIdFieldConfig('spellId', 'channel_spells', ''), - ], + fields: [AplHelpers.actionIdFieldConfig('spellId', 'channel_spells', '')], }), - 'channelClipDelay': inputBuilder({ + channelClipDelay: inputBuilder({ label: 'Channel Clip Delay', submenu: ['Spell'], shortDescription: 'The amount of time specified by the Channel Clip Delay setting.', newValue: APLValueChannelClipDelay.create, - fields: [ - ], + fields: [], }), // Auras - 'auraIsActive': inputBuilder({ + auraIsActive: inputBuilder({ label: 'Aura Active', submenu: ['Aura'], shortDescription: 'True if the aura is currently active, otherwise False.', newValue: APLValueAuraIsActive.create, - fields: [ - AplHelpers.unitFieldConfig('sourceUnit', 'aura_sources'), - AplHelpers.actionIdFieldConfig('auraId', 'auras', 'sourceUnit'), - ], + fields: [AplHelpers.unitFieldConfig('sourceUnit', 'aura_sources'), AplHelpers.actionIdFieldConfig('auraId', 'auras', 'sourceUnit')], }), - 'auraIsActiveWithReactionTime': inputBuilder({ + auraIsActiveWithReactionTime: inputBuilder({ label: 'Aura Active (with Reaction Time)', submenu: ['Aura'], - shortDescription: 'True if the aura is currently active AND it has been active for at least as long as the player reaction time (configured in Settings), otherwise False.', + shortDescription: + 'True if the aura is currently active AND it has been active for at least as long as the player reaction time (configured in Settings), otherwise False.', newValue: APLValueAuraIsActiveWithReactionTime.create, - fields: [ - AplHelpers.unitFieldConfig('sourceUnit', 'aura_sources'), - AplHelpers.actionIdFieldConfig('auraId', 'auras', 'sourceUnit'), - ], + fields: [AplHelpers.unitFieldConfig('sourceUnit', 'aura_sources'), AplHelpers.actionIdFieldConfig('auraId', 'auras', 'sourceUnit')], }), - 'auraRemainingTime': inputBuilder({ + auraRemainingTime: inputBuilder({ label: 'Aura Remaining Time', submenu: ['Aura'], shortDescription: 'Time remaining before this aura will expire, or 0 if the aura is not currently active.', newValue: APLValueAuraRemainingTime.create, - fields: [ - AplHelpers.unitFieldConfig('sourceUnit', 'aura_sources'), - AplHelpers.actionIdFieldConfig('auraId', 'auras', 'sourceUnit'), - ], + fields: [AplHelpers.unitFieldConfig('sourceUnit', 'aura_sources'), AplHelpers.actionIdFieldConfig('auraId', 'auras', 'sourceUnit')], }), - 'auraNumStacks': inputBuilder({ + auraNumStacks: inputBuilder({ label: 'Aura Num Stacks', submenu: ['Aura'], shortDescription: 'Number of stacks of the aura.', newValue: APLValueAuraNumStacks.create, - fields: [ - AplHelpers.unitFieldConfig('sourceUnit', 'aura_sources'), - AplHelpers.actionIdFieldConfig('auraId', 'stackable_auras', 'sourceUnit'), - ], + fields: [AplHelpers.unitFieldConfig('sourceUnit', 'aura_sources'), AplHelpers.actionIdFieldConfig('auraId', 'stackable_auras', 'sourceUnit')], }), - 'auraInternalCooldown': inputBuilder({ + auraInternalCooldown: inputBuilder({ label: 'Aura Remaining ICD', submenu: ['Aura'], - shortDescription: 'Time remaining before this aura\'s internal cooldown will be ready, or 0 if the ICD is ready now.', + shortDescription: "Time remaining before this aura's internal cooldown will be ready, or 0 if the ICD is ready now.", newValue: APLValueAuraInternalCooldown.create, - fields: [ - AplHelpers.unitFieldConfig('sourceUnit', 'aura_sources'), - AplHelpers.actionIdFieldConfig('auraId', 'icd_auras', 'sourceUnit'), - ], + fields: [AplHelpers.unitFieldConfig('sourceUnit', 'aura_sources'), AplHelpers.actionIdFieldConfig('auraId', 'icd_auras', 'sourceUnit')], }), - 'auraIcdIsReadyWithReactionTime': inputBuilder({ + auraIcdIsReadyWithReactionTime: inputBuilder({ label: 'Aura ICD Is Ready (with Reaction Time)', submenu: ['Aura'], - shortDescription: 'True if the aura\'s ICD is currently ready OR it was put on CD recently, within the player\'s reaction time (configured in Settings), otherwise False.', + shortDescription: + "True if the aura's ICD is currently ready OR it was put on CD recently, within the player's reaction time (configured in Settings), otherwise False.", newValue: APLValueAuraICDIsReadyWithReactionTime.create, - fields: [ - AplHelpers.unitFieldConfig('sourceUnit', 'aura_sources'), - AplHelpers.actionIdFieldConfig('auraId', 'icd_auras', 'sourceUnit'), - ], + fields: [AplHelpers.unitFieldConfig('sourceUnit', 'aura_sources'), AplHelpers.actionIdFieldConfig('auraId', 'icd_auras', 'sourceUnit')], }), - 'auraShouldRefresh': inputBuilder({ + auraShouldRefresh: inputBuilder({ label: 'Should Refresh Aura', submenu: ['Aura'], shortDescription: 'Whether this aura should be refreshed, e.g. for the purpose of maintaining a debuff.', @@ -920,16 +856,17 @@ const valueKindFactories: {[f in NonNullable]: ValueKindConfigThis condition checks not only the specified aura but also any other auras on the same unit, including auras applied by other raid members, which apply the same debuff category.

For example, 'Should Refresh Debuff(Sunder Armor)' will return False if the unit has an active Expose Armor aura.

`, - newValue: () => APLValueAuraShouldRefresh.create({ - maxOverlap: { - value: { - oneofKind: 'const', - const: { - val: '0ms', + newValue: () => + APLValueAuraShouldRefresh.create({ + maxOverlap: { + value: { + oneofKind: 'const', + const: { + val: '0ms', + }, }, }, - }, - }), + }), fields: [ AplHelpers.unitFieldConfig('sourceUnit', 'aura_sources_targets_first'), AplHelpers.actionIdFieldConfig('auraId', 'exclusive_effect_auras', 'sourceUnit', 'currentTarget'), @@ -941,100 +878,81 @@ const valueKindFactories: {[f in NonNullable]: ValueKindConfigTrue if the specified dot is currently ticking, otherwise False.', newValue: APLValueDotIsActive.create, - fields: [ - AplHelpers.unitFieldConfig('targetUnit', 'targets'), - AplHelpers.actionIdFieldConfig('spellId', 'dot_spells', ''), - ], + fields: [AplHelpers.unitFieldConfig('targetUnit', 'targets'), AplHelpers.actionIdFieldConfig('spellId', 'dot_spells', '')], }), - 'dotRemainingTime': inputBuilder({ + dotRemainingTime: inputBuilder({ label: 'Dot Remaining Time', submenu: ['DoT'], shortDescription: 'Time remaining before the last tick of this DoT will occur, or 0 if the DoT is not currently ticking.', newValue: APLValueDotRemainingTime.create, - fields: [ - AplHelpers.unitFieldConfig('targetUnit', 'targets'), - AplHelpers.actionIdFieldConfig('spellId', 'dot_spells', ''), - ], + fields: [AplHelpers.unitFieldConfig('targetUnit', 'targets'), AplHelpers.actionIdFieldConfig('spellId', 'dot_spells', '')], }), - 'sequenceIsComplete': inputBuilder({ + sequenceIsComplete: inputBuilder({ label: 'Sequence Is Complete', submenu: ['Sequence'], shortDescription: 'True if there are no more subactions left to execute in the sequence, otherwise False.', newValue: APLValueSequenceIsComplete.create, - fields: [ - AplHelpers.stringFieldConfig('sequenceName'), - ], + fields: [AplHelpers.stringFieldConfig('sequenceName')], }), - 'sequenceIsReady': inputBuilder({ + sequenceIsReady: inputBuilder({ label: 'Sequence Is Ready', submenu: ['Sequence'], shortDescription: 'True if the next subaction in the sequence is ready to be executed, otherwise False.', newValue: APLValueSequenceIsReady.create, - fields: [ - AplHelpers.stringFieldConfig('sequenceName'), - ], + fields: [AplHelpers.stringFieldConfig('sequenceName')], }), - 'sequenceTimeToReady': inputBuilder({ + sequenceTimeToReady: inputBuilder({ label: 'Sequence Time To Ready', submenu: ['Sequence'], shortDescription: 'Returns the amount of time remaining until the next subaction in the sequence will be ready.', newValue: APLValueSequenceTimeToReady.create, - fields: [ - AplHelpers.stringFieldConfig('sequenceName'), - ], + fields: [AplHelpers.stringFieldConfig('sequenceName')], }), // Class/spec specific values - 'totemRemainingTime': inputBuilder({ + totemRemainingTime: inputBuilder({ label: 'Totem Remaining Time', submenu: ['Shaman'], shortDescription: 'Returns the amount of time remaining until the totem will expire.', newValue: APLValueTotemRemainingTime.create, - includeIf: (player: Player, isPrepull: boolean) => player.getClass() == Class.ClassShaman, - fields: [ - totemTypeFieldConfig('totemType'), - ], + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassShaman, + fields: [totemTypeFieldConfig('totemType')], }), - 'catExcessEnergy': inputBuilder({ + catExcessEnergy: inputBuilder({ label: 'Excess Energy', submenu: ['Feral Druid'], shortDescription: 'Returns the amount of excess energy available, after subtracting energy that will be needed to maintain DoTs.', newValue: APLValueCatExcessEnergy.create, - includeIf: (player: Player, isPrepull: boolean) => player.spec == Spec.SpecFeralDruid, - fields: [ - ], + includeIf: (player: Player, _isPrepull: boolean) => player.getSpec() == Spec.SpecFeralDruid, + fields: [], }), - 'catNewSavageRoarDuration': inputBuilder({ + catNewSavageRoarDuration: inputBuilder({ label: 'New Savage Roar Duration', submenu: ['Feral Druid'], shortDescription: 'Returns duration of savage roar based on current combo points', newValue: APLValueCatNewSavageRoarDuration.create, - includeIf: (player: Player, isPrepull: boolean) => player.spec == Spec.SpecFeralDruid, - fields: [ - ], + includeIf: (player: Player, _isPrepull: boolean) => player.getSpec() == Spec.SpecFeralDruid, + fields: [], }), - 'warlockShouldRecastDrainSoul': inputBuilder({ + warlockShouldRecastDrainSoul: inputBuilder({ label: 'Should Recast Drain Soul', submenu: ['Warlock'], shortDescription: 'Returns True if the current Drain Soul channel should be immediately recast, to get a better snapshot.', newValue: APLValueWarlockShouldRecastDrainSoul.create, - includeIf: (player: Player, isPrepull: boolean) => player.getClass() == Class.ClassWarlock, - fields: [ - ], + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassWarlock, + fields: [], }), - 'warlockShouldRefreshCorruption': inputBuilder({ + warlockShouldRefreshCorruption: inputBuilder({ label: 'Should Refresh Corruption', submenu: ['Warlock'], shortDescription: 'Returns True if the current Corruption has expired, or should be refreshed to get a better snapshot.', newValue: APLValueWarlockShouldRefreshCorruption.create, - includeIf: (player: Player, isPrepull: boolean) => player.getClass() == Class.ClassWarlock, - fields: [ - AplHelpers.unitFieldConfig('targetUnit', 'targets'), - ], + includeIf: (player: Player, _isPrepull: boolean) => player.getClass() == Class.ClassWarlock, + fields: [AplHelpers.unitFieldConfig('targetUnit', 'targets')], }), }; diff --git a/ui/raid/assignments_picker.ts b/ui/raid/assignments_picker.ts index 40d9b1947d..8ca8388355 100644 --- a/ui/raid/assignments_picker.ts +++ b/ui/raid/assignments_picker.ts @@ -4,8 +4,8 @@ import { PriestTalents } from 'ui/core/proto/priest.js'; import { Component } from '../core/components/component.js'; import { UnitReferencePicker } from '../core/components/raid_target_picker.js'; import { Player } from '../core/player.js'; -import { Class, Spec,UnitReference } from '../core/proto/common.js'; -import { emptyUnitReference } from '../core/proto_utils/utils.js'; +import { Class, Spec, UnitReference } from '../core/proto/common.js'; +import { emptyUnitReference, RogueSpecs } from '../core/proto_utils/utils.js'; import { EventID, TypedEvent } from '../core/typed_event.js'; import { RaidSimUI } from './raid_sim_ui.js'; @@ -32,10 +32,10 @@ export class AssignmentsPicker extends Component { } interface AssignmentTargetPicker { - player: Player, - targetPicker: UnitReferencePicker>, + player: Player; + targetPicker: UnitReferencePicker>; targetPlayer: Player | null; -}; +} abstract class AssignedBuffPicker extends Component { readonly raidSimUI: RaidSimUI; @@ -54,31 +54,27 @@ abstract class AssignedBuffPicker extends Component { this.playersContainer.classList.add('assigned-buff-container'); this.rootElem.appendChild(this.playersContainer); - this.raidSimUI.changeEmitter.on(eventID => this.update()); + this.raidSimUI.changeEmitter.on(_eventID => this.update()); this.update(); } private update() { this.playersContainer.innerHTML = ` - ` + `; const sourcePlayers = this.getSourcePlayers(); - if (sourcePlayers.length == 0) - this.rootElem.classList.add('hide'); - else - this.rootElem.classList.remove('hide'); + if (sourcePlayers.length == 0) this.rootElem.classList.add('hide'); + else this.rootElem.classList.remove('hide'); - this.targetPickers = sourcePlayers.map((sourcePlayer, sourcePlayerIndex) => { + this.targetPickers = sourcePlayers.map((sourcePlayer, _sourcePlayerIndex) => { const row = document.createElement('div'); row.classList.add('assigned-buff-player', 'input-inline'); this.playersContainer.appendChild(row); const sourceElem = document.createElement('div'); sourceElem.classList.add('raid-target-picker-root'); - sourceElem.appendChild( - UnitReferencePicker.makeOptionElem({ player: sourcePlayer, isDropdown: false }) - ); + sourceElem.appendChild(UnitReferencePicker.makeOptionElem({ player: sourcePlayer, isDropdown: false })); row.appendChild(sourceElem); const arrow = document.createElement('i'); @@ -101,7 +97,7 @@ abstract class AssignedBuffPicker extends Component { targetPlayer: this.raidSimUI.sim.raid.getPlayerFromUnitReference(raidTargetPicker!.getInputValue()), }; - raidTargetPicker!.changeEmitter.on(eventID => { + raidTargetPicker!.changeEmitter.on(_eventID => { targetPickerData.targetPlayer = this.raidSimUI.sim.raid.getPlayerFromUnitReference(raidTargetPicker!.getInputValue()); }); @@ -142,15 +138,17 @@ class PowerInfusionsPicker extends AssignedBuffPicker { } getSourcePlayers(): Array> { - return this.raidSimUI.getActivePlayers().filter(player => player.isClass(Class.ClassPriest) && (player.getTalents() as PriestTalents).powerInfusion); + return this.raidSimUI + .getActivePlayers() + .filter(player => player.isSpec(Spec.SpecDisciplinePriest) && (player.getTalents() as PriestTalents).powerInfusion); } getPlayerValue(player: Player): UnitReference { - return (player as Player).getSpecOptions().powerInfusionTarget || emptyUnitReference(); + return (player as Player).getSpecOptions().powerInfusionTarget || emptyUnitReference(); } setPlayerValue(eventID: EventID, player: Player, newValue: UnitReference) { - const newOptions = (player as Player).getSpecOptions(); + const newOptions = (player as Player).getSpecOptions(); newOptions.powerInfusionTarget = newValue; player.setSpecOptions(eventID, newOptions); } @@ -166,12 +164,12 @@ class TricksOfTheTradesPicker extends AssignedBuffPicker { } getPlayerValue(player: Player): UnitReference { - return (player as Player).getSpecOptions().tricksOfTheTradeTarget || emptyUnitReference(); + return (player as Player).getSpecOptions().rogueOptions!.tricksOfTheTradeTarget || emptyUnitReference(); } setPlayerValue(eventID: EventID, player: Player, newValue: UnitReference) { - const newOptions = (player as Player).getSpecOptions(); - newOptions.tricksOfTheTradeTarget = newValue; + const newOptions = (player as Player).getSpecOptions(); + newOptions.rogueOptions!.tricksOfTheTradeTarget = newValue; player.setSpecOptions(eventID, newOptions); } } @@ -182,15 +180,17 @@ class UnholyFrenzyPicker extends AssignedBuffPicker { } getSourcePlayers(): Array> { - return this.raidSimUI.getActivePlayers().filter(player => player.isClass(Class.ClassDeathknight) && (player.getTalents() as DeathknightTalents).hysteria); + return this.raidSimUI + .getActivePlayers() + .filter(player => player.isSpec(Spec.SpecUnholyDeathKnight) && (player.getTalents() as DeathknightTalents).hysteria); } getPlayerValue(player: Player): UnitReference { - return (player as Player).getSpecOptions().unholyFrenzyTarget || emptyUnitReference(); + return (player as Player).getSpecOptions().unholyFrenzyTarget || emptyUnitReference(); } setPlayerValue(eventID: EventID, player: Player, newValue: UnitReference) { - const newOptions = (player as Player).getSpecOptions(); + const newOptions = (player as Player).getSpecOptions(); newOptions.unholyFrenzyTarget = newValue; player.setSpecOptions(eventID, newOptions); } @@ -202,16 +202,16 @@ class FocusMagicsPicker extends AssignedBuffPicker { } getSourcePlayers(): Array> { - return this.raidSimUI.getActivePlayers().filter(player => player.isClass(Class.ClassMage)); + return this.raidSimUI.getActivePlayers().filter(player => player.isSpec(Spec.SpecArcaneMage)); } getPlayerValue(player: Player): UnitReference { - return (player as Player).getSpecOptions().focusMagicTarget || emptyUnitReference(); + return (player as Player).getSpecOptions().mageOptions!.focusMagicTarget || emptyUnitReference(); } setPlayerValue(eventID: EventID, player: Player, newValue: UnitReference) { - const newOptions = (player as Player).getSpecOptions(); - newOptions.focusMagicTarget = newValue; + const newOptions = (player as Player).getSpecOptions(); + newOptions.mageOptions!.focusMagicTarget = newValue; player.setSpecOptions(eventID, newOptions); } } diff --git a/ui/raid/raid_stats.ts b/ui/raid/raid_stats.ts index 7013f6ddd4..6ecba4ef19 100644 --- a/ui/raid/raid_stats.ts +++ b/ui/raid/raid_stats.ts @@ -1,50 +1,43 @@ import { Tooltip } from 'bootstrap'; import { Component } from '../core/components/component.js'; -import { Player } from "../core/player.js"; -import { - Class, - RaidBuffs, - Spec, -} from '../core/proto/common.js'; +import { Player } from '../core/player.js'; +import { PlayerClasses } from '../core/player_classes'; +import { PlayerSpecs } from '../core/player_specs'; +import { Class, RaidBuffs, Spec } from '../core/proto/common.js'; import { HunterOptions_PetType as HunterPetType } from '../core/proto/hunter.js'; import { PaladinAura } from '../core/proto/paladin.js'; import { AirTotem, EarthTotem, FireTotem, WaterTotem } from '../core/proto/shaman.js'; import { WarlockOptions_Summon as WarlockSummon } from '../core/proto/warlock.js'; import { WarriorShout } from '../core/proto/warrior.js'; import { ActionId } from '../core/proto_utils/action_id.js'; -import { - ClassSpecs, - SpecTalents, - specToClass, - textCssClassForClass, -} from '../core/proto_utils/utils.js'; -import { Raid } from "../core/raid.js"; +import { ClassSpecs, SpecTalents, textCssClassForClass } from '../core/proto_utils/utils.js'; +import { Raid } from '../core/raid.js'; import { sum } from '../core/utils.js'; import { RaidSimUI } from './raid_sim_ui.js'; interface RaidStatsOptions { - sections: Array, + sections: Array; } interface RaidStatsSectionOptions { - label: string, - categories: Array, + label: string; + categories: Array; } interface RaidStatsCategoryOptions { - label: string, - effects: Array, + label: string; + effects: Array; } -type PlayerProvider = { class?: Class, condition: (player: Player) => boolean }; +type PlayerProvider = { class?: Class; condition: (player: Player) => boolean }; type RaidProvider = (raid: Raid) => boolean; interface RaidStatsEffectOptions { - label: string, - actionId?: ActionId, - playerData?: PlayerProvider, - raidData?: RaidProvider, + label: string; + actionId?: ActionId; + playerData?: PlayerProvider; + raidData?: RaidProvider; } export class RaidStats extends Component { @@ -99,7 +92,7 @@ class RaidStatsCategory extends Component { this.tooltipElem = document.createElement('div'); this.tooltipElem.innerHTML = ` - ` + `; this.effects = options.effects.map(opt => new RaidStatsEffect(this.tooltipElem, raidSimUI, opt)); @@ -112,7 +105,7 @@ class RaidStatsCategory extends Component { html: true, placement: 'right', title: this.tooltipElem, - }) + }); } } @@ -157,13 +150,13 @@ class RaidStatsEffect extends Component { if (this.options.playerData?.class) { const labelElem = this.rootElem.querySelector('.raid-stats-effect-label') as HTMLElement; - const playerCssClass = textCssClassForClass(this.options.playerData.class); + const playerCssClass = textCssClassForClass(PlayerClasses.fromProto(this.options.playerData.class)); labelElem.classList.add(playerCssClass); } const iconElem = this.rootElem.querySelector('.raid-stats-effect-icon') as HTMLImageElement; if (options.actionId) { - options.actionId.fill().then(actionId => iconElem.src = actionId.iconUrl); + options.actionId.fill().then(actionId => (iconElem.src = actionId.iconUrl)); } else { iconElem.remove(); } @@ -194,41 +187,64 @@ function playerClass(clazz: T, extraCondition?: (player: Player return { class: clazz, condition: (player: Player): boolean => { - return player.isClass(clazz) - && (!extraCondition || extraCondition(player)); + return player.isClass(clazz) && (!extraCondition || extraCondition(player)); }, }; } -function playerClassAndTalentInternal(clazz: T, talentName: keyof SpecTalents>, negateTalent: boolean, extraCondition?: (player: Player>) => boolean): PlayerProvider { +function playerClassAndTalentInternal( + clazz: T, + talentName: keyof SpecTalents>, + negateTalent: boolean, + extraCondition?: (player: Player>) => boolean, +): PlayerProvider { return { class: clazz, condition: (player: Player): boolean => { - return player.isClass(clazz) - && negateIf(Boolean((player.getTalents() as any)[talentName]), negateTalent) - && (!extraCondition || extraCondition(player)); + return ( + player.isClass(clazz) && + negateIf(Boolean((player.getTalents() as any)[talentName]), negateTalent) && + (!extraCondition || extraCondition(player)) + ); }, }; } -function playerClassAndTalent(clazz: T, talentName: keyof SpecTalents>, extraCondition?: (player: Player>) => boolean): PlayerProvider { +function playerClassAndTalent( + clazz: T, + talentName: keyof SpecTalents>, + extraCondition?: (player: Player>) => boolean, +): PlayerProvider { return playerClassAndTalentInternal(clazz, talentName, false, extraCondition); } -function playerClassAndMissingTalent(clazz: T, talentName: keyof SpecTalents>, extraCondition?: (player: Player>) => boolean): PlayerProvider { +function playerClassAndMissingTalent( + clazz: T, + talentName: keyof SpecTalents>, + extraCondition?: (player: Player>) => boolean, +): PlayerProvider { return playerClassAndTalentInternal(clazz, talentName, true, extraCondition); } -function playerSpecAndTalentInternal(spec: T, talentName: keyof SpecTalents, negateTalent: boolean, extraCondition?: (player: Player) => boolean): PlayerProvider { +function playerSpecAndTalentInternal( + spec: T, + talentName: keyof SpecTalents, + negateTalent: boolean, + extraCondition?: (player: Player) => boolean, +): PlayerProvider { return { - class: specToClass[spec], + class: PlayerSpecs.fromProto(spec).playerClass.protoID, condition: (player: Player): boolean => { - return player.isSpec(spec) - && negateIf(Boolean((player.getTalents() as any)[talentName]), negateTalent) - && (!extraCondition || extraCondition(player)); + return ( + player.isSpec(spec) && negateIf(Boolean((player.getTalents() as any)[talentName]), negateTalent) && (!extraCondition || extraCondition(player)) + ); }, }; } function playerSpecAndTalent(spec: T, talentName: keyof SpecTalents, extraCondition?: (player: Player) => boolean): PlayerProvider { return playerSpecAndTalentInternal(spec, talentName, false, extraCondition); } -function playerSpecAndMissingTalent(spec: T, talentName: keyof SpecTalents, extraCondition?: (player: Player) => boolean): PlayerProvider { +function playerSpecAndMissingTalent( + spec: T, + talentName: keyof SpecTalents, + extraCondition?: (player: Player) => boolean, +): PlayerProvider { return playerSpecAndTalentInternal(spec, talentName, true, extraCondition); } @@ -340,22 +356,38 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { { label: 'Improved Devotion Aura', actionId: ActionId.fromSpellId(20140), - playerData: playerClassAndTalent(Class.ClassPaladin, 'improvedDevotionAura', player => player.getSpecOptions().aura == PaladinAura.DevotionAura), + playerData: playerClassAndTalent( + Class.ClassPaladin, + 'improvedDevotionAura', + player => player.getSpecOptions().aura == PaladinAura.DevotionAura, + ), }, { label: 'Devotion Aura', actionId: ActionId.fromSpellId(48942), - playerData: playerClassAndMissingTalent(Class.ClassPaladin, 'improvedDevotionAura', player => player.getSpecOptions().aura == PaladinAura.DevotionAura), + playerData: playerClassAndMissingTalent( + Class.ClassPaladin, + 'improvedDevotionAura', + player => player.getSpecOptions().aura == PaladinAura.DevotionAura, + ), }, { label: 'Improved Stoneskin Totem', actionId: ActionId.fromSpellId(16293), - playerData: playerClassAndTalent(Class.ClassShaman, 'guardianTotems', player => player.getSpecOptions().totems?.earth == EarthTotem.StoneskinTotem), + playerData: playerClassAndTalent( + Class.ClassShaman, + 'guardianTotems', + player => player.getSpecOptions().totems?.earth == EarthTotem.StoneskinTotem, + ), }, { label: 'Stoneskin Totem', actionId: ActionId.fromSpellId(58753), - playerData: playerClassAndMissingTalent(Class.ClassShaman, 'guardianTotems', player => player.getSpecOptions().totems?.earth == EarthTotem.StoneskinTotem), + playerData: playerClassAndMissingTalent( + Class.ClassShaman, + 'guardianTotems', + player => player.getSpecOptions().totems?.earth == EarthTotem.StoneskinTotem, + ), }, { label: 'Scroll of Protection', @@ -390,17 +422,25 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { { label: 'Improved Strength of Earth Totem', actionId: ActionId.fromSpellId(52456), - playerData: playerClassAndTalent(Class.ClassShaman, 'enhancingTotems', player => player.getSpecOptions().totems?.earth == EarthTotem.StrengthOfEarthTotem), + playerData: playerClassAndTalent( + Class.ClassShaman, + 'enhancingTotems', + player => player.getSpecOptions().totems?.earth == EarthTotem.StrengthOfEarthTotem, + ), }, { label: 'Strength of Earth Totem', actionId: ActionId.fromSpellId(58643), - playerData: playerClassAndMissingTalent(Class.ClassShaman, 'enhancingTotems', player => player.getSpecOptions().totems?.earth == EarthTotem.StrengthOfEarthTotem), + playerData: playerClassAndMissingTalent( + Class.ClassShaman, + 'enhancingTotems', + player => player.getSpecOptions().totems?.earth == EarthTotem.StrengthOfEarthTotem, + ), }, { label: 'Horn of Winter', actionId: ActionId.fromSpellId(57623), - playerData: playerClass(Class.ClassDeathknight), + playerData: playerClass(Class.ClassDeathKnight), }, { label: 'Scroll of Strength', @@ -425,12 +465,20 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { { label: 'Improved Fel Intelligence', actionId: ActionId.fromSpellId(54038), - playerData: playerClassAndTalent(Class.ClassWarlock, 'improvedFelhunter', player => player.getSpecOptions().summon == WarlockSummon.Felhunter), + playerData: playerClassAndTalent( + Class.ClassWarlock, + 'improvedFelhunter', + player => player.getSpecOptions().warlockOptions!.summon == WarlockSummon.Felhunter, + ), }, { label: 'Fel Intelligence', actionId: ActionId.fromSpellId(57567), - playerData: playerClassAndMissingTalent(Class.ClassWarlock, 'improvedFelhunter', player => player.getSpecOptions().summon == WarlockSummon.Felhunter), + playerData: playerClassAndMissingTalent( + Class.ClassWarlock, + 'improvedFelhunter', + player => player.getSpecOptions().warlockOptions!.summon == WarlockSummon.Felhunter, + ), }, { label: 'Scroll of Intellect', @@ -450,12 +498,20 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { { label: 'Improved Fel Intelligence', actionId: ActionId.fromSpellId(54038), - playerData: playerClassAndTalent(Class.ClassWarlock, 'improvedFelhunter', player => player.getSpecOptions().summon == WarlockSummon.Felhunter), + playerData: playerClassAndTalent( + Class.ClassWarlock, + 'improvedFelhunter', + player => player.getSpecOptions().warlockOptions!.summon == WarlockSummon.Felhunter, + ), }, { label: 'Fel Intelligence', actionId: ActionId.fromSpellId(57567), - playerData: playerClassAndMissingTalent(Class.ClassWarlock, 'improvedFelhunter', player => player.getSpecOptions().summon == WarlockSummon.Felhunter), + playerData: playerClassAndMissingTalent( + Class.ClassWarlock, + 'improvedFelhunter', + player => player.getSpecOptions().warlockOptions!.summon == WarlockSummon.Felhunter, + ), }, { label: 'Scroll of Spirit', @@ -480,12 +536,20 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { { label: 'Improved Battle Shout', actionId: ActionId.fromSpellId(12861), - playerData: playerClassAndTalent(Class.ClassWarrior, 'commandingPresence', player => player.getSpecOptions().shout == WarriorShout.WarriorShoutBattle), + playerData: playerClassAndTalent( + Class.ClassWarrior, + 'commandingPresence', + player => player.getSpecOptions().shout == WarriorShout.WarriorShoutBattle, + ), }, { label: 'Battle Shout', actionId: ActionId.fromSpellId(47436), - playerData: playerClassAndMissingTalent(Class.ClassWarrior, 'commandingPresence', player => player.getSpecOptions().shout == WarriorShout.WarriorShoutBattle), + playerData: playerClassAndMissingTalent( + Class.ClassWarrior, + 'commandingPresence', + player => player.getSpecOptions().shout == WarriorShout.WarriorShoutBattle, + ), }, ], }, @@ -493,9 +557,9 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { label: 'Atk Pwr %', effects: [ { - label: 'Abomination\'s Might', + label: "Abomination's Might", actionId: ActionId.fromSpellId(53138), - playerData: playerClassAndTalent(Class.ClassDeathknight, 'abominationsMight'), + playerData: playerClassAndTalent(Class.ClassDeathKnight, 'abominationsMight'), }, { label: 'Unleashed Rage', @@ -580,12 +644,20 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { { label: 'Improved Mana Spring Totem', actionId: ActionId.fromSpellId(16206), - playerData: playerClassAndTalent(Class.ClassShaman, 'restorativeTotems', player => player.getSpecOptions().totems?.water == WaterTotem.ManaSpringTotem), + playerData: playerClassAndTalent( + Class.ClassShaman, + 'restorativeTotems', + player => player.getSpecOptions().totems?.water == WaterTotem.ManaSpringTotem, + ), }, { label: 'Mana Spring Totem', actionId: ActionId.fromSpellId(58774), - playerData: playerClassAndMissingTalent(Class.ClassShaman, 'restorativeTotems', player => player.getSpecOptions().totems?.water == WaterTotem.ManaSpringTotem), + playerData: playerClassAndMissingTalent( + Class.ClassShaman, + 'restorativeTotems', + player => player.getSpecOptions().totems?.water == WaterTotem.ManaSpringTotem, + ), }, ], }, @@ -610,17 +682,25 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { { label: 'Improved Icy Talons', actionId: ActionId.fromSpellId(55610), - playerData: playerClassAndTalent(Class.ClassDeathknight, 'improvedIcyTalons'), + playerData: playerClassAndTalent(Class.ClassDeathKnight, 'improvedIcyTalons'), }, { label: 'Improved Windfury Totem', actionId: ActionId.fromSpellId(29193), - playerData: playerClassAndTalent(Class.ClassShaman, 'improvedWindfuryTotem', player => player.getSpecOptions().totems?.air == AirTotem.WindfuryTotem), + playerData: playerClassAndTalent( + Class.ClassShaman, + 'improvedWindfuryTotem', + player => player.getSpecOptions().totems?.air == AirTotem.WindfuryTotem, + ), }, { label: 'Windfury Totem', actionId: ActionId.fromSpellId(65990), - playerData: playerClassAndMissingTalent(Class.ClassShaman, 'improvedWindfuryTotem', player => player.getSpecOptions().totems?.air == AirTotem.WindfuryTotem), + playerData: playerClassAndMissingTalent( + Class.ClassShaman, + 'improvedWindfuryTotem', + player => player.getSpecOptions().totems?.air == AirTotem.WindfuryTotem, + ), }, ], }, @@ -635,7 +715,11 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { { label: 'Totem of Wrath', actionId: ActionId.fromSpellId(57722), - playerData: playerClassAndTalent(Class.ClassShaman, 'totemOfWrath', player => player.getSpecOptions().totems?.fire == FireTotem.TotemOfWrath), + playerData: playerClassAndTalent( + Class.ClassShaman, + 'totemOfWrath', + player => player.getSpecOptions().totems?.fire == FireTotem.TotemOfWrath, + ), }, { label: 'Flametongue Totem', @@ -675,22 +759,38 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { { label: 'Improved Commanding Shout', actionId: ActionId.fromSpellId(12861), - playerData: playerClassAndTalent(Class.ClassWarrior, 'commandingPresence', player => player.getSpecOptions().shout == WarriorShout.WarriorShoutCommanding), + playerData: playerClassAndTalent( + Class.ClassWarrior, + 'commandingPresence', + player => player.getSpecOptions().shout == WarriorShout.WarriorShoutCommanding, + ), }, { label: 'Commanding Shout', actionId: ActionId.fromSpellId(47440), - playerData: playerClassAndMissingTalent(Class.ClassWarrior, 'commandingPresence', player => player.getSpecOptions().shout == WarriorShout.WarriorShoutCommanding), + playerData: playerClassAndMissingTalent( + Class.ClassWarrior, + 'commandingPresence', + player => player.getSpecOptions().shout == WarriorShout.WarriorShoutCommanding, + ), }, { label: 'Improved Imp', actionId: ActionId.fromSpellId(18696), - playerData: playerClassAndTalent(Class.ClassWarlock, 'improvedImp', player => player.getSpecOptions().summon == WarlockSummon.Imp), + playerData: playerClassAndTalent( + Class.ClassWarlock, + 'improvedImp', + player => player.getSpecOptions().warlockOptions!.summon == WarlockSummon.Imp, + ), }, { label: 'Blood Pact', actionId: ActionId.fromSpellId(47982), - playerData: playerClassAndMissingTalent(Class.ClassWarlock, 'improvedImp', player => player.getSpecOptions().summon == WarlockSummon.Imp), + playerData: playerClassAndMissingTalent( + Class.ClassWarlock, + 'improvedImp', + player => player.getSpecOptions().warlockOptions!.summon == WarlockSummon.Imp, + ), }, ], }, @@ -775,7 +875,7 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { { label: 'Unholy Frenzy', actionId: ActionId.fromSpellId(49016), - playerData: playerClassAndTalent(Class.ClassDeathknight, 'hysteria'), + playerData: playerClassAndTalent(Class.ClassDeathKnight, 'hysteria'), }, ], }, @@ -890,12 +990,18 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { { label: 'Totem of Wrath', actionId: ActionId.fromSpellId(30706), - playerData: playerClassAndTalent(Class.ClassShaman, 'totemOfWrath', player => player.getSpecOptions().totems?.fire == FireTotem.TotemOfWrath), + playerData: playerClassAndTalent( + Class.ClassShaman, + 'totemOfWrath', + player => player.getSpecOptions().totems?.fire == FireTotem.TotemOfWrath, + ), }, { label: 'Heart of the Crusader', actionId: ActionId.fromSpellId(20337), - playerData: playerClassAndTalent(Class.ClassPaladin, 'heartOfTheCrusader', player => [Spec.SpecRetributionPaladin, Spec.SpecProtectionPaladin].includes(player.spec)), + playerData: playerClassAndTalent(Class.ClassPaladin, 'heartOfTheCrusader', player => + [Spec.SpecRetributionPaladin, Spec.SpecProtectionPaladin].includes(player.spec), + ), }, { label: 'Master Poisoner', @@ -918,7 +1024,7 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { playerData: playerClassAndTalent(Class.ClassMage, 'improvedScorch'), }, { - label: 'Winter\'s Chill', + label: "Winter's Chill", actionId: ActionId.fromSpellId(28593), playerData: playerClassAndTalent(Class.ClassMage, 'wintersChill'), }, @@ -945,7 +1051,7 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { { label: 'Ebon Plaguebringer', actionId: ActionId.fromSpellId(51161), - playerData: playerClassAndTalent(Class.ClassDeathknight, 'ebonPlaguebringer'), + playerData: playerClassAndTalent(Class.ClassDeathKnight, 'ebonPlaguebringer'), }, { label: 'Earth and Moon', @@ -970,7 +1076,9 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { { label: 'Vindication', actionId: ActionId.fromSpellId(26016), - playerData: playerClassAndTalent(Class.ClassPaladin, 'vindication', player => [Spec.SpecRetributionPaladin, Spec.SpecProtectionPaladin].includes(player.spec)), + playerData: playerClassAndTalent(Class.ClassPaladin, 'vindication', player => + [Spec.SpecRetributionPaladin, Spec.SpecProtectionPaladin].includes(player.spec), + ), }, { label: 'Improved Demoralizing Shout', @@ -1025,12 +1133,12 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { { label: 'Improved Frost Fever', actionId: ActionId.fromSpellId(51456), - playerData: playerClassAndTalent(Class.ClassDeathknight, 'improvedIcyTouch'), + playerData: playerClassAndTalent(Class.ClassDeathKnight, 'improvedIcyTouch'), }, { label: 'Frost Fever', actionId: ActionId.fromSpellId(51456), - playerData: playerClassAndMissingTalent(Class.ClassDeathknight, 'improvedIcyTouch'), + playerData: playerClassAndMissingTalent(Class.ClassDeathKnight, 'improvedIcyTouch'), }, { label: 'Judgements of the Just', @@ -1040,7 +1148,9 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { { label: 'Infected Wounds', actionId: ActionId.fromSpellId(48485), - playerData: playerClassAndTalent(Class.ClassDruid, 'infectedWounds', player => [Spec.SpecFeralDruid, Spec.SpecFeralTankDruid].includes(player.spec)), + playerData: playerClassAndTalent(Class.ClassDruid, 'infectedWounds', player => + [Spec.SpecFeralDruid, Spec.SpecFeralTankDruid].includes(player.spec), + ), }, ], }, @@ -1061,5 +1171,5 @@ const RAID_STATS_OPTIONS: RaidStatsOptions = { }, ], }, - ] + ], };