Skip to content

Commit

Permalink
Merge pull request #4118 from wowsims/bfix
Browse files Browse the repository at this point in the history
Use APL for pets and targets, and make cast failure behavior consistent
  • Loading branch information
jimmyt857 authored Jan 2, 2024
2 parents b70dc25 + b929516 commit 9fcdb30
Show file tree
Hide file tree
Showing 57 changed files with 4,835 additions and 4,820 deletions.
12 changes: 9 additions & 3 deletions proto/apl.proto
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ message APLListItem {
APLAction action = 3; // The action to be performed.
}

// NextIndex: 19
// NextIndex: 20
message APLAction {
APLValue condition = 1; // If set, action will only execute if value is true or != 0.

Expand Down Expand Up @@ -76,8 +76,11 @@ message APLAction {
APLActionTriggerICD trigger_icd = 11;
APLActionItemSwap item_swap = 17;

// Class or Spec-specific actions
APLActionCatOptimalRotationAction cat_optimal_rotation_action = 18;
// Class or Spec-specific actions
APLActionCatOptimalRotationAction cat_optimal_rotation_action = 18;

// Internal use only, not exposed in UI.
APLActionCustomRotation custom_rotation = 19;
}
}

Expand Down Expand Up @@ -277,6 +280,9 @@ message APLActionCatOptimalRotationAction {
bool flower_weave = 9;
}

message APLActionCustomRotation {
}

///////////////////////////////////////////////////////////////////////////
// VALUES
///////////////////////////////////////////////////////////////////////////
Expand Down
11 changes: 4 additions & 7 deletions sim/common/wotlk/nibelung.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package wotlk

import (
"time"

"github.com/wowsims/wotlk/sim/core"
"github.com/wowsims/wotlk/sim/core/stats"
"time"
)

