Skip to content

Commit

Permalink
Move hunter APL to fully launched (with Simple Rotation) and fix a fe…
Browse files Browse the repository at this point in the history
…w UI bugs
  • Loading branch information
jimmyt857 committed Sep 17, 2023
1 parent 205040a commit c7939fa
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 69 deletions.
4 changes: 2 additions & 2 deletions proto/hunter.proto
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ message Hunter {
ExplosiveTrap = 11;
Volley = 12;
}
CustomRotation custom_rotation = 8;
CustomRotation custom_rotation = 8 [deprecated = true];

// Switch to Aspect of the Viper when mana goes below this percent.
double viper_start_mana_percent = 6;
Expand All @@ -228,7 +228,7 @@ message Hunter {
bool allow_explosive_shot_downrank = 10;

bool multi_dot_serpent_sting = 11;
double steady_shot_max_delay = 12;
double steady_shot_max_delay = 12 [deprecated = true];
}
Rotation rotation = 1;

Expand Down
16 changes: 11 additions & 5 deletions ui/core/components/individual_sim_ui/rotation_tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
import {
APLRotation,
APLRotation_Type as APLRotationType,
SimpleRotation,
} from "../../proto/apl";
import {
SavedRotation,
Expand Down Expand Up @@ -196,21 +197,21 @@ export class RotationTab extends SimTab {
}

private buildSavedDataPickers() {
const launchStatus = aplLaunchStatuses[this.simUI.player.spec];
const aplIsLaunched = aplLaunchStatuses[this.simUI.player.spec] == LaunchStatus.Launched;

const savedRotationsManager = new SavedDataManager<Player<any>, SavedRotation>(this.rightPanel, this.simUI, this.simUI.player, {
label: 'Rotation',
header: { title: 'Saved Rotations' },
storageKey: this.simUI.getSavedRotationStorageKey(),
getData: (player: Player<any>) => SavedRotation.create({
rotation: APLRotation.clone(player.aplRotation),
specRotationOptionsJson: launchStatus == LaunchStatus.Launched ? '' : JSON.stringify(player.specTypeFunctions.rotationToJson(player.getRotation())),
cooldowns: LaunchStatus.Launched ? undefined : player.getCooldowns(),
specRotationOptionsJson: aplIsLaunched ? '{}' : JSON.stringify(player.specTypeFunctions.rotationToJson(player.getRotation())),
cooldowns: aplIsLaunched ? Cooldowns.create() : player.getCooldowns(),
}),
setData: (eventID: EventID, player: Player<any>, newRotation: SavedRotation) => {
TypedEvent.freezeAllAndDo(() => {
player.setAplRotation(eventID, newRotation.rotation || APLRotation.create());
if (launchStatus != LaunchStatus.Launched) {
if (!aplIsLaunched) {
if (newRotation.specRotationOptionsJson) {
try {
const json = JSON.parse(newRotation.specRotationOptionsJson);
Expand All @@ -225,7 +226,11 @@ export class RotationTab extends SimTab {
});
},
changeEmitters: [this.simUI.player.rotationChangeEmitter, this.simUI.player.cooldownsChangeEmitter],
equals: (a: SavedRotation, b: SavedRotation) => SavedRotation.equals(a, b),
equals: (a: SavedRotation, b: SavedRotation) => {
// Uncomment this to debug equivalence checks with preset rotations (e.g. the chip doesn't highlight)
//console.log(`Rot A: ${SavedRotation.toJsonString(a, {prettySpaces: 2})}\n\nRot B: ${SavedRotation.toJsonString(b, {prettySpaces: 2})}`);
return SavedRotation.equals(a, b);
},
toJson: (a: SavedRotation) => SavedRotation.toJson(a),
fromJson: (obj: any) => SavedRotation.fromJson(obj),
});
Expand All @@ -236,6 +241,7 @@ export class RotationTab extends SimTab {
const rotData = presetRotation.rotation;
// Fill default values so the equality checks always work.
if (!rotData.cooldowns) rotData.cooldowns = Cooldowns.create();
if (!rotData.specRotationOptionsJson) rotData.specRotationOptionsJson = '{}';
if (!rotData.rotation) rotData.rotation = APLRotation.create();

savedRotationsManager.addSavedData({
Expand Down
2 changes: 1 addition & 1 deletion ui/core/launched_sims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const aplLaunchStatuses: Record<Spec, LaunchStatus> = {
[Spec.SpecElementalShaman]: LaunchStatus.Alpha,
[Spec.SpecEnhancementShaman]: LaunchStatus.Alpha,
[Spec.SpecRestorationShaman]: LaunchStatus.Alpha,
[Spec.SpecHunter]: LaunchStatus.Beta,
[Spec.SpecHunter]: LaunchStatus.Launched,
[Spec.SpecMage]: LaunchStatus.Alpha,
[Spec.SpecRogue]: LaunchStatus.Alpha,
[Spec.SpecHolyPaladin]: LaunchStatus.Alpha,
Expand Down
20 changes: 13 additions & 7 deletions ui/core/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ export class UnitMetadataList {
}

export type AutoRotationGenerator<SpecType extends Spec> = (player: Player<SpecType>) => APLRotation;
export type SimpleRotationGenerator<SpecType extends Spec> = (player: Player<SpecType>, simpleRotation: SpecRotation<SpecType>) => APLRotation;
export type SimpleRotationGenerator<SpecType extends Spec> = (player: Player<SpecType>, simpleRotation: SpecRotation<SpecType>, cooldowns: Cooldowns) => APLRotation;

// Manages all the gear / consumes / other settings for a single Player.
export class Player<SpecType extends Spec> {
Expand Down Expand Up @@ -656,11 +656,16 @@ export class Player<SpecType extends Spec> {

getRotation(): SpecRotation<SpecType> {
if (aplLaunchStatuses[this.spec] == LaunchStatus.Launched) {
const jsonStr = this.aplRotation.simple?.specRotationJson || '';
if (!jsonStr) {
return this.specTypeFunctions.rotationCreate();
}

try {
const json = JSON.parse(this.aplRotation.simple?.specRotationJson || '');
const json = JSON.parse(jsonStr);
return this.specTypeFunctions.rotationFromJson(json);
} catch (e) {
console.warn('Error parsing rotation spec options: ' + e);
console.warn(`Error parsing rotation spec options: ${e}\n\nSpec options: '${jsonStr}'`);
return this.specTypeFunctions.rotationCreate();
}
} else {
Expand All @@ -676,7 +681,7 @@ export class Player<SpecType extends Spec> {
if (!this.aplRotation.simple) {
this.aplRotation.simple = SimpleRotation.create();
}
this.aplRotation.simple.specRotationJson = this.specTypeFunctions.rotationToJson(newRotation);
this.aplRotation.simple.specRotationJson = JSON.stringify(this.specTypeFunctions.rotationToJson(newRotation));
} else {
this.rotation = this.specTypeFunctions.rotationCopy(newRotation);
}
Expand Down Expand Up @@ -721,7 +726,7 @@ export class Player<SpecType extends Spec> {
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()));
const rot = APLRotation.clone(this.simpleRotationGenerator(this, this.getRotation(), this.getCooldowns()));
rot.type = APLRotationType.TypeAPL; // Set this here for convenience, so the generator functions don't need to.
return rot;
} else {
Expand Down Expand Up @@ -1200,6 +1205,7 @@ export class Player<SpecType extends Spec> {
}

toProto(forExport?: boolean, forSimming?: boolean): PlayerProto {
const aplIsLaunched = aplLaunchStatuses[this.spec] == LaunchStatus.Launched;
const gear = this.getGear();
return withSpecProto(
this.spec,
Expand All @@ -1211,7 +1217,7 @@ export class Player<SpecType extends Spec> {
consumes: this.getConsumes(),
bonusStats: this.getBonusStats().toProto(),
buffs: this.getBuffs(),
cooldowns: this.getCooldowns(),
cooldowns: aplIsLaunched ? Cooldowns.create({ hpPercentForDefensives: this.getCooldowns().hpPercentForDefensives }) : this.getCooldowns(),
talentsString: this.getTalentsString(),
glyphs: this.getGlyphs(),
rotation: forSimming ? this.getResolvedAplRotation() : this.aplRotation,
Expand All @@ -1223,7 +1229,7 @@ export class Player<SpecType extends Spec> {
healingModel: this.getHealingModel(),
database: forExport ? SimDatabase.create() : this.toDatabase(),
}),
this.getRotation(),
aplIsLaunched ? this.specTypeFunctions.rotationCreate() : this.getRotation(),
this.getSpecOptions());
}

Expand Down
44 changes: 44 additions & 0 deletions ui/core/proto_utils/apl_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
ActionID as ActionIdProto,
Cooldowns,
} from '../proto/common.js';

import {
APLAction,
APLPrepullAction,
} from '../proto/apl.js';

export function prepullPotionAction(doAt?: string): APLPrepullAction {
return APLPrepullAction.fromJsonString(`{"action":{"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}},"doAtValue":{"const":{"val":"${doAt || '-1s'}"}}}`);
}

export function autocastCooldownsAction(startAt?: string): APLAction {
if (startAt) {
return APLAction.fromJsonString(`{"condition":{"cmp":{"op":"OpGt","lhs":{"currentTime":{}},"rhs":{"const":{"val":"${startAt}"}}}},"autocastOtherCooldowns":{}}`);
} else {
return APLAction.fromJsonString(`{"autocastOtherCooldowns":{}}`);
}
}

export function scheduledCooldownAction(schedule: string, actionId: ActionIdProto): APLAction {
return APLAction.fromJsonString(`{"schedule":{"schedule":"${schedule}","innerAction":{"castSpell":{"spellId":${ActionIdProto.toJson(actionId)}}}}}`);
}

export function simpleCooldownActions(cooldowns: Cooldowns): Array<APLAction> {
return cooldowns.cooldowns
.filter(cd => cd.id)
.map(cd => {
const schedule = cd.timings.map(timing => timing.toFixed(1) + 's').join(', ');
return scheduledCooldownAction(schedule, cd.id!);
});
}

export function standardCooldownDefaults(cooldowns: Cooldowns, prepotAt?: string, startAutocastCDsAt?: string): [Array<APLPrepullAction>, Array<APLAction>] {
return [
[prepullPotionAction(prepotAt)],
[
autocastCooldownsAction(startAutocastCDsAt),
simpleCooldownActions(cooldowns),
].flat(),
];
}
27 changes: 0 additions & 27 deletions ui/hunter/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ export const HunterRotationConfig = {
values: [
{ name: 'Single Target', value: RotationType.SingleTarget },
{ name: 'AOE', value: RotationType.Aoe },
{ name: 'Custom', value: RotationType.Custom },
],
}),
InputHelpers.makeRotationEnumInput<Spec.SpecHunter, StingType>({
Expand All @@ -90,12 +89,6 @@ export const HunterRotationConfig = {
fieldName: 'trapWeave',
label: 'Trap Weave',
labelTooltip: 'Uses Explosive Trap at appropriate times. Note that selecting this will disable Black Arrow because they share a CD.',
showWhen: (player: Player<Spec.SpecHunter>) => player.getRotation().type != RotationType.Custom,
}),
InputHelpers.makeRotationNumberInput<Spec.SpecHunter>({
fieldName: 'steadyShotMaxDelay',
label: 'Steady Shot Max Delay (ms)',
labelTooltip: 'If another higher-priority spell comes off cooldown in the specified time then steady shot is not cast and the rotation waits',
}),
InputHelpers.makeRotationBooleanInput<Spec.SpecHunter>({
fieldName: 'allowExplosiveShotDownrank',
Expand All @@ -110,26 +103,6 @@ export const HunterRotationConfig = {
labelTooltip: 'Casts Serpent Sting on multiple targets',
changeEmitter: (player: Player<Spec.SpecHunter>) => TypedEvent.onAny([player.rotationChangeEmitter, player.talentsChangeEmitter]),
}),
InputHelpers.makeCustomRotationInput<Spec.SpecHunter, SpellOption>({
fieldName: 'customRotation',
numColumns: 2,
values: [
{ actionId: ActionId.fromSpellId(49052), value: SpellOption.SteadyShot },
{ actionId: ActionId.fromSpellId(49045), value: SpellOption.ArcaneShot },
{ actionId: ActionId.fromSpellId(49050), value: SpellOption.AimedShot },
{ actionId: ActionId.fromSpellId(49048), value: SpellOption.MultiShot },
{ actionId: ActionId.fromSpellId(49001), value: SpellOption.SerpentStingSpell },
{ actionId: ActionId.fromSpellId(3043), value: SpellOption.ScorpidStingSpell },
{ actionId: ActionId.fromSpellId(61006), value: SpellOption.KillShot },
{ actionId: ActionId.fromSpellId(63672), value: SpellOption.BlackArrow },
{ actionId: ActionId.fromSpellId(53209), value: SpellOption.ChimeraShot },
{ actionId: ActionId.fromSpellId(60053), value: SpellOption.ExplosiveShot, text: 'R4' },
{ actionId: ActionId.fromSpellId(60052), value: SpellOption.ExplosiveShotDownrank, text: 'R3' },
{ actionId: ActionId.fromSpellId(49067), value: SpellOption.ExplosiveTrap },
{ actionId: ActionId.fromSpellId(58434), value: SpellOption.Volley },
],
showWhen: (player: Player<Spec.SpecHunter>) => player.getRotation().type == RotationType.Custom,
}),
InputHelpers.makeRotationNumberInput<Spec.SpecHunter>({
fieldName: 'viperStartManaPercent',
label: 'Viper Start Mana %',
Expand Down
24 changes: 8 additions & 16 deletions ui/hunter/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Glyphs } from '../core/proto/common.js';
import { PetFood } from '../core/proto/common.js';
import { Potions } from '../core/proto/common.js';
import { SavedRotation, SavedTalents } from '../core/proto/ui.js';
import { APLRotation } from '../core/proto/apl.js';
import { APLRotation, APLRotation_Type } from '../core/proto/apl.js';
import { ferocityDefault, ferocityBMDefault } from '../core/talents/hunter_pet.js';
import { Player } from '../core/player.js';

Expand Down Expand Up @@ -84,25 +84,17 @@ export const DefaultRotation = HunterRotation.create({
viperStopManaPercent: 0.3,
multiDotSerpentSting: true,
allowExplosiveShotDownrank: true,
steadyShotMaxDelay: 300,
customRotation: CustomRotation.create({
spells: [
CustomSpell.create({ spell: SpellOption.SerpentStingSpell }),
CustomSpell.create({ spell: SpellOption.KillShot }),
CustomSpell.create({ spell: SpellOption.ChimeraShot }),
CustomSpell.create({ spell: SpellOption.BlackArrow }),
CustomSpell.create({ spell: SpellOption.ExplosiveShot }),
CustomSpell.create({ spell: SpellOption.AimedShot }),
CustomSpell.create({ spell: SpellOption.ArcaneShot }),
CustomSpell.create({ spell: SpellOption.SteadyShot }),
],
}),
});

export const ROTATION_PRESET_LEGACY_DEFAULT = {
name: 'Legacy Default',
name: 'Simple Default',
rotation: SavedRotation.create({
specRotationOptionsJson: HunterRotation.toJsonString(DefaultRotation),
rotation: {
type: APLRotation_Type.TypeSimple,
simple: {
specRotationJson: HunterRotation.toJsonString(DefaultRotation),
},
},
}),
}
export const ROTATION_PRESET_BM = {
Expand Down
Loading

0 comments on commit c7939fa

Please sign in to comment.