Skip to content

Commit

Permalink
Merge pull request #3327 from wowsims/apl
Browse files Browse the repository at this point in the history
Organize unit metadatas in UI
  • Loading branch information
jimmyt857 authored Jul 16, 2023
2 parents fdebb3b + c34bf3a commit d8d27a4
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 56 deletions.
12 changes: 6 additions & 6 deletions ui/core/components/individual_sim_ui/apl_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const actionIdSets: Record<ACTION_ID_SET, {
['auras']: {
defaultLabel: 'Aura',
getActionIDs: async (player) => {
return player.getAuras().map(actionId => {
return player.getMetadata().getAuras().map(actionId => {
return {
value: actionId.id,
};
Expand All @@ -30,7 +30,7 @@ const actionIdSets: Record<ACTION_ID_SET, {
['stackable_auras']: {
defaultLabel: 'Aura',
getActionIDs: async (player) => {
return player.getAuras().filter(aura => aura.data.maxStacks > 0).map(actionId => {
return player.getMetadata().getAuras().filter(aura => aura.data.maxStacks > 0).map(actionId => {
return {
value: actionId.id,
};
Expand All @@ -40,7 +40,7 @@ const actionIdSets: Record<ACTION_ID_SET, {
['icd_auras']: {
defaultLabel: 'Aura',
getActionIDs: async (player) => {
return player.getAuras().filter(aura => aura.data.hasIcd).map(actionId => {
return player.getMetadata().getAuras().filter(aura => aura.data.hasIcd).map(actionId => {
return {
value: actionId.id,
};
Expand All @@ -50,7 +50,7 @@ const actionIdSets: Record<ACTION_ID_SET, {
['castable_spells']: {
defaultLabel: 'Spell',
getActionIDs: async (player) => {
const castableSpells = player.getSpells().filter(spell => spell.data.isCastable);
const castableSpells = player.getMetadata().getSpells().filter(spell => spell.data.isCastable);

// Split up non-cooldowns and cooldowns into separate sections for easier browsing.
const { 'spells': spells, 'cooldowns': cooldowns } = bucket(castableSpells, spell => spell.data.isMajorCooldown ? 'cooldowns' : 'spells');
Expand Down Expand Up @@ -96,7 +96,7 @@ const actionIdSets: Record<ACTION_ID_SET, {
['dot_spells']: {
defaultLabel: 'DoT Spell',
getActionIDs: async (player) => {
return player.getSpells().filter(spell => spell.data.hasDot).map(actionId => {
return player.getMetadata().getSpells().filter(spell => spell.data.hasDot).map(actionId => {
return {
value: actionId.id,
};
Expand Down Expand Up @@ -145,7 +145,7 @@ export class APLActionIDPicker extends DropdownPicker<Player<any>, ActionID, Act
this.setOptions(values);
};
updateValues();
player.currentSpellsAndAurasEmitter.on(updateValues);
player.sim.unitMetadataEmitter.on(updateValues);
}
}

Expand Down
4 changes: 2 additions & 2 deletions ui/core/components/individual_sim_ui/cooldowns_picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class CooldownsPicker extends Component {
this.player = player;
this.cooldownPickers = [];

TypedEvent.onAny([this.player.cooldownsChangeEmitter, this.player.currentSpellsAndAurasEmitter]).on(eventID => {
TypedEvent.onAny([this.player.cooldownsChangeEmitter, this.player.sim.unitMetadataEmitter]).on(eventID => {
this.update();
});
this.update();
Expand Down Expand Up @@ -127,7 +127,7 @@ export class CooldownsPicker extends Component {
}

private makeActionPicker(parentElem: HTMLElement, cooldownIndex: number): IconEnumPicker<Player<any>, ActionIdProto> {
const availableCooldowns = this.player.getSpells().filter(spell => spell.data.isMajorCooldown).map(spell => spell.id);
const availableCooldowns = this.player.getMetadata().getSpells().filter(spell => spell.data.isMajorCooldown).map(spell => spell.id);

const actionPicker = new IconEnumPicker<Player<any>, ActionIdProto>(parentElem, this.player, {
extraCssClasses: [
Expand Down
3 changes: 3 additions & 0 deletions ui/core/encounter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Stats } from './proto_utils/stats.js';
import * as Mechanics from './constants/mechanics.js';

import { Sim } from './sim.js';
import { UnitMetadataList } from './player.js';
import { EventID, TypedEvent } from './typed_event.js';

// Manages all the settings for an Encounter.
Expand All @@ -25,6 +26,7 @@ export class Encounter {
private executeProportion35: number = 0.35;
private useHealth: boolean = false;
targets: Array<TargetProto>;
targetsMetadata: UnitMetadataList;

readonly targetsChangeEmitter = new TypedEvent<void>();
readonly durationChangeEmitter = new TypedEvent<void>();
Expand All @@ -36,6 +38,7 @@ export class Encounter {
constructor(sim: Sim) {
this.sim = sim;
this.targets = [Encounter.defaultTargetProto()];
this.targetsMetadata = new UnitMetadataList();

[
this.targetsChangeEmitter,
Expand Down
129 changes: 86 additions & 43 deletions ui/core/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
import {
AuraStats as AuraStatsProto,
SpellStats as SpellStatsProto,
UnitMetadata as UnitMetadataProto,
} from './proto/api.js';
import {
APLRotation,
Expand Down Expand Up @@ -96,6 +97,81 @@ export interface SpellStats {
id: ActionId,
}

export class UnitMetadata {
private auras: Array<AuraStats>;
private spells: Array<SpellStats>;

constructor() {
this.auras = [];
this.spells = [];
}

getAuras(): Array<AuraStats> {
return this.auras.slice();
}

getSpells(): Array<SpellStats> {
return this.spells.slice();
}

// Returns whether any updates were made.
async update(metadata: UnitMetadataProto): Promise<boolean> {
let newSpells = metadata!.spells.map(spell => {
return {
data: spell,
id: ActionId.fromProto(spell.id!),
};
});
let newAuras = metadata!.auras.map(aura => {
return {
data: aura,
id: ActionId.fromProto(aura.id!),
};
});

await Promise.all([...newSpells, ...newAuras].map(newSpell => newSpell.id.fill().then(newId => newSpell.id = newId)));

newSpells = newSpells.sort((a, b) => stringComparator(a.id.name, b.id.name))
newAuras = newAuras.sort((a, b) => stringComparator(a.id.name, b.id.name))

let anyUpdates = false;
if (newSpells.length != this.spells.length || newSpells.some((newSpell, i) => !newSpell.id.equals(this.spells[i].id))) {
this.spells = newSpells;
anyUpdates = true;
}
if (newAuras.length != this.auras.length || newAuras.some((newAura, i) => !newAura.id.equals(this.auras[i].id))) {
this.auras = newAuras;
anyUpdates = true;
}

return anyUpdates;
}
}

export class UnitMetadataList {
private metadatas: Array<UnitMetadata>;

constructor() {
this.metadatas = [];
}

async update(newMetadatas: Array<UnitMetadataProto>): Promise<boolean> {
const oldLen = this.metadatas.length;

if (newMetadatas.length > oldLen) {
for (let i = oldLen; i < newMetadatas.length; i++) {
this.metadatas.push(new UnitMetadata());
}
} else if (newMetadatas.length < oldLen) {
this.metadatas = this.metadatas.slice(0, newMetadatas.length);
}

const anyUpdates = await Promise.all(newMetadatas.map((metadata, i) => this.metadatas[i].update(metadata)));

return oldLen != this.metadatas.length || anyUpdates.some(v => v);
}
}

// Manages all the gear / consumes / other settings for a single Player.
export class Player<SpecType extends Spec> {
readonly sim: Sim;
Expand Down Expand Up @@ -135,8 +211,8 @@ export class Player<SpecType extends Spec> {
private epRatios: Array<number> = new Array<number>(Player.numEpRatios).fill(0);
private epWeights: Stats = new Stats();
private currentStats: PlayerStats = PlayerStats.create();
private spells: Array<SpellStats> = [];
private auras: Array<AuraStats> = [];
private metadata: UnitMetadata = new UnitMetadata();
private petMetadatas: UnitMetadataList = new UnitMetadataList();

readonly nameChangeEmitter = new TypedEvent<void>('PlayerName');
readonly buffsChangeEmitter = new TypedEvent<void>('PlayerBuffs');
Expand All @@ -156,7 +232,6 @@ export class Player<SpecType extends Spec> {
readonly epWeightsChangeEmitter = new TypedEvent<void>('PlayerEpWeights');

readonly currentStatsEmitter = new TypedEvent<void>('PlayerCurrentStats');
readonly currentSpellsAndAurasEmitter = new TypedEvent<void>('PlayerCurrentSpellsAndAuras');
readonly epRatiosChangeEmitter = new TypedEvent<void>('PlayerEpRatios');

// Emits when any of the above emitters emit.
Expand Down Expand Up @@ -322,51 +397,19 @@ export class Player<SpecType extends Spec> {

setCurrentStats(eventID: EventID, newStats: PlayerStats) {
this.currentStats = newStats;
this.updateSpellsAndAuras(eventID);

this.currentStatsEmitter.emit(eventID);
}

getSpells(): Array<SpellStats> {
return this.spells.slice();
getMetadata(): UnitMetadata {
return this.metadata;
}

getAuras(): Array<AuraStats> {
return this.auras.slice();
}

private async updateSpellsAndAuras(eventID: EventID) {
let newSpells = this.currentStats.metadata!.spells.map(spell => {
return {
data: spell,
id: ActionId.fromProto(spell.id!),
};
});
let newAuras = this.currentStats.metadata!.auras.map(aura => {
return {
data: aura,
id: ActionId.fromProto(aura.id!),
};
});

await Promise.all([...newSpells, ...newAuras].map(newSpell => newSpell.id.fill().then(newId => newSpell.id = newId)));

newSpells = newSpells.sort((a, b) => stringComparator(a.id.name, b.id.name))
newAuras = newAuras.sort((a, b) => stringComparator(a.id.name, b.id.name))

let anyUpdates = false;
if (newSpells.length != this.spells.length || newSpells.some((newSpell, i) => !newSpell.id.equals(this.spells[i].id))) {
this.spells = newSpells;
anyUpdates = true;
}
if (newAuras.length != this.auras.length || newAuras.some((newAura, i) => !newAura.id.equals(this.auras[i].id))) {
this.auras = newAuras;
anyUpdates = true;
}

if (anyUpdates) {
this.currentSpellsAndAurasEmitter.emit(eventID);
}
async updateMetadata(): Promise<boolean> {
const playerPromise = this.metadata.update(this.currentStats.metadata!);
const petsPromise = this.petMetadatas.update(this.currentStats.pets.map(p => p.metadata!));
const playerUpdated = await playerPromise;
const petsUpdated = await petsPromise;
return playerUpdated || petsUpdated;
}

getName(): string {
Expand Down
29 changes: 24 additions & 5 deletions ui/core/sim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ export class Sim {
// Emits when any of the settings change (but not the raid / encounter).
readonly settingsChangeEmitter: TypedEvent<void>;

// Emits when any player, target, or pet has metadata changes (spells or auras).
readonly unitMetadataEmitter = new TypedEvent<void>('UnitMetadata');

// Emits when any of the above emitters emit.
readonly changeEmitter: TypedEvent<void>;

Expand Down Expand Up @@ -288,11 +291,27 @@ export class Sim {
return;
}

TypedEvent.freezeAllAndDo(() => {
result.raidStats!.parties
.forEach((partyStats, partyIndex) =>
partyStats.players.forEach((playerStats, playerIndex) =>
players[partyIndex * 5 + playerIndex]?.setCurrentStats(eventID, playerStats)));
TypedEvent.freezeAllAndDo(async () => {
const playerUpdatePromises = result.raidStats!.parties
.map((partyStats, partyIndex) =>
partyStats.players.map((playerStats, playerIndex) => {
const player = players[partyIndex * 5 + playerIndex];
if (player) {
player.setCurrentStats(eventID, playerStats);
return player.updateMetadata();
} else {
return null;
}
}))
.flat()
.filter(p => p != null) as Array<Promise<boolean>>;

const targetUpdatePromise = this.encounter.targetsMetadata.update(result.encounterStats!.targets.map(t => t.metadata!));

const anyUpdates = await Promise.all(playerUpdatePromises.concat([targetUpdatePromise]));
if (anyUpdates.some(v => v)) {
this.unitMetadataEmitter.emit(eventID);
}
});
}

Expand Down

0 comments on commit d8d27a4

Please sign in to comment.