diff --git a/ui/core/components/individual_sim_ui/talents_tab.ts b/ui/core/components/individual_sim_ui/talents_tab.ts index 7a6c4871a1..66e33f4dbf 100644 --- a/ui/core/components/individual_sim_ui/talents_tab.ts +++ b/ui/core/components/individual_sim_ui/talents_tab.ts @@ -1,14 +1,11 @@ -import * as Mechanics from '../../constants/mechanics'; import { IndividualSimUI } from '../../individual_sim_ui'; import { Player } from '../../player'; import { Class, Glyphs, Spec } from '../../proto/common'; import { SavedTalents } from '../../proto/ui'; -import { HunterSpecs } from '../../proto_utils/utils'; import { classTalentsConfig } from '../../talents/factory'; -import { HunterPetTalentsPicker, makePetTypeInputConfig } from '../../talents/hunter_pet'; +import { HunterPetTalentsPicker } from '../../talents/hunter_pet'; import { TalentsPicker } from '../../talents/talents_picker'; import { EventID, TypedEvent } from '../../typed_event'; -import { IconEnumPicker } from '../icon_enum_picker'; import { SavedDataManager } from '../saved_data_manager'; import { SimTab } from '../sim_tab'; @@ -53,7 +50,6 @@ export class TalentsTab extends SimTab { player.setTalentsString(eventID, newValue); }, pointsPerRow: 5, - maxPoints: Mechanics.MAX_TALENT_POINTS, }); } @@ -99,15 +95,13 @@ export class TalentsTab extends SimTab { const petTab = this.leftPanel.querySelector('#pet-talents-tab') as HTMLElement; this.buildTalentsPicker(playerTab); - - if (this.simUI.player.getClass() == Class.ClassHunter) { - this.buildHunterPetPicker(petTab); - } + this.buildHunterPetPicker(petTab); } - private buildHunterPetPicker(parentElem: HTMLElement) { - new HunterPetTalentsPicker(parentElem, this.simUI, this.simUI.player as unknown as Player); - new IconEnumPicker(parentElem, this.simUI.player as unknown as Player, makePetTypeInputConfig()); + private buildHunterPetPicker(parentElem: HTMLElement) { + if (this.simUI.player.isClass(Class.ClassHunter)) { + new HunterPetTalentsPicker(parentElem, this.simUI, this.simUI.player); + } } private buildSavedTalentsPicker() { diff --git a/ui/core/constants/mechanics.ts b/ui/core/constants/mechanics.ts index ab881c5518..c56b976de9 100644 --- a/ui/core/constants/mechanics.ts +++ b/ui/core/constants/mechanics.ts @@ -1,5 +1,4 @@ export const CHARACTER_LEVEL = 85; -export const MAX_TALENT_POINTS = 41; export const BOSS_LEVEL = CHARACTER_LEVEL + 3; export const EXPERTISE_PER_QUARTER_PERCENT_REDUCTION = 32.79 / 4; diff --git a/ui/core/individual_sim_ui.ts b/ui/core/individual_sim_ui.ts index 8266b3dc37..c2dec9b642 100644 --- a/ui/core/individual_sim_ui.ts +++ b/ui/core/individual_sim_ui.ts @@ -46,6 +46,7 @@ import { Stats } from './proto_utils/stats'; import { getTalentPoints, SpecOptions } from './proto_utils/utils'; import { SimSettingCategories } from './sim'; import { SimUI, SimWarning } from './sim_ui'; +import { MAX_POINTS_PLAYER } from './talents/talents_picker'; import { EventID, TypedEvent } from './typed_event'; const SAVED_GEAR_STORAGE_KEY = '__savedGear__'; @@ -233,9 +234,9 @@ export abstract class IndividualSimUI extends SimUI { if (talentPoints == 0) { // Just return here, so we don't show a warning during page load. return ''; - } else if (talentPoints < Mechanics.MAX_TALENT_POINTS) { + } else if (talentPoints < MAX_POINTS_PLAYER) { return 'Unspent talent points.'; - } else if (talentPoints > Mechanics.MAX_TALENT_POINTS) { + } else if (talentPoints > MAX_POINTS_PLAYER) { return 'More than maximum talent points spent.'; } else { return ''; diff --git a/ui/core/talents/hunter_pet.ts b/ui/core/talents/hunter_pet.tsx similarity index 75% rename from ui/core/talents/hunter_pet.ts rename to ui/core/talents/hunter_pet.tsx index f2c95dd8c7..51028ae404 100644 --- a/ui/core/talents/hunter_pet.ts +++ b/ui/core/talents/hunter_pet.tsx @@ -1,20 +1,26 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { element } from 'tsx-vanilla'; + import { Component } from '../components/component.js'; +import { IconEnumPicker } from '../components/icon_enum_picker'; import * as InputHelpers from '../components/input_helpers.js'; import { SavedDataManager } from '../components/saved_data_manager.js'; import { Player } from '../player.js'; +import { Spec } from '../proto/common'; import { HunterOptions_PetType as PetType, HunterPetTalents } from '../proto/hunter.js'; import { ActionId } from '../proto_utils/action_id.js'; -import { HunterSpecs } from '../proto_utils/utils'; +import { getTalentTree, getTalentTreePoints, HunterSpecs } from '../proto_utils/utils.js'; import { SimUI } from '../sim_ui.js'; import { EventID, TypedEvent } from '../typed_event.js'; import { protoToTalentString, talentStringToProto } from './factory.js'; -import { newTalentsConfig, TalentsConfig, TalentsPicker } from './talents_picker.js'; +import { newTalentsConfig, TalentsConfig, TalentsPicker } from './talents_picker.jsx'; import HunterPetCunningJson from './trees/hunter_cunning.json'; import HunterPetFerocityJson from './trees/hunter_ferocity.json'; import HunterPetTenacityJson from './trees/hunter_tenacity.json'; export function makePetTypeInputConfig(): InputHelpers.TypedIconEnumPickerConfig, PetType> { return InputHelpers.makeClassOptionsEnumIconInput({ + extraCssClasses: ['pet-type-picker'], fieldName: 'petType', numColumns: 5, values: [ @@ -100,130 +106,6 @@ const petCategories: Record = { const categoryOrder = [PetCategory.Cunning, PetCategory.Ferocity, PetCategory.Tenacity]; const categoryClasses = ['cunning', 'ferocity', 'tenacity']; -export class HunterPetTalentsPicker extends Component { - private readonly simUI: SimUI; - private readonly player: Player; - private curCategory: PetCategory | null; - private curTalents: HunterPetTalents; - - // Not saved to storage, just holds last-used values for this session. - private savedSets: Array; - - constructor(parent: HTMLElement, simUI: SimUI, player: Player) { - super(parent, 'hunter-pet-talents-picker'); - this.simUI = simUI; - this.player = player; - - this.rootElem.innerHTML = ` -
- `; - - this.curCategory = this.getCategoryFromPlayer(); - this.curTalents = this.getPetTalentsFromPlayer(); - this.savedSets = defaultTalents.slice(); - this.savedSets[this.curCategory] = this.curTalents; - this.rootElem.classList.add(categoryClasses[this.curCategory]); - - const talentsContainer = this.rootElem.getElementsByClassName('pet-talents-container')[0] as HTMLElement; - - const pickers = categoryOrder.map((category, i) => { - const talentsConfig = petTalentsConfig[i]; - - const pickerContainer = document.createElement('div'); - pickerContainer.classList.add('hunter-pet-talents-' + categoryClasses[i]); - talentsContainer.appendChild(pickerContainer); - - const picker = new TalentsPicker(pickerContainer, player, { - klass: player.getClass(), - trees: talentsConfig, - changedEvent: (player: Player) => player.specOptionsChangeEmitter, - getValue: (_player: Player) => protoToTalentString(this.getPetTalentsFromPlayer(), talentsConfig), - setValue: (eventID: EventID, player: Player, newValue: string) => { - const options = player.getClassOptions(); - options.petTalents = talentStringToProto(HunterPetTalents.create(), newValue, talentsConfig); - player.setClassOptions(eventID, options); - - this.savedSets[i] = options.petTalents; - this.curTalents = options.petTalents; - }, - pointsPerRow: 3, - maxPoints: 16, - }); - - const savedTalentsManager = new SavedDataManager, string>(pickerContainer, this.player, { - presetsOnly: true, - label: 'Pet Talents', - storageKey: '__NEVER_USED__', - getData: (_player: Player) => protoToTalentString(this.getPetTalentsFromPlayer(), talentsConfig), - setData: (eventID: EventID, player: Player, newValue: string) => { - const options = player.getClassOptions(); - options.petTalents = talentStringToProto(HunterPetTalents.create(), newValue, talentsConfig); - player.setClassOptions(eventID, options); - - this.savedSets[i] = options.petTalents; - this.curTalents = options.petTalents; - }, - changeEmitters: [this.player.specOptionsChangeEmitter], - equals: (a: string, b: string) => a == b, - toJson: (a: string) => a, - fromJson: (_obj: any) => '', - }); - savedTalentsManager.addSavedData({ - name: 'Default', - isPreset: true, - data: protoToTalentString(defaultTalents[i], talentsConfig), - }); - savedTalentsManager.addSavedData({ - name: 'Beast Mastery', - isPreset: true, - data: protoToTalentString(defaultBMTalents[i], talentsConfig), - }); - - return picker; - }); - - player.specOptionsChangeEmitter.on(() => { - const petCategory = this.getCategoryFromPlayer(); - const categoryIdx = categoryOrder.indexOf(petCategory); - - if (petCategory != this.curCategory) { - this.curCategory = petCategory; - this.rootElem.classList.remove(...categoryClasses); - this.rootElem.classList.add(categoryClasses[categoryIdx]); - - const curTalents = this.getPetTalentsFromPlayer(); - if (!HunterPetTalents.equals(curTalents, this.curTalents)) { - // If the current talents have also changed, this was probably a load so we shouldn't switch sets. - this.curTalents = curTalents; - this.savedSets[this.curCategory] = this.curTalents; - } else { - // Revert to the talents from last time the user was editing this category. - const options = this.player.getClassOptions(); - options.petTalents = this.savedSets[this.curCategory]; - this.player.setClassOptions(TypedEvent.nextEventID(), options); - this.curTalents = options.petTalents; - } - } - }); - - const updateIsBM = () => { - const maxPoints = this.player.getTalents().beastMastery ? 20 : 16; - pickers.forEach(picker => picker.setMaxPoints(maxPoints)); - }; - player.talentsChangeEmitter.on(updateIsBM); - updateIsBM(); - } - - getPetTalentsFromPlayer(): HunterPetTalents { - return this.player.getClassOptions().petTalents || HunterPetTalents.create(); - } - - getCategoryFromPlayer(): PetCategory { - const petType = this.player.getClassOptions().petType; - return petCategories[petType]; - } -} - export function getPetTalentsConfig(petType: PetType): TalentsConfig { const petCategory = petCategories[petType]; const categoryIdx = categoryOrder.indexOf(petCategory); @@ -309,8 +191,139 @@ export const tenacityBMDefault: HunterPetTalents = HunterPetTalents.create({ }); const defaultBMTalents = [cunningBMDefault, ferocityBMDefault, tenacityBMDefault]; -const cunningPetTalentsConfig: TalentsConfig = newTalentsConfig(HunterPetCunningJson); -const ferocityPetTalentsConfig: TalentsConfig = newTalentsConfig(HunterPetFerocityJson); -const tenacityPetTalentsConfig: TalentsConfig = newTalentsConfig(HunterPetTenacityJson); +export const cunningPetTalentsConfig: TalentsConfig = newTalentsConfig(HunterPetCunningJson); +export const ferocityPetTalentsConfig: TalentsConfig = newTalentsConfig(HunterPetFerocityJson); +export const tenacityPetTalentsConfig: TalentsConfig = newTalentsConfig(HunterPetTenacityJson); + +export const petTalentsConfig = [cunningPetTalentsConfig, ferocityPetTalentsConfig, tenacityPetTalentsConfig]; + +export class HunterPet { + readonly player: Player; + + private talents: HunterPetTalents; + private talentsConfig: TalentsConfig; + private talentsString: string; + + readonly talentsChangeEmitter: TypedEvent; + + constructor(player: Player) { + this.player = player; + this.talents = player.getClassOptions().petTalents ?? HunterPetTalents.create(); + this.talentsConfig = getPetTalentsConfig(player.getClassOptions().petType); + this.talentsString = protoToTalentString(this.talents, this.talentsConfig); + this.talentsChangeEmitter = this.player.specOptionsChangeEmitter; + } + + getTalents(): HunterPetTalents { + return this.talents; + } + + getTalentsString(): string { + return protoToTalentString(this.talents, this.talentsConfig); + } + + setTalentsString(eventID: EventID, newTalentsString: string) { + if (newTalentsString == this.talentsString) return; + + const options = this.player.getClassOptions(); + options.petTalents = talentStringToProto(HunterPetTalents.create(), newTalentsString, this.talentsConfig); + + this.talents = options.petTalents; + this.talentsString = newTalentsString; + this.player.setClassOptions(eventID, options); + } + + getTalentTree(): number { + return getTalentTree(this.getTalentsString()); + } + + getTalentTreePoints(): Array { + return getTalentTreePoints(this.getTalentsString()); + } +} + +export class HunterPetTalentsPicker extends Component { + private readonly simUI: SimUI; + private readonly player: Player; + private curCategory: PetCategory | null; + private curTalents: HunterPetTalents; + + // Not saved to storage, just holds last-used values for this session. + private savedSets: Array; + + constructor(parent: HTMLElement, simUI: SimUI, player: Player) { + super(parent, 'hunter-pet-talents-picker'); + this.simUI = simUI; + this.player = player; + + this.curCategory = this.getCategoryFromPlayer(); + this.curTalents = this.getPetTalentsFromPlayer(); + this.savedSets = defaultTalents.slice(); + this.savedSets[this.curCategory] = this.curTalents; + + this.rootElem.classList.add(categoryClasses[this.curCategory]); + + const talentsContainer =
; + this.rootElem.appendChild(talentsContainer); + + simUI.sim.waitForInit().then(() => { + const pet = new HunterPet(player); + categoryOrder.map((_category, i) => { + const pickerContainer = document.createElement('div'); + pickerContainer.classList.add('hunter-pet-talents-' + categoryClasses[i]); + talentsContainer.appendChild(pickerContainer); + + const talentsConfig = petTalentsConfig[i]; + const picker = new TalentsPicker(pickerContainer, pet, { + klass: player.getClass(), + trees: talentsConfig, + changedEvent: (pet: HunterPet) => pet.player.specOptionsChangeEmitter, + getValue: (pet: HunterPet) => pet.getTalentsString(), + setValue: (eventID: EventID, pet: HunterPet, newValue: string) => { + pet.setTalentsString(eventID, newValue); + this.savedSets[i] = pet.getTalents(); + this.curTalents = pet.getTalents(); + }, + pointsPerRow: 3, + }); -const petTalentsConfig = [cunningPetTalentsConfig, ferocityPetTalentsConfig, tenacityPetTalentsConfig]; + return picker; + }); + }); + + new IconEnumPicker(this.rootElem, this.player, makePetTypeInputConfig()); + + player.specOptionsChangeEmitter.on(() => { + const petCategory = this.getCategoryFromPlayer(); + const categoryIdx = categoryOrder.indexOf(petCategory); + + if (petCategory != this.curCategory) { + this.curCategory = petCategory; + this.rootElem.classList.remove(...categoryClasses); + this.rootElem.classList.add(categoryClasses[categoryIdx]); + + const curTalents = this.getPetTalentsFromPlayer(); + if (!HunterPetTalents.equals(curTalents, this.curTalents)) { + // If the current talents have also changed, this was probably a load so we shouldn't switch sets. + this.curTalents = curTalents; + this.savedSets[this.curCategory] = this.curTalents; + } else { + // Revert to the talents from last time the user was editing this category. + const options = this.player.getClassOptions(); + options.petTalents = this.savedSets[this.curCategory]; + this.player.setClassOptions(TypedEvent.nextEventID(), options); + this.curTalents = options.petTalents; + } + } + }); + } + + getPetTalentsFromPlayer(): HunterPetTalents { + return this.player.getClassOptions().petTalents || HunterPetTalents.create(); + } + + getCategoryFromPlayer(): PetCategory { + const petType = this.player.getClassOptions().petType; + return petCategories[petType]; + } +} diff --git a/ui/core/talents/talents_picker.tsx b/ui/core/talents/talents_picker.tsx index 3985b0bb33..044b61f1c6 100644 --- a/ui/core/talents/talents_picker.tsx +++ b/ui/core/talents/talents_picker.tsx @@ -7,7 +7,6 @@ import { CopyButton } from '../components/copy_button.js'; import { Input, InputConfig } from '../components/input.js'; import { Player } from '../player.js'; import { PlayerClasses } from '../player_classes'; -import { PlayerSpecs } from '../player_specs'; import { Class, Spec } from '../proto/common.js'; import { ActionId } from '../proto_utils/action_id.js'; import { getTalentTreePoints } from '../proto_utils/utils'; @@ -15,23 +14,22 @@ import { TypedEvent } from '../typed_event.js'; import { isRightClick, sum } from '../utils.js'; import { classGlyphsConfig } from './factory'; import { GlyphsPicker } from './glyphs_picker'; +import { HunterPet } from './hunter_pet'; -const MAX_POINTS_PLAYER = 41; +export const MAX_POINTS_PLAYER = 41; const MAX_POINTS_HUNTER_PET = 17; const MAX_POINTS_HUNTER_PET_BM = 21; const PLAYER_TREES_UNLOCK_POINT_THRESHOLD = 31; -export interface TalentsPickerConfig extends InputConfig, string> { +export interface TalentsPickerConfig extends InputConfig { klass: Class; - maxPoints: number; pointsPerRow: number; trees: TalentsConfig; } -export class TalentsPicker extends Input, string> { - private readonly player: Player; - private readonly config: TalentsPickerConfig; +export class TalentsPicker | HunterPet, TalentsProto> extends Input { + readonly modObject: ModObject; readonly numRows: number; readonly numCols: number; @@ -39,18 +37,29 @@ export class TalentsPicker extends Input, string> { maxPoints: number; + private readonly config: TalentsPickerConfig; + readonly trees: Array>; - constructor(parent: HTMLElement, player: Player, config: TalentsPickerConfig) { - super(parent, 'talents-picker-root', player, { ...config }); - this.player = player; + constructor(parent: HTMLElement, modObject: ModObject, config: TalentsPickerConfig) { + super(parent, 'talents-picker-root', modObject, { ...config }); + this.modObject = modObject; this.config = config; this.pointsPerRow = config.pointsPerRow; this.numRows = Math.max(...config.trees.map(treeConfig => treeConfig.talents.map(talentConfig => talentConfig.location.rowIdx).flat()).flat()) + 1; this.numCols = Math.max(...config.trees.map(treeConfig => treeConfig.talents.map(talentConfig => talentConfig.location.colIdx).flat()).flat()) + 1; - this.maxPoints = config.maxPoints; - const getPointsRemaining = () => this.maxPoints - player.getTalentTreePoints().reduce((sum, points) => sum + points, 0); + if (this.isHunterPet()) { + if (this.modObject.player.isSpec(Spec.SpecBeastMasteryHunter)) { + this.maxPoints = MAX_POINTS_HUNTER_PET_BM; + } else { + this.maxPoints = MAX_POINTS_HUNTER_PET; + } + } else { + this.maxPoints = MAX_POINTS_PLAYER; + } + + const getPointsRemaining = (): number => this.maxPoints - modObject.getTalentTreePoints().reduce((sum, points) => sum + points, 0); const containerElemRef = ref(); const pointsRemainingElemRef = ref(); @@ -90,16 +99,16 @@ export class TalentsPicker extends Input, string> { const carouselPrevBtn = carouselPrevBtnRef.value!; const carouselNextBtn = carouselNextBtnRef.value!; - player.talentsChangeEmitter.on(() => (pointsRemainingElemRef.value!.textContent = `${getPointsRemaining()}`)); + modObject.talentsChangeEmitter.on(() => (pointsRemainingElemRef.value!.textContent = `${getPointsRemaining()}`)); new CopyButton(actionsContainerRef.value!, { extraCssClasses: ['btn-sm', 'btn-outline-primary', 'copy-talents'], - getContent: () => player.getTalentsString(), + getContent: () => modObject.getTalentsString(), text: 'Copy', tooltip: 'Copy talent string', }); - this.trees = config.trees.map((treeConfig, i) => { + this.trees = this.config.trees.map((treeConfig, i) => { const carouselItem = document.createElement('div'); carouselContainer.appendChild(carouselItem); @@ -107,37 +116,36 @@ export class TalentsPicker extends Input, string> { // Set middle talents active by default for mobile slider if (i === 1) carouselItem.classList.add('active'); - // If using a hunter pet, add 3 to skip the hunter specs - if (treeConfig.name === 'Ferocity') i += 3; - if (treeConfig.name === 'Tenacity') i += 4; - if (treeConfig.name === 'Cunning') i += 5; - return new TalentTreePicker(carouselItem, treeConfig, this, config.klass, i); }); this.trees.forEach(tree => tree.talents.forEach(talent => talent.setPoints(0, false))); - this.disableSecondaryPlayerTrees(); - - let carouselitemIdx = 0; - const slidePrev = () => { - if (carouselitemIdx >= 1) return; - carouselitemIdx += 1; - carouselContainer.style.transform = `translateX(${33.3 * carouselitemIdx}%)`; - carouselContainer.children[Math.abs(carouselitemIdx - 2) % 3]!.classList.remove('active'); - carouselContainer.children[Math.abs(carouselitemIdx - 1) % 3]!.classList.add('active'); - }; - const slideNext = () => { - if (carouselitemIdx <= -1) return; - carouselitemIdx -= 1; - carouselContainer.style.transform = `translateX(${33.3 * carouselitemIdx}%)`; - carouselContainer.children[Math.abs(carouselitemIdx) % 3]!.classList.remove('active'); - carouselContainer.children[Math.abs(carouselitemIdx) + (1 % 3)]!.classList.add('active'); - }; - - carouselPrevBtn.addEventListener('click', slidePrev); - carouselNextBtn.addEventListener('click', slideNext); - - new GlyphsPicker(containerElem, player, classGlyphsConfig[player.getClass()]); + if (!this.isHunterPet()) { + this.disableSecondaryPlayerTrees(); + + let carouselitemIdx = 0; + const slidePrev = () => { + if (carouselitemIdx >= 1) return; + carouselitemIdx += 1; + carouselContainer.style.transform = `translateX(${33.3 * carouselitemIdx}%)`; + carouselContainer.children[Math.abs(carouselitemIdx - 2) % 3]!.classList.remove('active'); + carouselContainer.children[Math.abs(carouselitemIdx - 1) % 3]!.classList.add('active'); + }; + const slideNext = () => { + if (carouselitemIdx <= -1) return; + carouselitemIdx -= 1; + carouselContainer.style.transform = `translateX(${33.3 * carouselitemIdx}%)`; + carouselContainer.children[Math.abs(carouselitemIdx) % 3]!.classList.remove('active'); + carouselContainer.children[Math.abs(carouselitemIdx) + (1 % 3)]!.classList.add('active'); + }; + + carouselPrevBtn.addEventListener('click', slidePrev); + carouselNextBtn.addEventListener('click', slideNext); + + if (this.isPlayer()) { + new GlyphsPicker(containerElem, this.modObject, classGlyphsConfig[this.modObject.getClass()]); + } + } this.init(); } @@ -159,9 +167,8 @@ export class TalentsPicker extends Input, string> { this.updateTrees(); // Disable other player trees if the first tree is not filled to 31 points - if (!this.isHunterPet()) { - const specNumber = PlayerSpecs.getSpecNumber(this.player.getPlayerSpec()); - const pointsSpent = getTalentTreePoints(newValue)[specNumber]; + if (this.isPlayer()) { + const pointsSpent = getTalentTreePoints(newValue)[this.modObject.getTalentTree()]; // trying to use the bare minimum number of updates. 0 covers resetting the tree, -1 removing the 31st point if (pointsSpent == 0 || pointsSpent == PLAYER_TREES_UNLOCK_POINT_THRESHOLD - 1) { @@ -196,12 +203,16 @@ export class TalentsPicker extends Input, string> { } } - isHunterPet(): boolean { - return ['Cunning', 'Ferocity', 'Tenacity'].includes(this.config.trees[0].name); + isPlayer(): this is TalentsPicker, TalentsProto> { + return !!(this.modObject as unknown as Player)?.playerClass; + } + + isHunterPet(): this is TalentsPicker, TalentsProto> { + return !this.isPlayer(); } private disableSecondaryPlayerTrees() { - const specNumber = PlayerSpecs.getSpecNumber(this.player.getPlayerSpec()); + const specNumber = this.modObject.getTalentTree(); [0, 1, 2] .filter(i => ![specNumber].includes(i)) .forEach(i => { @@ -223,12 +234,12 @@ class TalentTreePicker extends Component { private readonly pointsElem: HTMLElement; readonly talents: Array>; - readonly picker: TalentsPicker; + readonly picker: TalentsPicker; // The current number of points in this tree numPoints: number; - constructor(parent: HTMLElement, config: TalentTreeConfig, picker: TalentsPicker, klass: Class, specNumber: number) { + constructor(parent: HTMLElement, config: TalentTreeConfig, picker: TalentsPicker, klass: Class, specNumber: number) { super(parent, 'talent-tree-picker-root'); this.config = config; this.numPoints = 0; @@ -237,7 +248,7 @@ class TalentTreePicker extends Component { this.rootElem.appendChild( <>
- +