diff --git a/ui/raid/import_export.ts b/ui/raid/import_export.ts index eba1aabc48..099287fba9 100644 --- a/ui/raid/import_export.ts +++ b/ui/raid/import_export.ts @@ -1,7 +1,8 @@ import { Exporter } from '../core/components/exporters'; import { Importer } from '../core/components/importers'; -import { RaidSimSettings } from '../core/proto/ui'; -import { EventID, TypedEvent } from '../core/typed_event'; +import { Encounter } from '../core/encounter'; +import { RaidSimPreset } from '../core/individual_sim_ui'; +import { Player } from '../core/player'; import { Party as PartyProto, Player as PlayerProto, Raid as RaidProto } from '../core/proto/api'; import { Class, @@ -11,30 +12,28 @@ import { ItemSpec, Profession, Race, - UnitReference, Spec, Target as TargetProto, + UnitReference, UnitReference_Type, } from '../core/proto/common'; +import { RaidSimSettings } from '../core/proto/ui'; import { professionNames, raceNames } from '../core/proto_utils/names'; import { - DruidSpecs, DeathknightSpecs, - PriestSpecs, - RogueSpecs, - SpecOptions, + DruidSpecs, getTalentTreePoints, - makeDefaultBlessings, - raceToFaction, isTankSpec, + makeDefaultBlessings, playerToSpec, + PriestSpecs, + raceToFaction, + RogueSpecs, + SpecOptions, } from '../core/proto_utils/utils'; import { MAX_NUM_PARTIES } from '../core/raid'; -import { RaidSimPreset } from '../core/individual_sim_ui'; -import { Player } from '../core/player'; -import { Encounter } from '../core/encounter'; +import { EventID, TypedEvent } from '../core/typed_event'; import { bucket, distinct } from '../core/utils'; - import { playerPresets } from './presets'; import { RaidSimUI } from './raid_sim_ui'; @@ -54,7 +53,7 @@ export class RaidJsonImporter extends Importer { `; } - onImport(data: string) { + async onImport(data: string) { const settings = RaidSimSettings.fromJsonString(data, { ignoreUnknownFields: true }); this.simUI.fromProto(TypedEvent.nextEventID(), settings); this.close(); @@ -65,7 +64,7 @@ export class RaidJsonExporter extends Exporter { private readonly simUI: RaidSimUI; constructor(parent: HTMLElement, simUI: RaidSimUI) { - super(parent, simUI, {title: 'JSON Export', allowDownload: true}); + super(parent, simUI, { title: 'JSON Export', allowDownload: true }); this.simUI = simUI; this.init(); } @@ -76,8 +75,7 @@ export class RaidJsonExporter extends Exporter { } export class RaidWCLImporter extends Importer { - - private queryCounter: number = 0; + private queryCounter = 0; private readonly simUI: RaidSimUI; constructor(parent: HTMLElement, simUI: RaidSimUI) { @@ -121,18 +119,18 @@ export class RaidWCLImporter extends Importer { `; } - private token: string = ''; + private token = ''; private async getWCLBearerToken(): Promise { if (this.token == '') { const response = await fetch('https://classic.warcraftlogs.com/oauth/token', { - 'method': 'POST', - 'headers': { - 'Authorization': 'Basic ' + btoa('963d31c8-7efa-4dde-87cf-1b254a8a2f8c:lRJVhujEEnF96xfUoxVHSpnqKN9v8bTqGEjutsO3'), + method: 'POST', + headers: { + Authorization: 'Basic ' + btoa('963d31c8-7efa-4dde-87cf-1b254a8a2f8c:lRJVhujEEnF96xfUoxVHSpnqKN9v8bTqGEjutsO3'), }, body: new URLSearchParams({ - 'grant_type': 'client_credentials', + grant_type: 'client_credentials', }), - }) + }); const json = await response.json(); this.token = json.access_token; } @@ -143,8 +141,8 @@ export class RaidWCLImporter extends Importer { const token = await this.getWCLBearerToken(); const headers = { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, - 'Accept': 'application/json', + Authorization: `Bearer ${token}`, + Accept: 'application/json', }; const queryURL = `https://classic.warcraftlogs.com/api/v2/client?query=${query}`; @@ -152,8 +150,8 @@ export class RaidWCLImporter extends Importer { // Query WCL const res = await fetch(encodeURI(queryURL), { - 'method': 'GET', - 'headers': headers, + method: 'GET', + headers: headers, }); const result = await res.json(); @@ -175,7 +173,7 @@ export class RaidWCLImporter extends Importer { const urlData = { reportID: match[1], fightID: '', - } + }; // If the URL has a Fight ID in it, use it if (match[2] && match[3] && match[3] != 'last') { @@ -196,7 +194,7 @@ export class RaidWCLImporter extends Importer { const fights = fightData.data.reportData.report.fights; if (match[3] == 'last') { - urlData.fightID = String(fights[fights.length - 1].id) + urlData.fightID = String(fights[fights.length - 1].id); } else { // Default to using the first Fight urlData.fightID = String(fights[0].id); @@ -227,7 +225,7 @@ export class RaidWCLImporter extends Importer { console.error(error); alert('Failed import from WCL: ' + error); } - this.importButton.disabled = false + this.importButton.disabled = false; this.rootElem.style.removeProperty('cursor'); } @@ -255,14 +253,23 @@ export class RaidWCLImporter extends Importer { startTime, endTime, id, name } - reportCastEvents: events(dataType:Casts, endTime: 99999999, filterExpression: "${[racialSpells, professionSpells].flat().map(spell => spell.id).map(id => `ability.id = ${id}`).join(' OR ') - }", limit: 10000) { data } + reportCastEvents: events(dataType:Casts, endTime: 99999999, filterExpression: "${[racialSpells, professionSpells] + .flat() + .map(spell => spell.id) + .map(id => `ability.id = ${id}`) + .join(' OR ')}", limit: 10000) { data } - fightCastEvents: events(fightIDs: [${urlData.fightID}], dataType:Casts, filterExpression: "${[externalCDSpells].flat().map(spell => spell.id).map(id => `ability.id = ${id}`).join(' OR ') - }", limit: 10000) { data } + fightCastEvents: events(fightIDs: [${urlData.fightID}], dataType:Casts, filterExpression: "${[externalCDSpells] + .flat() + .map(spell => spell.id) + .map(id => `ability.id = ${id}`) + .join(' OR ')}", limit: 10000) { data } - fightHealEvents: events(fightIDs: [${urlData.fightID}], dataType:Healing, filterExpression: "${[samePartyHealingSpells, otherPartyHealingSpells].flat().map(spell => spell.id).map(id => `ability.id = ${id}`).join(' OR ') - }", limit: 10000) { data } + fightHealEvents: events(fightIDs: [${urlData.fightID}], dataType:Healing, filterExpression: "${[samePartyHealingSpells, otherPartyHealingSpells] + .flat() + .map(spell => spell.id) + .map(id => `ability.id = ${id}`) + .join(' OR ')}", limit: 10000) { data } manaTideTotem: events(fightIDs: [${urlData.fightID}], dataType:Resources, filterExpression: "ability.id = 39609", limit: 100) { data } } @@ -466,7 +473,7 @@ export class RaidWCLImporter extends Importer { } private getEncounterProto(wclData: any): EncounterProto { - const fight: { startTime: number, endTime: number, id: number, name: string } = wclData.fights[0]; + const fight: { startTime: number; endTime: number; id: number; name: string } = wclData.fights[0]; const encounter = EncounterProto.create({ duration: (fight.endTime - fight.startTime) / 1000, @@ -474,7 +481,7 @@ export class RaidWCLImporter extends Importer { }); // Use the preset encounter if it exists. - let closestEncounterPreset = this.simUI.sim.db.getAllPresetEncounters().find(enc => enc.path.includes(fight.name)); + const closestEncounterPreset = this.simUI.sim.db.getAllPresetEncounters().find(enc => enc.path.includes(fight.name)); if (closestEncounterPreset && closestEncounterPreset.targets.length) { closestEncounterPreset.targets .map(mob => mob.target as TargetProto) @@ -492,22 +499,23 @@ export class RaidWCLImporter extends Importer { private getRaidProto(wclPlayers: WCLSimPlayer[]): RaidProto { const raid = RaidProto.create({ - parties: [...new Array(MAX_NUM_PARTIES).keys()].map(_party => PartyProto.create({ - players: [...new Array(5).keys()].map(_player => PlayerProto.create()), - })), + parties: [...new Array(MAX_NUM_PARTIES).keys()].map(_party => + PartyProto.create({ + players: [...new Array(5).keys()].map(_player => PlayerProto.create()), + }), + ), }); - wclPlayers - .forEach(player => { - const positionInParty = player.raidIndex % 5; - const partyIdx = (player.raidIndex - positionInParty) / 5; - const playerProto = player.player.toProto(); - raid.parties[partyIdx].players[positionInParty] = playerProto; + wclPlayers.forEach(player => { + const positionInParty = player.raidIndex % 5; + const partyIdx = (player.raidIndex - positionInParty) / 5; + const playerProto = player.player.toProto(); + raid.parties[partyIdx].players[positionInParty] = playerProto; - if (isTankSpec(playerToSpec(playerProto))) { - raid.tanks.push(player.toUnitReference()); - } - }); + if (isTankSpec(playerToSpec(playerProto))) { + raid.tanks.push(player.toUnitReference()); + } + }); return raid; } @@ -518,7 +526,7 @@ class WCLSimPlayer { public readonly id: number; public readonly name: string; public readonly type: string; - public raidIndex: number = -1; + public raidIndex = -1; private readonly simUI: RaidSimUI; private readonly fullType: string; @@ -552,11 +560,14 @@ class WCLSimPlayer { this.preset = WCLSimPlayer.getMatchingPreset(foundSpec, data.talents); if (this.preset === undefined) { - throw new Error('Could not find matching preset: ' + JSON.stringify({ - 'name': this.name, - 'type': this.fullType, - 'talents': data.talents, - }).toString()); + throw new Error( + 'Could not find matching preset: ' + + JSON.stringify({ + name: this.name, + type: this.fullType, + talents: data.talents, + }).toString(), + ); } // Apply preset defaults. @@ -569,17 +580,24 @@ class WCLSimPlayer { // Apply settings from report data. this.player.setName(eventID, data.name); - this.player.setGear(eventID, simUI.sim.db.lookupEquipmentSpec(EquipmentSpec.create({ - items: data.gear.map(gear => ItemSpec.create({ - id: gear.id, - enchant: gear.permanentEnchant, - gems: gear.gems ? gear.gems.map(gemInfo => gemInfo.id) : [], - })), - }))); + this.player.setGear( + eventID, + simUI.sim.db.lookupEquipmentSpec( + EquipmentSpec.create({ + items: data.gear.map(gear => + ItemSpec.create({ + id: gear.id, + enchant: gear.permanentEnchant, + gems: gear.gems ? gear.gems.map(gemInfo => gemInfo.id) : [], + }), + ), + }), + ), + ); } private static getMatchingPreset(spec: Spec, talents: wclTalents[]): RaidSimPreset { - const matchingPresets = playerPresets.filter((preset) => preset.spec == spec); + const matchingPresets = playerPresets.filter(preset => preset.spec == spec); let presetIdx = 0; if (matchingPresets && matchingPresets.length > 1) { @@ -588,7 +606,7 @@ class WCLSimPlayer { matchingPresets.forEach((preset, i) => { const presetTalents = getTalentTreePoints(preset.talents.talentsString); // Diff the distance to the preset. - const newDistance = presetTalents.reduce((acc, v, i) => acc += Math.abs(talents[i]?.guid - presetTalents[i]), 0); + const newDistance = presetTalents.reduce((acc, v, i) => (acc += Math.abs(talents[i]?.guid - presetTalents[i])), 0); // If this is the best distance, assign this preset. if (newDistance < distance) { @@ -617,50 +635,50 @@ class WCLSimPlayer { } const fullTypeToSpec: Record = { - 'DeathKnightBlood': Spec.SpecTankDeathknight, - 'DeathKnightLichborne': Spec.SpecTankDeathknight, - 'DeathKnightRuneblade': Spec.SpecDeathknight, - 'DeathKnightBloodDPS': Spec.SpecDeathknight, - 'DeathKnightFrost': Spec.SpecDeathknight, - 'DeathKnightUnholy': Spec.SpecDeathknight, - 'DruidBalance': Spec.SpecBalanceDruid, - 'DruidFeral': Spec.SpecFeralDruid, - 'DruidWarden': Spec.SpecFeralTankDruid, - 'DruidGuardian': Spec.SpecFeralTankDruid, - 'DruidRestoration': Spec.SpecRestorationDruid, - 'HunterBeastMastery': Spec.SpecHunter, - 'HunterSurvival': Spec.SpecHunter, - 'HunterMarksmanship': Spec.SpecHunter, - 'MageArcane': Spec.SpecMage, - 'MageFire': Spec.SpecMage, - 'MageFrost': Spec.SpecMage, - 'PaladinHoly': Spec.SpecHolyPaladin, - 'PaladinJusticar': Spec.SpecProtectionPaladin, - 'PaladinProtection': Spec.SpecProtectionPaladin, - 'PaladinRetribution': Spec.SpecRetributionPaladin, - 'PriestHoly': Spec.SpecHealingPriest, - 'PriestDiscipline': Spec.SpecHealingPriest, - 'PriestShadow': Spec.SpecShadowPriest, - 'PriestSmite': Spec.SpecSmitePriest, - 'RogueAssassination': Spec.SpecRogue, - 'RogueCombat': Spec.SpecRogue, - 'RogueSubtlety': Spec.SpecRogue, - 'ShamanElemental': Spec.SpecElementalShaman, - 'ShamanEnhancement': Spec.SpecEnhancementShaman, - 'ShamanRestoration': Spec.SpecRestorationShaman, - 'WarlockDestruction': Spec.SpecWarlock, - 'WarlockAffliction': Spec.SpecWarlock, - 'WarlockDemonology': Spec.SpecWarlock, - 'WarriorArms': Spec.SpecWarrior, - 'WarriorFury': Spec.SpecWarrior, - 'WarriorChampion': Spec.SpecWarrior, - 'WarriorWarrior': Spec.SpecWarrior, - 'WarriorGladiator': Spec.SpecWarrior, - 'WarriorProtection': Spec.SpecProtectionWarrior, + DeathKnightBlood: Spec.SpecTankDeathknight, + DeathKnightLichborne: Spec.SpecTankDeathknight, + DeathKnightRuneblade: Spec.SpecDeathknight, + DeathKnightBloodDPS: Spec.SpecDeathknight, + DeathKnightFrost: Spec.SpecDeathknight, + DeathKnightUnholy: Spec.SpecDeathknight, + DruidBalance: Spec.SpecBalanceDruid, + DruidFeral: Spec.SpecFeralDruid, + DruidWarden: Spec.SpecFeralTankDruid, + DruidGuardian: Spec.SpecFeralTankDruid, + DruidRestoration: Spec.SpecRestorationDruid, + HunterBeastMastery: Spec.SpecHunter, + HunterSurvival: Spec.SpecHunter, + HunterMarksmanship: Spec.SpecHunter, + MageArcane: Spec.SpecMage, + MageFire: Spec.SpecMage, + MageFrost: Spec.SpecMage, + PaladinHoly: Spec.SpecHolyPaladin, + PaladinJusticar: Spec.SpecProtectionPaladin, + PaladinProtection: Spec.SpecProtectionPaladin, + PaladinRetribution: Spec.SpecRetributionPaladin, + PriestHoly: Spec.SpecHealingPriest, + PriestDiscipline: Spec.SpecHealingPriest, + PriestShadow: Spec.SpecShadowPriest, + PriestSmite: Spec.SpecSmitePriest, + RogueAssassination: Spec.SpecRogue, + RogueCombat: Spec.SpecRogue, + RogueSubtlety: Spec.SpecRogue, + ShamanElemental: Spec.SpecElementalShaman, + ShamanEnhancement: Spec.SpecEnhancementShaman, + ShamanRestoration: Spec.SpecRestorationShaman, + WarlockDestruction: Spec.SpecWarlock, + WarlockAffliction: Spec.SpecWarlock, + WarlockDemonology: Spec.SpecWarlock, + WarriorArms: Spec.SpecWarrior, + WarriorFury: Spec.SpecWarrior, + WarriorChampion: Spec.SpecWarrior, + WarriorWarrior: Spec.SpecWarrior, + WarriorGladiator: Spec.SpecWarrior, + WarriorProtection: Spec.SpecProtectionWarrior, }; // Spells which imply a specific Race. -const racialSpells: Array<{ id: number, name: string, race: Race }> = [ +const racialSpells: Array<{ id: number; name: string; race: Race }> = [ { id: 25046, name: 'Arcane Torrent (Energy)', race: Race.RaceBloodElf }, { id: 28730, name: 'Arcane Torrent (Mana)', race: Race.RaceBloodElf }, { id: 50613, name: 'Arcane Torrent (Runic Power)', race: Race.RaceBloodElf }, @@ -676,44 +694,56 @@ const racialSpells: Array<{ id: number, name: string, race: Race }> = [ ]; // Spells which imply a specific Profession. -const professionSpells: Array<{ id: number, name: string, profession: Profession }> = [ +const professionSpells: Array<{ id: number; name: string; profession: Profession }> = [ { id: 55503, name: 'Lifeblood', profession: Profession.Herbalism }, { id: 50305, name: 'Skinning', profession: Profession.Skinning }, ]; -const externalCDSpells: Array<{ id: number, name: string, class: Class, applyFunc: (player: Player, raidTarget: UnitReference) => SpecOptions }> = [ +const externalCDSpells: Array<{ id: number; name: string; class: Class; applyFunc: (player: Player, raidTarget: UnitReference) => SpecOptions }> = [ { - id: 29166, name: 'Innervate', class: Class.ClassDruid, applyFunc: (player: Player, raidTarget: UnitReference) => { + id: 29166, + name: 'Innervate', + class: Class.ClassDruid, + applyFunc: (player: Player, raidTarget: UnitReference) => { const options = player.getSpecOptions() as SpecOptions; options.innervateTarget = raidTarget; return options; - } + }, }, { - id: 10060, name: 'Power Infusion', class: Class.ClassPriest, applyFunc: (player: Player, raidTarget: UnitReference) => { + id: 10060, + name: 'Power Infusion', + class: Class.ClassPriest, + applyFunc: (player: Player, raidTarget: UnitReference) => { const options = player.getSpecOptions() as SpecOptions; options.powerInfusionTarget = raidTarget; return options; - } + }, }, { - id: 57933, name: 'Tricks of the Trade', class: Class.ClassRogue, applyFunc: (player: Player, raidTarget: UnitReference) => { + id: 57933, + name: 'Tricks of the Trade', + class: Class.ClassRogue, + applyFunc: (player: Player, raidTarget: UnitReference) => { const options = player.getSpecOptions() as SpecOptions; options.tricksOfTheTradeTarget = raidTarget; return options; - } + }, }, { - id: 49016, name: 'Unholy Frenzy', class: Class.ClassDeathknight, applyFunc: (player: Player, raidTarget: UnitReference) => { + id: 49016, + name: 'Unholy Frenzy', + class: Class.ClassDeathknight, + applyFunc: (player: Player, raidTarget: UnitReference) => { const options = player.getSpecOptions() as SpecOptions; options.unholyFrenzyTarget = raidTarget; return options; - } + }, }, ]; // Healing spells which only affect the caster's party. -const samePartyHealingSpells: Array<{ id: number, name: string }> = [ +const samePartyHealingSpells: Array<{ id: number; name: string }> = [ { id: 52042, name: 'Healing Stream Totem' }, { id: 48076, name: 'Holy Nova' }, { id: 48445, name: 'Tranquility' }, @@ -721,17 +751,15 @@ const samePartyHealingSpells: Array<{ id: number, name: string }> = [ ]; // Healing spells which only affect a single party, but not necessarily the caster's party. -const otherPartyHealingSpells: Array<{ id: number, name: string }> = [ - { id: 48072, name: 'Prayer of Healing' }, -]; +const otherPartyHealingSpells: Array<{ id: number; name: string }> = [{ id: 48072, name: 'Prayer of Healing' }]; interface wclUrlData { - reportID: string, - fightID: string, + reportID: string; + fightID: string; } interface wclCastEvent { - type: 'cast', + type: 'cast'; timestamp: number; sourceID: number; targetID: number; @@ -740,7 +768,7 @@ interface wclCastEvent { } interface wclHealEvent { - type: 'heal', + type: 'heal'; timestamp: number; sourceID: number; targetID: number; @@ -760,9 +788,9 @@ interface wclCombatantInfoEvent { } interface wclRateLimitData { - limitPerHour: number, - pointsSpentThisHour: number, - pointsResetIn: number + limitPerHour: number; + pointsSpentThisHour: number; + pointsResetIn: number; } // Typed interface for WCL talents @@ -821,7 +849,7 @@ interface _wclAura { totalUptime: number; totalUses: number; bands: { - startTime: number, - endTime: number, + startTime: number; + endTime: number; }[]; }