var valkyrStats = stats.Stats{
Expand Down Expand Up @@ -71,12 +72,8 @@ func (valkyr *ValkyrPet) Initialize() {}

func (valkyr *ValkyrPet) Reset(_ *core.Simulation) {}

func (valkyr *ValkyrPet) OnGCDReady(sim *core.Simulation) {
target := valkyr.CurrentTarget

if valkyr.smite.CanCast(sim, target) {
valkyr.smite.Cast(sim, target)
}
func (valkyr *ValkyrPet) ExecuteCustomRotation(sim *core.Simulation) {
valkyr.smite.Cast(sim, valkyr.CurrentTarget)
}

func (valkyr *ValkyrPet) GetPet() *core.Pet {
Expand Down
4 changes: 4 additions & 0 deletions sim/core/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ type Agent interface {
// Should return nil when the config doesn't match any custom behaviors.
NewAPLValue(rot *APLRotation, config *proto.APLValue) APLValue
NewAPLAction(rot *APLRotation, config *proto.APLAction) APLActionImpl

// Implements custom rotation behavior. Usually for pets and targets but can be used
// for players too.
ExecuteCustomRotation(sim *Simulation)
}

type ActionID struct {
Expand Down
13 changes: 13 additions & 0 deletions sim/core/apl.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ func (rot *APLRotation) doAndRecordWarnings(warningsList *[]string, isPrepull bo
rot.parsingPrepull = false
}

func (unit *Unit) newCustomRotation() *APLRotation {
return unit.newAPLRotation(&proto.APLRotation{
Type: proto.APLRotation_TypeAPL,
PriorityList: []*proto.APLListItem{
{
Action: &proto.APLAction{
Action: &proto.APLAction_CustomRotation{},
},
},
},
})
}

func (unit *Unit) newAPLRotation(config *proto.APLRotation) *APLRotation {
if config == nil || !unit.IsUsingAPL {
return nil
Expand Down
6 changes: 5 additions & 1 deletion sim/core/apl_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func (rot *APLRotation) newAPLActionImpl(config *proto.APLAction) APLActionImpl
return nil
}

customAction := rot.unit.Env.Raid.GetPlayerFromUnit(rot.unit).NewAPLAction(rot, config)
customAction := rot.unit.Env.GetAgentFromUnit(rot.unit).NewAPLAction(rot, config)
if customAction != nil {
return customAction
}
Expand Down Expand Up @@ -177,6 +177,10 @@ func (rot *APLRotation) newAPLActionImpl(config *proto.APLAction) APLActionImpl
return rot.newActionTriggerICD(config.GetTriggerIcd())
case *proto.APLAction_ItemSwap:
return rot.newActionItemSwap(config.GetItemSwap())

case *proto.APLAction_CustomRotation:
return rot.newActionCustomRotation(config.GetCustomRotation())

default:
return nil
}
Expand Down
35 changes: 35 additions & 0 deletions sim/core/apl_actions_misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package core

import (
"fmt"
"time"

"github.com/wowsims/wotlk/sim/core/proto"
)
Expand Down Expand Up @@ -161,3 +162,37 @@ func (action *APLActionItemSwap) Execute(sim *Simulation) {
func (action *APLActionItemSwap) String() string {
return fmt.Sprintf("Item Swap(%s)", action.swapSet)
}

type APLActionCustomRotation struct {
defaultAPLActionImpl
unit *Unit
agent Agent

lastExecutedAt time.Duration
}

func (rot *APLRotation) newActionCustomRotation(config *proto.APLActionCustomRotation) APLActionImpl {
agent := rot.unit.Env.GetAgentFromUnit(rot.unit)
if agent == nil {
panic("Agent not found for custom rotation")
}

return &APLActionCustomRotation{
unit: rot.unit,
agent: agent,
}
}
func (action *APLActionCustomRotation) Reset(sim *Simulation) {
action.lastExecutedAt = -1
}
func (action *APLActionCustomRotation) IsReady(sim *Simulation) bool {
// Prevent infinite loops by only allowing this action to be performed once at each timestamp.
return action.lastExecutedAt != sim.CurrentTime
}
func (action *APLActionCustomRotation) Execute(sim *Simulation) {
action.lastExecutedAt = sim.CurrentTime
action.agent.ExecuteCustomRotation(sim)
}
func (action *APLActionCustomRotation) String() string {
return "Custom Rotation()"
}
2 changes: 1 addition & 1 deletion sim/core/apl_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (rot *APLRotation) newAPLValue(config *proto.APLValue) APLValue {
return nil
}

customValue := rot.unit.Env.Raid.GetPlayerFromUnit(rot.unit).NewAPLValue(rot, config)
customValue := rot.unit.Env.GetAgentFromUnit(rot.unit).NewAPLValue(rot, config)
if customValue != nil {
return customValue
}
Expand Down
24 changes: 11 additions & 13 deletions sim/core/cast.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,13 @@ func (cast *Cast) EffectiveTime() time.Duration {
type CastFunc func(*Simulation, *Unit)
type CastSuccessFunc func(*Simulation, *Unit) bool

func (spell *Spell) castFailureHelper(sim *Simulation, gracefulFailure bool, message string, vals ...any) bool {
func (spell *Spell) castFailureHelper(sim *Simulation, message string, vals ...any) bool {
if sim.CurrentTime < 0 && spell.Unit.IsUsingAPL {
spell.Unit.Rotation.ValidationWarning(fmt.Sprintf(spell.ActionID.String()+" failed to cast: "+message, vals...))
} else if gracefulFailure {
} else {
if sim.Log != nil && !spell.Flags.Matches(SpellFlagNoLogs) {
spell.Unit.Log(sim, fmt.Sprintf(spell.ActionID.String()+" failed to cast: "+message, vals...))
}
} else {
panic(fmt.Sprintf(spell.ActionID.String()+" failed to cast: "+message, vals...))
}
return false
}
Expand All @@ -94,13 +92,13 @@ func (spell *Spell) makeCastFunc(config CastConfig) CastSuccessFunc {

if spell.ExtraCastCondition != nil {
if !spell.ExtraCastCondition(sim, target) {
return spell.castFailureHelper(sim, true, "extra spell condition")
return spell.castFailureHelper(sim, "extra spell condition")
}
}

if spell.Cost != nil {
if !spell.Cost.MeetsRequirement(spell) {
return spell.castFailureHelper(sim, true, spell.Cost.CostFailureReason(sim, spell))
return spell.castFailureHelper(sim, spell.Cost.CostFailureReason(sim, spell))
}
}

Expand All @@ -113,26 +111,26 @@ func (spell *Spell) makeCastFunc(config CastConfig) CastSuccessFunc {
if config.CD.Timer != nil {
// By panicking if spell is on CD, we force each sim to properly check for their own CDs.
if !spell.CD.IsReady(sim) {
return spell.castFailureHelper(sim, false, "still on cooldown for %s, curTime = %s", spell.CD.TimeToReady(sim), sim.CurrentTime)
return spell.castFailureHelper(sim, "still on cooldown for %s, curTime = %s", spell.CD.TimeToReady(sim), sim.CurrentTime)
}
spell.CD.Set(sim.CurrentTime + spell.CurCast.CastTime + spell.CD.Duration)
}

if config.SharedCD.Timer != nil {
// By panicking if spell is on CD, we force each sim to properly check for their own CDs.
if !spell.SharedCD.IsReady(sim) {
return spell.castFailureHelper(sim, false, "still on shared cooldown for %s, curTime = %s", spell.SharedCD.TimeToReady(sim), sim.CurrentTime)
return spell.castFailureHelper(sim, "still on shared cooldown for %s, curTime = %s", spell.SharedCD.TimeToReady(sim), sim.CurrentTime)
}
spell.SharedCD.Set(sim.CurrentTime + spell.CurCast.CastTime + spell.SharedCD.Duration)
}

// By panicking if spell is on CD, we force each sim to properly check for their own CDs.
if spell.CurCast.GCD != 0 && !spell.Unit.GCD.IsReady(sim) {
return spell.castFailureHelper(sim, false, "GCD on cooldown for %s, curTime = %s", spell.Unit.GCD.TimeToReady(sim), sim.CurrentTime)
return spell.castFailureHelper(sim, "GCD on cooldown for %s, curTime = %s", spell.Unit.GCD.TimeToReady(sim), sim.CurrentTime)
}

if hc := spell.Unit.Hardcast; hc.Expires > sim.CurrentTime {
return spell.castFailureHelper(sim, false, "casting/channeling %v for %s, curTime = %s", hc.ActionID, hc.Expires-sim.CurrentTime, sim.CurrentTime)
return spell.castFailureHelper(sim, "casting/channeling %v for %s, curTime = %s", hc.ActionID, hc.Expires-sim.CurrentTime, sim.CurrentTime)
}

if effectiveTime := spell.CurCast.EffectiveTime(); effectiveTime != 0 {
Expand Down Expand Up @@ -204,14 +202,14 @@ func (spell *Spell) makeCastFuncSimple() CastSuccessFunc {
return func(sim *Simulation, target *Unit) bool {
if spell.ExtraCastCondition != nil {
if !spell.ExtraCastCondition(sim, target) {
return spell.castFailureHelper(sim, true, "extra spell condition")
return spell.castFailureHelper(sim, "extra spell condition")
}
}

if spell.CD.Timer != nil {
// By panicking if spell is on CD, we force each sim to properly check for their own CDs.
if !spell.CD.IsReady(sim) {
return spell.castFailureHelper(sim, false, "still on cooldown for %s, curTime = %s", spell.CD.TimeToReady(sim), sim.CurrentTime)
return spell.castFailureHelper(sim, "still on cooldown for %s, curTime = %s", spell.CD.TimeToReady(sim), sim.CurrentTime)
}

spell.CD.Set(sim.CurrentTime + spell.CD.Duration)
Expand All @@ -220,7 +218,7 @@ func (spell *Spell) makeCastFuncSimple() CastSuccessFunc {
if spell.SharedCD.Timer != nil {
// By panicking if spell is on CD, we force each sim to properly check for their own CDs.
if !spell.SharedCD.IsReady(sim) {
return spell.castFailureHelper(sim, false, "still on shared cooldown for %s, curTime = %s", spell.SharedCD.TimeToReady(sim), sim.CurrentTime)
return spell.castFailureHelper(sim, "still on shared cooldown for %s, curTime = %s", spell.SharedCD.TimeToReady(sim), sim.CurrentTime)
}

spell.SharedCD.Set(sim.CurrentTime + spell.SharedCD.Duration)
Expand Down
18 changes: 18 additions & 0 deletions sim/core/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ func (env *Environment) finalize(raidProto *proto.Raid, _ *proto.Encounter, raid

for _, target := range env.Encounter.Targets {
target.finalize()
if target.AI != nil {
target.Rotation = target.newCustomRotation()
}
}

for _, party := range env.Raid.Parties {
Expand All @@ -156,6 +159,7 @@ func (env *Environment) finalize(raidProto *proto.Raid, _ *proto.Encounter, raid
character.Finalize()
for _, pet := range character.Pets {
pet.Finalize()
pet.Rotation = pet.newCustomRotation()
}
}
}
Expand Down Expand Up @@ -257,6 +261,20 @@ func (env *Environment) NextTarget(target *Unit) *Target {
func (env *Environment) NextTargetUnit(target *Unit) *Unit {
return &env.NextTarget(target).Unit
}
func (env *Environment) GetAgentFromUnit(unit *Unit) Agent {
raidAgent := env.Raid.GetPlayerFromUnit(unit)
if raidAgent != nil {
return raidAgent
}

for _, target := range env.Encounter.Targets {
if unit == &target.Unit {
return target
}
}

return nil
}

func (env *Environment) GetUnit(ref *proto.UnitReference, contextUnit *Unit) *Unit {
if ref == nil {
Expand Down
2 changes: 2 additions & 0 deletions sim/core/pet.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func NewPet(name string, owner *Character, baseStats stats.Stats, statInheritanc
PseudoStats: stats.NewPseudoStats(),
auraTracker: newAuraTracker(),
Metrics: NewUnitMetrics(),
IsUsingAPL: true,

StatDependencyManager: stats.NewStatDependencyManager(),
},
Expand Down Expand Up @@ -268,3 +269,4 @@ func (pet *Pet) GetCharacter() *Character {
func (pet *Pet) AddRaidBuffs(_ *proto.RaidBuffs) {}
func (pet *Pet) AddPartyBuffs(_ *proto.PartyBuffs) {}
func (pet *Pet) ApplyTalents() {}
func (pet *Pet) OnGCDReady(_ *Simulation) {}
1 change: 1 addition & 0 deletions sim/core/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ func NewTarget(options *proto.Target, targetIndex int32) *Target {
preset := GetPresetTargetWithID(options.Id)
if preset != nil && preset.AI != nil {
target.AI = preset.AI()
target.IsUsingAPL = true
}

return target
Expand Down
29 changes: 8 additions & 21 deletions sim/core/target_ai.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package core

import (
"fmt"
"github.com/wowsims/wotlk/sim/core/proto"
"log"

"github.com/wowsims/wotlk/sim/core/proto"
)

type TargetAI interface {
Initialize(*Target, *proto.Target)
Reset(*Simulation)
DoAction(*Simulation)
ExecuteCustomRotation(*Simulation)
}

func (target *Target) initialize(config *proto.Target) {
Expand Down Expand Up @@ -44,15 +44,7 @@ func (target *Target) initialize(config *proto.Target) {
target.gcdAction = &PendingAction{
Priority: ActionPriorityGCD,
OnAction: func(sim *Simulation) {
if target.GCD.IsReady(sim) {
target.OnGCDReady(sim)

if !target.doNothing && target.GCD.IsReady(sim) && (!target.IsWaiting() && !target.IsWaitingForMana()) {
msg := fmt.Sprintf("Target `%s` did not perform any actions. Either this is a bug or agent should use 'WaitUntil' or 'WaitForMana' to explicitly wait.\n\tIf character has no action to perform use 'DoNothing'.", target.Label)
panic(msg)
}
target.doNothing = false
}
target.Rotation.DoNextAction(sim)
},
}
}
Expand All @@ -69,16 +61,11 @@ func (target *Target) DoNothing() {
target.doNothing = true
}

func (target *Target) OnAutoAttack(sim *Simulation, _ *Spell) {
if target.GCD.IsReady(sim) {
if target.AI != nil {
target.AI.DoAction(sim)
}
}
}
func (target *Target) OnGCDReady(sim *Simulation) {
func (target *Target) OnAutoAttack(sim *Simulation, _ *Spell) {}
func (target *Target) OnGCDReady(sim *Simulation) {}
func (target *Target) ExecuteCustomRotation(sim *Simulation) {
if target.AI != nil {
target.AI.DoAction(sim)
target.AI.ExecuteCustomRotation(sim)
}
}

Expand Down
1 change: 1 addition & 0 deletions sim/core/target_dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ func (td *TargetDummy) OnGCDReady(sim *Simulation) {
td.DoNothing()
}
func (td *TargetDummy) OnAutoAttack(sim *Simulation, spell *Spell) {}
func (td *TargetDummy) ExecuteCustomRotation(sim *Simulation) {}
4 changes: 4 additions & 0 deletions sim/core/unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -567,3 +567,7 @@ func (unit *Unit) DoneAPLLoop(sim *Simulation, usedGCD bool) {
unit.ManaRequired = 0
}
}

func (unit *Unit) ExecuteCustomRotation(sim *Simulation) {
panic("Unimplemented ExecuteCustomRotation")
}
2 changes: 1 addition & 1 deletion sim/deathknight/bloodworm_pet.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (bloodworm *BloodwormPet) Initialize() {
func (bloodworm *BloodwormPet) Reset(_ *core.Simulation) {
}

func (bloodworm *BloodwormPet) OnGCDReady(_ *core.Simulation) {
func (bloodworm *BloodwormPet) ExecuteCustomRotation(_ *core.Simulation) {
}

func (bloodworm *BloodwormPet) enable(sim *core.Simulation) {
Expand Down
Loading

0 comments on commit 9fcdb30

Please sign in to comment.