diff --git a/proto/apl.proto b/proto/apl.proto index 5242fb53bb..627423b280 100644 --- a/proto/apl.proto +++ b/proto/apl.proto @@ -9,7 +9,7 @@ import "shaman.proto"; // Rotation options are based heavily on APL. See https://github.com/simulationcraft/simc/wiki/ActionLists. message APLRotation { - bool enabled = 20; // If false, use old rotation options. + bool enabled = 20 [deprecated = true]; // If false, use old rotation options. enum Type { TypeUnknown = 0; diff --git a/ui/core/components/individual_sim_ui/rotation_tab.ts b/ui/core/components/individual_sim_ui/rotation_tab.ts index bae4113734..10f6745ab8 100644 --- a/ui/core/components/individual_sim_ui/rotation_tab.ts +++ b/ui/core/components/individual_sim_ui/rotation_tab.ts @@ -60,10 +60,7 @@ export class RotationTab extends SimTab { this.buildAutoContent(); this.buildAplContent(); - - // Legacy - this.buildRotationSettings(); - this.buildCooldownSettings(); + this.buildSimpleOrLegacyContent(this.simUI.player.hasSimpleRotationGenerator()); this.buildSavedDataPickers(); } @@ -100,10 +97,17 @@ export class RotationTab extends SimTab { values: aplLaunchStatus == LaunchStatus.Alpha ? [ { value: APLRotationType.TypeLegacy, name: 'Legacy' }, { value: APLRotationType.TypeAPL, name: 'APL' }, - ] : [ + ] : aplLaunchStatus == LaunchStatus.Beta ? [ { value: APLRotationType.TypeAuto, name: 'Auto' }, { value: APLRotationType.TypeAPL, name: 'APL' }, { value: APLRotationType.TypeLegacy, name: 'Legacy' }, + ] : this.simUI.player.hasSimpleRotationGenerator() ? [ + { value: APLRotationType.TypeAuto, name: 'Auto' }, + { value: APLRotationType.TypeSimple, name: 'Simple' }, + { value: APLRotationType.TypeAPL, name: 'APL' }, + ] : [ + { value: APLRotationType.TypeAuto, name: 'Auto' }, + { value: APLRotationType.TypeAPL, name: 'APL' }, ], changedEvent: (player: Player) => player.rotationChangeEmitter, getValue: (player: Player) => player.getRotationType(), @@ -129,11 +133,13 @@ export class RotationTab extends SimTab { new APLRotationPicker(content, this.simUI, this.simUI.player); } - private buildRotationSettings() { + private buildSimpleOrLegacyContent(isSimple: boolean) { + const cssClass = isSimple ? 'rotation-tab-simple' : 'rotation-tab-legacy'; + const contentBlock = new ContentBlock(this.leftPanel, 'rotation-settings', { header: { title: 'Rotation' } }); - contentBlock.rootElem.classList.add('rotation-tab-legacy'); + contentBlock.rootElem.classList.add(cssClass); const rotationIconGroup = Input.newGroupContainer(); rotationIconGroup.classList.add('rotation-icon-group', 'icon-group'); @@ -152,15 +158,13 @@ export class RotationTab extends SimTab { contentBlock.bodyElement.querySelectorAll('.input-root').forEach(elem => { elem.classList.add('input-inline'); }) - } - private buildCooldownSettings() { - const contentBlock = new ContentBlock(this.leftPanel, 'cooldown-settings', { + const cooldownsContentBlock = new ContentBlock(this.leftPanel, 'cooldown-settings', { header: { title: 'Cooldowns', tooltip: Tooltips.COOLDOWNS_SECTION } }); - contentBlock.rootElem.classList.add('rotation-tab-legacy'); + cooldownsContentBlock.rootElem.classList.add(cssClass); - new CooldownsPicker(contentBlock.bodyElement, this.simUI.player); + new CooldownsPicker(cooldownsContentBlock.bodyElement, this.simUI.player); } private configureInputSection(sectionElem: HTMLElement, sectionConfig: InputSection) { @@ -192,28 +196,32 @@ export class RotationTab extends SimTab { } private buildSavedDataPickers() { + const launchStatus = aplLaunchStatuses[this.simUI.player.spec]; + const savedRotationsManager = new SavedDataManager, SavedRotation>(this.rightPanel, this.simUI, this.simUI.player, { label: 'Rotation', header: { title: 'Saved Rotations' }, storageKey: this.simUI.getSavedRotationStorageKey(), getData: (player: Player) => SavedRotation.create({ rotation: APLRotation.clone(player.aplRotation), - specRotationOptionsJson: JSON.stringify(player.specTypeFunctions.rotationToJson(player.getRotation())), - cooldowns: player.getCooldowns(), + specRotationOptionsJson: launchStatus == LaunchStatus.Launched ? '' : JSON.stringify(player.specTypeFunctions.rotationToJson(player.getRotation())), + cooldowns: LaunchStatus.Launched ? undefined : player.getCooldowns(), }), setData: (eventID: EventID, player: Player, newRotation: SavedRotation) => { TypedEvent.freezeAllAndDo(() => { player.setAplRotation(eventID, newRotation.rotation || APLRotation.create()); - if (newRotation.specRotationOptionsJson) { - try { - const json = JSON.parse(newRotation.specRotationOptionsJson); - const specRot = player.specTypeFunctions.rotationFromJson(json); - player.setRotation(eventID, specRot); - } catch (e) { - console.warn('Error parsing rotation spec options: ' + e); + if (launchStatus != LaunchStatus.Launched) { + if (newRotation.specRotationOptionsJson) { + try { + const json = JSON.parse(newRotation.specRotationOptionsJson); + const specRot = player.specTypeFunctions.rotationFromJson(json); + player.setRotation(eventID, specRot); + } catch (e) { + console.warn('Error parsing rotation spec options: ' + e); + } } + player.setCooldowns(eventID, newRotation.cooldowns || Cooldowns.create()); } - player.setCooldowns(eventID, newRotation.cooldowns || Cooldowns.create()); }); }, changeEmitters: [this.simUI.player.rotationChangeEmitter, this.simUI.player.cooldownsChangeEmitter], diff --git a/ui/core/individual_sim_ui.ts b/ui/core/individual_sim_ui.ts index 8590e03023..e9f558b3cb 100644 --- a/ui/core/individual_sim_ui.ts +++ b/ui/core/individual_sim_ui.ts @@ -1,5 +1,5 @@ import { aplLaunchStatuses, LaunchStatus, simLaunchStatuses } from './launched_sims'; -import { AutoRotationGenerator, Player } from './player'; +import { Player, AutoRotationGenerator, SimpleRotationGenerator } from './player'; import { SimUI, SimWarning } from './sim_ui'; import { EventID, TypedEvent } from './typed_event'; @@ -138,6 +138,7 @@ export interface IndividualSimUIConfig { }, autoRotation?: AutoRotationGenerator, + simpleRotation?: SimpleRotationGenerator, } export interface GearAndStats { @@ -205,6 +206,9 @@ export abstract class IndividualSimUI extends SimUI { } player.setAutoRotationGenerator(config.autoRotation); } + if (aplLaunchStatuses[player.spec] == LaunchStatus.Launched && config.simpleRotation) { + player.setSimpleRotationGenerator(config.simpleRotation); + } this.addWarning({ updateOn: this.player.gearChangeEmitter, @@ -425,7 +429,7 @@ export abstract class IndividualSimUI extends SimUI { this.player.setConsumes(eventID, this.individualConfig.defaults.consumes); if (aplLaunchStatuses[this.player.spec] < LaunchStatus.Beta) { this.player.setRotation(eventID, this.individualConfig.defaults.rotation); - } else { + } else if (aplLaunchStatuses[this.player.spec] == LaunchStatus.Beta) { this.player.setRotation(eventID, this.player.specTypeFunctions.rotationCreate()); } this.player.setTalentsString(eventID, this.individualConfig.defaults.talents.talentsString); diff --git a/ui/core/player.ts b/ui/core/player.ts index a239f8357e..134b2ab055 100644 --- a/ui/core/player.ts +++ b/ui/core/player.ts @@ -28,6 +28,7 @@ import { APLRotation, APLRotation_Type as APLRotationType, APLValue, + SimpleRotation, } from './proto/apl.js'; import { DungeonDifficulty, @@ -187,6 +188,7 @@ export class UnitMetadataList { } export type AutoRotationGenerator = (player: Player) => APLRotation; +export type SimpleRotationGenerator = (player: Player, simpleRotation: SpecRotation) => APLRotation; // Manages all the gear / consumes / other settings for a single Player. export class Player { @@ -218,6 +220,7 @@ export class Player { private healingEnabled: boolean = false; private autoRotationGenerator: AutoRotationGenerator | null = null; + private simpleRotationGenerator: SimpleRotationGenerator | null = null; private itemEPCache = new Array>(); private gemEPCache = new Map(); @@ -541,15 +544,28 @@ export class Player { getCooldowns(): Cooldowns { // Make a defensive copy - return Cooldowns.clone(this.cooldowns); + if (aplLaunchStatuses[this.spec] == LaunchStatus.Launched) { + return Cooldowns.clone(this.aplRotation.simple?.cooldowns || Cooldowns.create()); + } else { + return Cooldowns.clone(this.cooldowns); + } } setCooldowns(eventID: EventID, newCooldowns: Cooldowns) { - if (Cooldowns.equals(this.cooldowns, newCooldowns)) + if (Cooldowns.equals(this.getCooldowns(), newCooldowns)) return; - // Make a defensive copy - this.cooldowns = Cooldowns.clone(newCooldowns); + if (aplLaunchStatuses[this.spec] == LaunchStatus.Launched) { + if (!this.aplRotation.simple) { + this.aplRotation.simple = SimpleRotation.create(); + } + this.aplRotation.simple.cooldowns = newCooldowns; + this.rotationChangeEmitter.emit(eventID); + } else { + // Make a defensive copy + this.cooldowns = Cooldowns.clone(newCooldowns); + } + this.cooldownsChangeEmitter.emit(eventID); } @@ -639,14 +655,32 @@ export class Player { } getRotation(): SpecRotation { - return this.specTypeFunctions.rotationCopy(this.rotation); + if (aplLaunchStatuses[this.spec] == LaunchStatus.Launched) { + try { + const json = JSON.parse(this.aplRotation.simple?.specRotationJson || ''); + return this.specTypeFunctions.rotationFromJson(json); + } catch (e) { + console.warn('Error parsing rotation spec options: ' + e); + return this.specTypeFunctions.rotationCreate(); + } + } else { + return this.specTypeFunctions.rotationCopy(this.rotation); + } } setRotation(eventID: EventID, newRotation: SpecRotation) { - if (this.specTypeFunctions.rotationEquals(newRotation, this.rotation)) + if (this.specTypeFunctions.rotationEquals(newRotation, this.getRotation())) return; - this.rotation = this.specTypeFunctions.rotationCopy(newRotation); + if (aplLaunchStatuses[this.spec] == LaunchStatus.Launched) { + if (!this.aplRotation.simple) { + this.aplRotation.simple = SimpleRotation.create(); + } + this.aplRotation.simple.specRotationJson = this.specTypeFunctions.rotationToJson(newRotation); + } else { + this.rotation = this.specTypeFunctions.rotationCopy(newRotation); + } + this.rotationChangeEmitter.emit(eventID); } @@ -668,8 +702,16 @@ export class Player { } } - setAutoRotationGenerator(arg: AutoRotationGenerator) { - this.autoRotationGenerator = arg; + setAutoRotationGenerator(generator: AutoRotationGenerator) { + this.autoRotationGenerator = generator; + } + + setSimpleRotationGenerator(generator: SimpleRotationGenerator) { + this.simpleRotationGenerator = generator; + } + + hasSimpleRotationGenerator(): boolean { + return this.simpleRotationGenerator != null; } getResolvedAplRotation(): APLRotation { @@ -677,6 +719,11 @@ export class Player { if (type == APLRotationType.TypeAuto && this.autoRotationGenerator) { // Clone to avoid modifying preset rotations, which are often returned directly. return APLRotation.clone(this.autoRotationGenerator(this)); + } else if (type == APLRotationType.TypeSimple && this.simpleRotationGenerator) { + // Clone to avoid modifying preset rotations, which are often returned directly. + const rot = APLRotation.clone(this.simpleRotationGenerator(this, this.getRotation())); + rot.type = APLRotationType.TypeAPL; // Set this here for convenience, so the generator functions don't need to. + return rot; } else { return this.aplRotation; } @@ -1196,6 +1243,25 @@ export class Player { } } + if (aplLaunchStatuses[this.spec] == LaunchStatus.Launched) { + const rot = this.specTypeFunctions.rotationFromPlayer(proto); + if (rot && !this.specTypeFunctions.rotationEquals(rot, this.specTypeFunctions.rotationCreate())) { + if (this.simpleRotationGenerator) { + proto.rotation = APLRotation.create({ + type: APLRotationType.TypeSimple, + simple: { + specRotationJson: this.specTypeFunctions.rotationToJson(rot), + cooldowns: proto.cooldowns, + }, + }); + } else { + proto.rotation = APLRotation.create({ + type: APLRotationType.TypeAuto, + }); + } + } + } + TypedEvent.freezeAllAndDo(() => { this.setName(eventID, proto.name); this.setRace(eventID, proto.race); @@ -1204,7 +1270,6 @@ export class Player { this.setConsumes(eventID, proto.consumes || Consumes.create()); this.setBonusStats(eventID, Stats.fromProto(proto.bonusStats || UnitStats.create())); this.setBuffs(eventID, proto.buffs || IndividualBuffs.create()); - this.setCooldowns(eventID, proto.cooldowns || Cooldowns.create()); this.setTalentsString(eventID, proto.talentsString); this.setGlyphs(eventID, proto.glyphs || Glyphs.create()); this.setProfession1(eventID, proto.profession1); @@ -1213,7 +1278,10 @@ export class Player { this.setInFrontOfTarget(eventID, proto.inFrontOfTarget); this.setDistanceFromTarget(eventID, proto.distanceFromTarget); this.setHealingModel(eventID, proto.healingModel || HealingModel.create()); - this.setRotation(eventID, this.specTypeFunctions.rotationFromPlayer(proto)); + if (aplLaunchStatuses[this.spec] != LaunchStatus.Launched) { + this.setCooldowns(eventID, proto.cooldowns || Cooldowns.create()); + this.setRotation(eventID, this.specTypeFunctions.rotationFromPlayer(proto)); + } this.setAplRotation(eventID, proto.rotation || APLRotation.create()) this.setSpecOptions(eventID, this.specTypeFunctions.optionsFromPlayer(proto));