Skip to content

Commit

Permalink
hunter pet talents working
Browse files Browse the repository at this point in the history
  • Loading branch information
kayla-glick committed Mar 17, 2024
1 parent 91234a8 commit 3baec8a
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 219 deletions.
18 changes: 6 additions & 12 deletions ui/core/components/individual_sim_ui/talents_tab.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -53,7 +50,6 @@ export class TalentsTab<SpecType extends Spec> extends SimTab {
player.setTalentsString(eventID, newValue);
},
pointsPerRow: 5,
maxPoints: Mechanics.MAX_TALENT_POINTS,
});
}

Expand Down Expand Up @@ -99,15 +95,13 @@ export class TalentsTab<SpecType extends Spec> 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<T extends HunterSpecs>(parentElem: HTMLElement) {
new HunterPetTalentsPicker(parentElem, this.simUI, this.simUI.player as unknown as Player<T>);
new IconEnumPicker(parentElem, this.simUI.player as unknown as Player<T>, makePetTypeInputConfig());
private buildHunterPetPicker(parentElem: HTMLElement) {
if (this.simUI.player.isClass(Class.ClassHunter)) {
new HunterPetTalentsPicker(parentElem, this.simUI, this.simUI.player);
}
}

private buildSavedTalentsPicker() {
Expand Down
1 change: 0 additions & 1 deletion ui/core/constants/mechanics.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
5 changes: 3 additions & 2 deletions ui/core/individual_sim_ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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__';
Expand Down Expand Up @@ -233,9 +234,9 @@ export abstract class IndividualSimUI<SpecType extends Spec> 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 '';
Expand Down
273 changes: 143 additions & 130 deletions ui/core/talents/hunter_pet.ts → ui/core/talents/hunter_pet.tsx
Original file line number Diff line number Diff line change
@@ -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<SpecType extends HunterSpecs>(): InputHelpers.TypedIconEnumPickerConfig<Player<SpecType>, PetType> {
return InputHelpers.makeClassOptionsEnumIconInput<SpecType, PetType>({
extraCssClasses: ['pet-type-picker'],
fieldName: 'petType',
numColumns: 5,
values: [
Expand Down Expand Up @@ -100,130 +106,6 @@ const petCategories: Record<PetType, PetCategory> = {
const categoryOrder = [PetCategory.Cunning, PetCategory.Ferocity, PetCategory.Tenacity];
const categoryClasses = ['cunning', 'ferocity', 'tenacity'];

export class HunterPetTalentsPicker<SpecType extends HunterSpecs> extends Component {
private readonly simUI: SimUI;
private readonly player: Player<SpecType>;
private curCategory: PetCategory | null;
private curTalents: HunterPetTalents;

// Not saved to storage, just holds last-used values for this session.
private savedSets: Array<HunterPetTalents>;

constructor(parent: HTMLElement, simUI: SimUI, player: Player<SpecType>) {
super(parent, 'hunter-pet-talents-picker');
this.simUI = simUI;
this.player = player;

this.rootElem.innerHTML = `
<div class="pet-talents-container"></div>
`;

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<SpecType>) => player.specOptionsChangeEmitter,
getValue: (_player: Player<SpecType>) => protoToTalentString(this.getPetTalentsFromPlayer(), talentsConfig),
setValue: (eventID: EventID, player: Player<SpecType>, 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<Player<SpecType>, string>(pickerContainer, this.player, {
presetsOnly: true,
label: 'Pet Talents',
storageKey: '__NEVER_USED__',
getData: (_player: Player<SpecType>) => protoToTalentString(this.getPetTalentsFromPlayer(), talentsConfig),
setData: (eventID: EventID, player: Player<SpecType>, 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<HunterPetTalents> {
const petCategory = petCategories[petType];
const categoryIdx = categoryOrder.indexOf(petCategory);
Expand Down Expand Up @@ -309,8 +191,139 @@ export const tenacityBMDefault: HunterPetTalents = HunterPetTalents.create({
});
const defaultBMTalents = [cunningBMDefault, ferocityBMDefault, tenacityBMDefault];

const cunningPetTalentsConfig: TalentsConfig<HunterPetTalents> = newTalentsConfig(HunterPetCunningJson);
const ferocityPetTalentsConfig: TalentsConfig<HunterPetTalents> = newTalentsConfig(HunterPetFerocityJson);
const tenacityPetTalentsConfig: TalentsConfig<HunterPetTalents> = newTalentsConfig(HunterPetTenacityJson);
export const cunningPetTalentsConfig: TalentsConfig<HunterPetTalents> = newTalentsConfig(HunterPetCunningJson);
export const ferocityPetTalentsConfig: TalentsConfig<HunterPetTalents> = newTalentsConfig(HunterPetFerocityJson);
export const tenacityPetTalentsConfig: TalentsConfig<HunterPetTalents> = newTalentsConfig(HunterPetTenacityJson);

export const petTalentsConfig = [cunningPetTalentsConfig, ferocityPetTalentsConfig, tenacityPetTalentsConfig];

export class HunterPet<SpecType extends HunterSpecs> {
readonly player: Player<SpecType>;

private talents: HunterPetTalents;
private talentsConfig: TalentsConfig<HunterPetTalents>;
private talentsString: string;

readonly talentsChangeEmitter: TypedEvent<void>;

constructor(player: Player<SpecType>) {
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<number> {
return getTalentTreePoints(this.getTalentsString());
}
}

export class HunterPetTalentsPicker<SpecType extends HunterSpecs> extends Component {
private readonly simUI: SimUI;
private readonly player: Player<SpecType>;
private curCategory: PetCategory | null;
private curTalents: HunterPetTalents;

// Not saved to storage, just holds last-used values for this session.
private savedSets: Array<HunterPetTalents>;

constructor(parent: HTMLElement, simUI: SimUI, player: Player<SpecType>) {
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 = <div className="pet-talents-container" />;
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<SpecType>) => pet.player.specOptionsChangeEmitter,
getValue: (pet: HunterPet<SpecType>) => pet.getTalentsString(),
setValue: (eventID: EventID, pet: HunterPet<SpecType>, 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];
}
}
Loading

0 comments on commit 3baec8a

Please sign in to comment.