From 903e2b6a08c74fc09ce35a60c54fc976460931f4 Mon Sep 17 00:00:00 2001 From: James Tanner Date: Tue, 4 Jul 2023 12:27:11 -0700 Subject: [PATCH] Implement autocast CD action for APL, hunter APL rotation DPS now matches default rotation --- proto/apl.proto | 4 ++ sim/core/apl.go | 11 ++++- sim/core/apl_action.go | 8 ++-- sim/core/apl_actions_core.go | 29 ++++++++++++- sim/core/apl_actions_sequences.go | 12 +++--- sim/core/major_cooldown.go | 42 ++++++++++++++++--- sim/hunter/rotation.go | 3 +- .../individual_sim_ui/apl_actions.ts | 15 +++++++ .../individual_sim_ui/apl_rotation_picker.ts | 2 +- .../individual_sim_ui/rotation_tab.ts | 1 - ui/hunter/presets.ts | 6 ++- 11 files changed, 111 insertions(+), 22 deletions(-) diff --git a/proto/apl.proto b/proto/apl.proto index f531f36d3f..90e395efe2 100644 --- a/proto/apl.proto +++ b/proto/apl.proto @@ -28,6 +28,7 @@ message APLAction { APLActionStrictSequence strict_sequence = 6; APLActionCastSpell cast_spell = 3; + APLActionAutocastOtherCooldowns autocast_other_cooldowns = 7; APLActionWait wait = 4; } } @@ -87,6 +88,9 @@ message APLActionCastSpell { ActionID spell_id = 1; } +message APLActionAutocastOtherCooldowns { +} + message APLActionWait { APLValue duration = 1; } diff --git a/sim/core/apl.go b/sim/core/apl.go index 8bc6a02a67..b8986c067c 100644 --- a/sim/core/apl.go +++ b/sim/core/apl.go @@ -37,6 +37,12 @@ func (unit *Unit) newAPLRotation(config *proto.APLRotation) *APLRotation { for _, action := range rotation.allAPLActions() { action.impl.Finalize() + + // Remove MCDs that are referenced by APL actions. + character := unit.Env.Raid.GetPlayerFromUnit(unit).GetCharacter() + if castSpellAction, ok := action.impl.(*APLActionCastSpell); ok { + character.removeInitialMajorCooldown(castSpellAction.spell.ActionID) + } } return rotation @@ -63,8 +69,11 @@ func (rot *APLRotation) reset(sim *Simulation) { func (apl *APLRotation) DoNextAction(sim *Simulation) { if apl.strictSequence == nil { for _, action := range apl.priorityList { - if action.IsAvailable(sim) { + if action.IsReady(sim) { action.Execute(sim) + if apl.unit.GCD.IsReady(sim) { + apl.unit.WaitUntil(sim, sim.CurrentTime) + } return } } diff --git a/sim/core/apl_action.go b/sim/core/apl_action.go index 31506017c7..a47f7e9627 100644 --- a/sim/core/apl_action.go +++ b/sim/core/apl_action.go @@ -9,8 +9,8 @@ type APLAction struct { impl APLActionImpl } -func (action *APLAction) IsAvailable(sim *Simulation) bool { - return (action.condition == nil || action.condition.GetBool(sim)) && action.impl.IsAvailable(sim) +func (action *APLAction) IsReady(sim *Simulation) bool { + return (action.condition == nil || action.condition.GetBool(sim)) && action.impl.IsReady(sim) } func (action *APLAction) Execute(sim *Simulation) { @@ -35,7 +35,7 @@ type APLActionImpl interface { Reset(*Simulation) // Whether this action is available to be used right now. - IsAvailable(*Simulation) bool + IsReady(*Simulation) bool // Performs the action. Execute(*Simulation) @@ -66,6 +66,8 @@ func (unit *Unit) newAPLActionImpl(config *proto.APLAction) APLActionImpl { return unit.newActionStrictSequence(config.GetStrictSequence()) case *proto.APLAction_CastSpell: return unit.newActionCastSpell(config.GetCastSpell()) + case *proto.APLAction_AutocastOtherCooldowns: + return unit.newActionAutocastOtherCooldowns(config.GetAutocastOtherCooldowns()) case *proto.APLAction_Wait: return unit.newActionWait(config.GetWait()) default: diff --git a/sim/core/apl_actions_core.go b/sim/core/apl_actions_core.go index ca5a2504de..d7f6541a54 100644 --- a/sim/core/apl_actions_core.go +++ b/sim/core/apl_actions_core.go @@ -21,13 +21,38 @@ func (unit *Unit) newActionCastSpell(config *proto.APLActionCastSpell) APLAction func (action *APLActionCastSpell) GetInnerActions() []*APLAction { return nil } func (action *APLActionCastSpell) Finalize() {} func (action *APLActionCastSpell) Reset(*Simulation) {} -func (action *APLActionCastSpell) IsAvailable(sim *Simulation) bool { +func (action *APLActionCastSpell) IsReady(sim *Simulation) bool { return action.spell.CanCast(sim, action.spell.Unit.CurrentTarget) } func (action *APLActionCastSpell) Execute(sim *Simulation) { action.spell.Cast(sim, action.spell.Unit.CurrentTarget) } +type APLActionAutocastOtherCooldowns struct { + character *Character + + nextReadyMCD *MajorCooldown +} + +func (unit *Unit) newActionAutocastOtherCooldowns(config *proto.APLActionAutocastOtherCooldowns) APLActionImpl { + return &APLActionAutocastOtherCooldowns{ + character: unit.Env.Raid.GetPlayerFromUnit(unit).GetCharacter(), + } +} +func (action *APLActionAutocastOtherCooldowns) GetInnerActions() []*APLAction { return nil } +func (action *APLActionAutocastOtherCooldowns) Finalize() {} +func (action *APLActionAutocastOtherCooldowns) Reset(*Simulation) { + action.nextReadyMCD = nil +} +func (action *APLActionAutocastOtherCooldowns) IsReady(sim *Simulation) bool { + action.nextReadyMCD = action.character.getFirstReadyMCD(sim) + return action.nextReadyMCD != nil +} +func (action *APLActionAutocastOtherCooldowns) Execute(sim *Simulation) { + action.nextReadyMCD.tryActivateHelper(sim, action.character) + action.character.UpdateMajorCooldowns() +} + type APLActionWait struct { unit *Unit duration APLValue @@ -42,7 +67,7 @@ func (unit *Unit) newActionWait(config *proto.APLActionWait) APLActionImpl { func (action *APLActionWait) GetInnerActions() []*APLAction { return nil } func (action *APLActionWait) Finalize() {} func (action *APLActionWait) Reset(*Simulation) {} -func (action *APLActionWait) IsAvailable(sim *Simulation) bool { +func (action *APLActionWait) IsReady(sim *Simulation) bool { return action.duration != nil } func (action *APLActionWait) Execute(sim *Simulation) { diff --git a/sim/core/apl_actions_sequences.go b/sim/core/apl_actions_sequences.go index ece120ac86..ca8a4cfb25 100644 --- a/sim/core/apl_actions_sequences.go +++ b/sim/core/apl_actions_sequences.go @@ -30,8 +30,8 @@ func (action *APLActionSequence) Finalize() {} func (action *APLActionSequence) Reset(*Simulation) { action.curIdx = 0 } -func (action *APLActionSequence) IsAvailable(sim *Simulation) bool { - return action.curIdx < len(action.actions) && action.actions[action.curIdx].IsAvailable(sim) +func (action *APLActionSequence) IsReady(sim *Simulation) bool { + return action.curIdx < len(action.actions) && action.actions[action.curIdx].IsReady(sim) } func (action *APLActionSequence) Execute(sim *Simulation) { action.actions[action.curIdx].Execute(sim) @@ -65,7 +65,7 @@ func (action *APLActionResetSequence) Finalize() { validationWarning("No sequence with name: %s", action.name) } func (action *APLActionResetSequence) Reset(*Simulation) {} -func (action *APLActionResetSequence) IsAvailable(sim *Simulation) bool { +func (action *APLActionResetSequence) IsReady(sim *Simulation) bool { return true } func (action *APLActionResetSequence) Execute(sim *Simulation) { @@ -96,9 +96,9 @@ func (action *APLActionStrictSequence) Finalize() {} func (action *APLActionStrictSequence) Reset(*Simulation) { action.curIdx = 0 } -func (action *APLActionStrictSequence) IsAvailable(sim *Simulation) bool { +func (action *APLActionStrictSequence) IsReady(sim *Simulation) bool { for _, subaction := range action.actions { - if !subaction.IsAvailable(sim) { + if !subaction.IsReady(sim) { return false } } @@ -106,7 +106,7 @@ func (action *APLActionStrictSequence) IsAvailable(sim *Simulation) bool { } func (action *APLActionStrictSequence) Execute(sim *Simulation) { action.unit.Rotation.strictSequence = action - if !action.actions[action.curIdx].IsAvailable(sim) { + if !action.actions[action.curIdx].IsReady(sim) { action.curIdx = 0 action.unit.Rotation.strictSequence = nil return diff --git a/sim/core/major_cooldown.go b/sim/core/major_cooldown.go index 57257b765c..30b09a58a5 100644 --- a/sim/core/major_cooldown.go +++ b/sim/core/major_cooldown.go @@ -123,9 +123,7 @@ func (mcd *MajorCooldown) tryActivateInternal(sim *Simulation, character *Charac return mcd.tryActivateHelper(sim, character) } -// Activates this MCD, if all the conditions pass. -// Returns whether the MCD was activated. -func (mcd *MajorCooldown) tryActivateHelper(sim *Simulation, character *Character) bool { +func (mcd *MajorCooldown) shouldActivateHelper(sim *Simulation, character *Character) bool { if mcd.Type.Matches(CooldownTypeSurvival) && character.cooldownConfigs.HpPercentForDefensives != 0 { if character.CurrentHealthPercent() > character.cooldownConfigs.HpPercentForDefensives { return false @@ -136,12 +134,17 @@ func (mcd *MajorCooldown) tryActivateHelper(sim *Simulation, character *Characte return false } - var shouldActivate bool if mcd.numUsages < len(mcd.timings) { - shouldActivate = sim.CurrentTime >= mcd.timings[mcd.numUsages] + return sim.CurrentTime >= mcd.timings[mcd.numUsages] } else { - shouldActivate = mcd.ShouldActivate(sim, character) + return mcd.ShouldActivate(sim, character) } +} + +// Activates this MCD, if all the conditions pass. +// Returns whether the MCD was activated. +func (mcd *MajorCooldown) tryActivateHelper(sim *Simulation, character *Character) bool { + shouldActivate := mcd.shouldActivateHelper(sim, character) if shouldActivate { if mcd.Spell.Flags.Matches(SpellFlagHelpful) { @@ -397,6 +400,16 @@ func (mcdm *majorCooldownManager) GetInitialMajorCooldown(actionID ActionID) Maj return MajorCooldown{} } +func (mcdm *majorCooldownManager) removeInitialMajorCooldown(actionID ActionID) { + for i, mcd := range mcdm.initialMajorCooldowns { + if mcd.Spell.SameAction(actionID) { + mcdm.initialMajorCooldowns = append(mcdm.initialMajorCooldowns[:i], mcdm.initialMajorCooldowns[i+1:]...) + mcdm.majorCooldowns = mcdm.majorCooldowns[:len(mcdm.majorCooldowns)-1] + return + } + } +} + func (mcdm *majorCooldownManager) GetMajorCooldown(actionID ActionID) *MajorCooldown { for _, mcd := range mcdm.majorCooldowns { if mcd.Spell.SameAction(actionID) { @@ -427,6 +440,23 @@ func (mcdm *majorCooldownManager) GetMajorCooldownIDs() []*proto.ActionID { return ids } +func (mcdm *majorCooldownManager) getFirstReadyMCD(sim *Simulation) *MajorCooldown { + if sim.CurrentTime < mcdm.minReady { + return nil + } + + for _, mcd := range mcdm.majorCooldowns { + if !mcd.IsReady(sim) { + return nil + } + if mcd.shouldActivateHelper(sim, mcdm.character) { + return mcd + } + } + + return nil +} + func (mcdm *majorCooldownManager) TryUseCooldowns(sim *Simulation) { if sim.CurrentTime < mcdm.minReady { return diff --git a/sim/hunter/rotation.go b/sim/hunter/rotation.go index 92c0aea797..bfcb3df93a 100644 --- a/sim/hunter/rotation.go +++ b/sim/hunter/rotation.go @@ -1,10 +1,11 @@ package hunter import ( + "time" + "github.com/wowsims/wotlk/sim/common" "github.com/wowsims/wotlk/sim/core" "github.com/wowsims/wotlk/sim/core/proto" - "time" ) func (hunter *Hunter) OnAutoAttack(sim *core.Simulation, spell *core.Spell) { diff --git a/ui/core/components/individual_sim_ui/apl_actions.ts b/ui/core/components/individual_sim_ui/apl_actions.ts index 282af051de..f1bbf57395 100644 --- a/ui/core/components/individual_sim_ui/apl_actions.ts +++ b/ui/core/components/individual_sim_ui/apl_actions.ts @@ -4,6 +4,7 @@ import { APLActionSequence, APLActionResetSequence, APLActionStrictSequence, + APLActionAutocastOtherCooldowns, APLActionWait, APLValue, } from '../../proto/apl.js'; @@ -260,8 +261,22 @@ export const actionTypeFactories: Record, ActionTypeC actionListFieldConfig('actions'), ], }), + ['autocastOtherCooldowns']: inputBuilder({ + label: 'Autocast Other Cooldowns', + submenu: ['Misc'], + shortDescription: 'Auto-casts cooldowns as soon as they are ready.', + fullDescription: ` + + `, + newValue: APLActionAutocastOtherCooldowns.create, + fields: [], + }), ['wait']: inputBuilder({ label: 'Wait', + submenu: ['Misc'], shortDescription: 'Pauses the GCD for a specified amount of time.', newValue: APLActionWait.create, fields: [ diff --git a/ui/core/components/individual_sim_ui/apl_rotation_picker.ts b/ui/core/components/individual_sim_ui/apl_rotation_picker.ts index d1dfbe9096..5a5ceaa60f 100644 --- a/ui/core/components/individual_sim_ui/apl_rotation_picker.ts +++ b/ui/core/components/individual_sim_ui/apl_rotation_picker.ts @@ -38,7 +38,7 @@ export class APLRotationPicker extends Component { inlineMenuBar: true, }); - modPlayer.rotationChangeEmitter.on(() => console.log('APL: ' + APLRotation.toJsonString(modPlayer.aplRotation))) + //modPlayer.rotationChangeEmitter.on(() => console.log('APL: ' + APLRotation.toJsonString(modPlayer.aplRotation))) } } diff --git a/ui/core/components/individual_sim_ui/rotation_tab.ts b/ui/core/components/individual_sim_ui/rotation_tab.ts index faa16c88fb..d45765b5a5 100644 --- a/ui/core/components/individual_sim_ui/rotation_tab.ts +++ b/ui/core/components/individual_sim_ui/rotation_tab.ts @@ -36,7 +36,6 @@ export class RotationTab extends SimTab { constructor(parentElem: HTMLElement, simUI: IndividualSimUI) { super(parentElem, simUI, {identifier: 'rotation-tab', title: 'Rotation'}); - this.rootElem.classList.add('experimental'); this.simUI = simUI; this.leftPanel = document.createElement('div'); diff --git a/ui/hunter/presets.ts b/ui/hunter/presets.ts index cfccef521e..c2d0bc0559 100644 --- a/ui/hunter/presets.ts +++ b/ui/hunter/presets.ts @@ -116,13 +116,17 @@ export const ROTATION_PRESET_DEFAULT = { rotation: APLRotation.fromJsonString(`{ "enabled": true, "priorityList": [ + {"action": {"autocastOtherCooldowns": {}}}, {"action": { "condition": {"not": {"val": {"dotIsActive": {"spellId": { "spellId": 49001 }}}}}, "castSpell": {"spellId": { "spellId": 49001 }} }}, {"action": {"castSpell": {"spellId": { "spellId": 61006 }}}}, {"action": {"castSpell": {"spellId": { "spellId": 63672 }}}}, - {"action": {"castSpell": {"spellId": { "spellId": 60053 }}}}, + {"action": { + "condition": {"not": {"val": {"dotIsActive": {"spellId": { "spellId": 60053 }}}}}, + "castSpell": {"spellId": { "spellId": 60053 }} + }}, {"action": {"castSpell": {"spellId": { "spellId": 49050 }}}}, {"action": { "condition": {"not": {"val": {"dotIsActive": {"spellId": { "spellId": 60053 }}}}},