diff --git a/proto/apl.proto b/proto/apl.proto index af0db8afe9..e99562fd07 100644 --- a/proto/apl.proto +++ b/proto/apl.proto @@ -29,7 +29,7 @@ message APLListItem { APLAction action = 3; // The action to be performed. } -// NextIndex: 13 +// NextIndex: 15 message APLAction { APLValue condition = 1; // If set, action will only execute if value is true or != 0. @@ -49,6 +49,7 @@ message APLAction { APLActionCancelAura cancel_aura = 10; APLActionTriggerICD trigger_icd = 11; APLActionWait wait = 4; + APLActionWaitUntil wait_until = 14; } } @@ -188,6 +189,10 @@ message APLActionWait { APLValue duration = 1; } +message APLActionWaitUntil { + APLValue condition = 1; +} + /////////////////////////////////////////////////////////////////////////// // VALUES /////////////////////////////////////////////////////////////////////////// diff --git a/sim/core/apl.go b/sim/core/apl.go index dd6ccbbc15..09989cc9b2 100644 --- a/sim/core/apl.go +++ b/sim/core/apl.go @@ -13,8 +13,8 @@ type APLRotation struct { prepullActions []*APLAction priorityList []*APLAction - // Current strict sequence - strictSequence *APLAction + // Action currently controlling this rotation (only used for certain actions, such as StrictSequence). + controllingAction APLActionImpl // Used inside of actions/value to determine whether they will occur during the prepull or regular rotation. parsingPrepull bool @@ -143,7 +143,7 @@ func (rot *APLRotation) allPrepullActions() []*APLAction { } func (rot *APLRotation) reset(sim *Simulation) { - rot.strictSequence = nil + rot.controllingAction = nil rot.inLoop = false for _, action := range rot.allAPLActions() { action.impl.Reset(sim) @@ -174,37 +174,19 @@ func (apl *APLRotation) DoNextAction(sim *Simulation) { } if apl.unit.GCD.IsReady(sim) { - apl.unit.WaitUntil(sim, sim.CurrentTime+time.Millisecond*500) + apl.unit.WaitUntil(sim, sim.CurrentTime+time.Millisecond*50) } else { apl.unit.DoNothing() } } func (apl *APLRotation) getNextAction(sim *Simulation) *APLAction { - if sim.CurrentTime < apl.unit.waitUntilTime { - return nil - } - - if apl.strictSequence != nil { - ss := apl.strictSequence.impl.(*APLActionStrictSequence) - if ss.actions[ss.curIdx].IsReady(sim) { - return apl.strictSequence - } else if apl.unit.GCD.IsReady(sim) { - // If the GCD is ready when the next subaction isn't, it means the sequence is bad - // so reset and exit the sequence. - ss.curIdx = 0 - apl.strictSequence = nil - } else { - // Return nil to wait for the GCD to become ready. - return nil - } + if apl.controllingAction != nil { + return apl.controllingAction.GetNextAction(sim) } for _, action := range apl.priorityList { if action.IsReady(sim) { - if _, ok := action.impl.(*APLActionStrictSequence); ok { - apl.strictSequence = action - } return action } } diff --git a/sim/core/apl_action.go b/sim/core/apl_action.go index 69f516e237..d491162128 100644 --- a/sim/core/apl_action.go +++ b/sim/core/apl_action.go @@ -73,6 +73,9 @@ type APLActionImpl interface { // Performs the action. Execute(*Simulation) + // Called only while this action is controlling the rotation. + GetNextAction(sim *Simulation) *APLAction + // Pretty-print string for debugging. String() string } @@ -81,10 +84,11 @@ type APLActionImpl interface { type defaultAPLActionImpl struct { } -func (impl defaultAPLActionImpl) GetInnerActions() []*APLAction { return nil } -func (impl defaultAPLActionImpl) GetAPLValues() []APLValue { return nil } -func (impl defaultAPLActionImpl) Finalize(*APLRotation) {} -func (impl defaultAPLActionImpl) Reset(*Simulation) {} +func (impl defaultAPLActionImpl) GetInnerActions() []*APLAction { return nil } +func (impl defaultAPLActionImpl) GetAPLValues() []APLValue { return nil } +func (impl defaultAPLActionImpl) Finalize(*APLRotation) {} +func (impl defaultAPLActionImpl) Reset(*Simulation) {} +func (impl defaultAPLActionImpl) GetNextAction(*Simulation) *APLAction { return nil } func (rot *APLRotation) newAPLAction(config *proto.APLAction) *APLAction { if config == nil { @@ -138,6 +142,8 @@ func (rot *APLRotation) newAPLActionImpl(config *proto.APLAction) APLActionImpl return rot.newActionTriggerICD(config.GetTriggerIcd()) case *proto.APLAction_Wait: return rot.newActionWait(config.GetWait()) + case *proto.APLAction_WaitUntil: + return rot.newActionWaitUntil(config.GetWaitUntil()) default: return nil } diff --git a/sim/core/apl_actions_core.go b/sim/core/apl_actions_core.go index ddce408355..ca5cc2a92c 100644 --- a/sim/core/apl_actions_core.go +++ b/sim/core/apl_actions_core.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "time" "github.com/wowsims/wotlk/sim/core/proto" ) @@ -208,38 +209,92 @@ type APLActionWait struct { defaultAPLActionImpl unit *Unit duration APLValue + + curWaitTime time.Duration } func (rot *APLRotation) newActionWait(config *proto.APLActionWait) APLActionImpl { unit := rot.unit + durationVal := rot.coerceTo(rot.newAPLValue(config.Duration), proto.APLValueType_ValueTypeDuration) + if durationVal == nil { + return nil + } + return &APLActionWait{ unit: unit, - duration: rot.coerceTo(rot.newAPLValue(config.Duration), proto.APLValueType_ValueTypeDuration), + duration: durationVal, } } func (action *APLActionWait) GetAPLValues() []APLValue { return []APLValue{action.duration} } func (action *APLActionWait) IsReady(sim *Simulation) bool { - return action.duration != nil + return true } func (action *APLActionWait) Execute(sim *Simulation) { - waitUntilTime := sim.CurrentTime + action.duration.GetDuration(sim) - action.unit.waitUntilTime = waitUntilTime + action.unit.Rotation.controllingAction = action + action.curWaitTime = sim.CurrentTime + action.duration.GetDuration(sim) - if waitUntilTime > action.unit.GCD.ReadyAt() { - action.unit.WaitUntil(sim, waitUntilTime) - return - } pa := &PendingAction{ Priority: ActionPriorityLow, OnAction: action.unit.gcdAction.OnAction, - NextActionAt: waitUntilTime, + NextActionAt: action.curWaitTime, } sim.AddPendingAction(pa) } +func (action *APLActionWait) GetNextAction(sim *Simulation) *APLAction { + if sim.CurrentTime >= action.curWaitTime { + action.unit.Rotation.controllingAction = nil + return action.unit.Rotation.getNextAction(sim) + } else { + return nil + } +} + func (action *APLActionWait) String() string { return fmt.Sprintf("Wait(%s)", action.duration) } + +type APLActionWaitUntil struct { + defaultAPLActionImpl + unit *Unit + condition APLValue +} + +func (rot *APLRotation) newActionWaitUntil(config *proto.APLActionWaitUntil) APLActionImpl { + unit := rot.unit + conditionVal := rot.coerceTo(rot.newAPLValue(config.Condition), proto.APLValueType_ValueTypeBool) + if conditionVal == nil { + return nil + } + + return &APLActionWaitUntil{ + unit: unit, + condition: conditionVal, + } +} +func (action *APLActionWaitUntil) GetAPLValues() []APLValue { + return []APLValue{action.condition} +} +func (action *APLActionWaitUntil) IsReady(sim *Simulation) bool { + return !action.condition.GetBool(sim) +} + +func (action *APLActionWaitUntil) Execute(sim *Simulation) { + action.unit.Rotation.controllingAction = action +} + +func (action *APLActionWaitUntil) GetNextAction(sim *Simulation) *APLAction { + if action.condition.GetBool(sim) { + action.unit.Rotation.controllingAction = nil + return action.unit.Rotation.getNextAction(sim) + } else { + return nil + } +} + +func (action *APLActionWaitUntil) String() string { + return fmt.Sprintf("WaitUntil(%s)", action.condition) +} diff --git a/sim/core/apl_actions_sequences.go b/sim/core/apl_actions_sequences.go index 3b38794200..d0bd71fb8d 100644 --- a/sim/core/apl_actions_sequences.go +++ b/sim/core/apl_actions_sequences.go @@ -127,12 +127,28 @@ func (action *APLActionStrictSequence) IsReady(sim *Simulation) bool { return true } func (action *APLActionStrictSequence) Execute(sim *Simulation) { - action.actions[action.curIdx].Execute(sim) - action.curIdx++ + action.unit.Rotation.controllingAction = action +} +func (action *APLActionStrictSequence) GetNextAction(sim *Simulation) *APLAction { + if action.actions[action.curIdx].IsReady(sim) { + nextAction := action.actions[action.curIdx] - if action.curIdx == len(action.actions) { + action.curIdx++ + if action.curIdx == len(action.actions) { + action.curIdx = 0 + action.unit.Rotation.controllingAction = nil + } + + return nextAction + } else if action.unit.GCD.IsReady(sim) { + // If the GCD is ready when the next subaction isn't, it means the sequence is bad + // so reset and exit the sequence. action.curIdx = 0 - action.unit.Rotation.strictSequence = nil + action.unit.Rotation.controllingAction = nil + return action.unit.Rotation.getNextAction(sim) + } else { + // Return nil to wait for the GCD to become ready. + return nil } } func (action *APLActionStrictSequence) String() string { diff --git a/sim/core/unit.go b/sim/core/unit.go index e26092fbf2..321888eb22 100644 --- a/sim/core/unit.go +++ b/sim/core/unit.go @@ -141,7 +141,6 @@ type Unit struct { waitingForEnergy float64 waitingForMana float64 waitStartTime time.Duration - waitUntilTime time.Duration // Cached mana return values per tick. manaTickWhileCasting float64 @@ -456,8 +455,6 @@ func (unit *Unit) reset(sim *Simulation, agent Agent) { if unit.Rotation != nil { unit.Rotation.reset(sim) } - - unit.waitUntilTime = 0 } func (unit *Unit) startPull(sim *Simulation) { diff --git a/sim/hunter/TestAPL.results b/sim/hunter/TestAPL.results index a6f0ddeb79..b35d35beae 100644 --- a/sim/hunter/TestAPL.results +++ b/sim/hunter/TestAPL.results @@ -810,8 +810,8 @@ dps_results: { dps_results: { key: "TestAPL-AllItems-UndeadSlayer'sBlessedArmor" value: { - dps: 5385.71714 - tps: 4633.2316 + dps: 5386.46454 + tps: 4633.99134 } } dps_results: { diff --git a/sim/rogue/TestAssassination.results b/sim/rogue/TestAssassination.results index 2f8f2eaedc..192bc2112f 100644 --- a/sim/rogue/TestAssassination.results +++ b/sim/rogue/TestAssassination.results @@ -13,7 +13,7 @@ character_stats_results: { final_stats: 221 final_stats: 0 final_stats: 5636.84 - final_stats: 469.94995 + final_stats: 469.94994 final_stats: 2072.9756 final_stats: 221 final_stats: 94 diff --git a/sim/rogue/TestCombat.results b/sim/rogue/TestCombat.results index fd3da1f76e..8073f2407f 100644 --- a/sim/rogue/TestCombat.results +++ b/sim/rogue/TestCombat.results @@ -13,7 +13,7 @@ character_stats_results: { final_stats: 221 final_stats: 0 final_stats: 5862.3136 - final_stats: 469.94995 + final_stats: 469.94994 final_stats: 2164.78757 final_stats: 221 final_stats: 94 diff --git a/ui/core/components/individual_sim_ui/apl_actions.ts b/ui/core/components/individual_sim_ui/apl_actions.ts index 05d966929b..1f9645fc5c 100644 --- a/ui/core/components/individual_sim_ui/apl_actions.ts +++ b/ui/core/components/individual_sim_ui/apl_actions.ts @@ -11,6 +11,7 @@ import { APLActionCancelAura, APLActionTriggerICD, APLActionWait, + APLActionWaitUntil, APLValue, APLActionMultishield, } from '../../proto/apl.js'; @@ -402,7 +403,7 @@ const actionKindFactories: {[f in NonNullable]: ActionKindConfig< ['wait']: inputBuilder({ label: 'Wait', submenu: ['Misc'], - shortDescription: 'Pauses the GCD for a specified amount of time.', + shortDescription: 'Pauses all APL actions for a specified amount of time.', includeIf: (player: Player, isPrepull: boolean) => !isPrepull, newValue: () => APLActionWait.create({ duration: { @@ -418,6 +419,16 @@ const actionKindFactories: {[f in NonNullable]: ActionKindConfig< AplValues.valueFieldConfig('duration'), ], }), + ['waitUntil']: inputBuilder({ + label: 'Wait Until', + submenu: ['Misc'], + shortDescription: 'Pauses all APL actions until the specified condition is True.', + includeIf: (player: Player, isPrepull: boolean) => !isPrepull, + newValue: () => APLActionWaitUntil.create(), + fields: [ + AplValues.valueFieldConfig('condition'), + ], + }), ['changeTarget']: inputBuilder({ label: 'Change Target', submenu: ['Misc'],