Skip to content

Commit

Permalink
Merge pull request #2362 from wowsims/fixes
Browse files Browse the repository at this point in the history
Refactor spell costs
  • Loading branch information
jimmyt857 authored Jan 12, 2023
2 parents f13e976 + 32eeb16 commit 81ed3b1
Show file tree
Hide file tree
Showing 120 changed files with 1,027 additions and 1,250 deletions.
51 changes: 30 additions & 21 deletions sim/core/cast.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (spell *Spell) ApplyCostModifiers(cost float64) float64 {
}

func (spell *Spell) wrapCastFuncInit(config CastConfig, onCastComplete CastSuccessFunc) CastSuccessFunc {
if config.DefaultCast == emptyCast {
if spell.DefaultCast == emptyCast {
return onCastComplete
}

Expand All @@ -113,29 +113,27 @@ func (spell *Spell) wrapCastFuncInit(config CastConfig, onCastComplete CastSucce
}

func (spell *Spell) wrapCastFuncResources(config CastConfig, onCastComplete CastFunc) CastSuccessFunc {
if spell.ResourceType == 0 || config.DefaultCast.Cost == 0 {
if spell.ResourceType == 0 || spell.DefaultCast.Cost == 0 {
return func(sim *Simulation, target *Unit) bool {
onCastComplete(sim, target)
return true
}
}

switch spell.ResourceType {
case stats.Mana:
if spell.Cost != nil {
return func(sim *Simulation, target *Unit) bool {
spell.CurCast.Cost = spell.ApplyCostModifiers(spell.CurCast.Cost)
if spell.Unit.CurrentMana() < spell.CurCast.Cost {
if !spell.Cost.MeetsRequirement(spell) {
if sim.Log != nil && !spell.Flags.Matches(SpellFlagNoLogs) {
spell.Unit.Log(sim, "Failed casting %s, not enough mana. (Current Mana = %0.03f, Mana Cost = %0.03f)",
spell.ActionID, spell.Unit.CurrentMana(), spell.CurCast.Cost)
spell.Cost.LogCostFailure(sim, spell)
}
return false
}

// Mana is subtracted at the end of the cast.
onCastComplete(sim, target)
return true
}
}

switch spell.ResourceType {
case stats.Rage:
return func(sim *Simulation, target *Unit) bool {
spell.CurCast.Cost = spell.ApplyCostModifiers(spell.CurCast.Cost)
Expand Down Expand Up @@ -185,7 +183,7 @@ func (spell *Spell) wrapCastFuncResources(config CastConfig, onCastComplete Cast
}

func (spell *Spell) wrapCastFuncHaste(config CastConfig, onCastComplete CastFunc) CastFunc {
if config.IgnoreHaste || (config.DefaultCast.GCD == 0 && config.DefaultCast.CastTime == 0 && config.DefaultCast.ChannelTime == 0) {
if config.IgnoreHaste || (spell.DefaultCast.GCD == 0 && spell.DefaultCast.CastTime == 0 && spell.DefaultCast.ChannelTime == 0) {
return onCastComplete
}

Expand All @@ -199,11 +197,11 @@ func (spell *Spell) wrapCastFuncHaste(config CastConfig, onCastComplete CastFunc
}

func (spell *Spell) wrapCastFuncGCD(config CastConfig, onCastComplete CastFunc) CastFunc {
if config.DefaultCast == emptyCast { // spells that are not actually cast (e.g. auto attacks, procs)
if spell.DefaultCast == emptyCast { // spells that are not actually cast (e.g. auto attacks, procs)
return onCastComplete
}

if config.DefaultCast.GCD == 0 { // mostly cooldowns (e.g. nature's swiftness, presence of mind)
if spell.DefaultCast.GCD == 0 { // mostly cooldowns (e.g. nature's swiftness, presence of mind)
return func(sim *Simulation, target *Unit) {
if hc := spell.Unit.Hardcast; hc.Expires > sim.CurrentTime {
panic(fmt.Sprintf("Trying to cast %s but casting/channeling %v for %s, curTime = %s", spell.ActionID, hc.ActionID, hc.Expires-sim.CurrentTime, sim.CurrentTime))
Expand Down Expand Up @@ -276,8 +274,8 @@ func (spell *Spell) wrapCastFuncSharedCooldown(config CastConfig, onCastComplete

func (spell *Spell) makeCastFuncWait(config CastConfig, onCastComplete CastFunc) CastFunc {
if !spell.Flags.Matches(SpellFlagNoOnCastComplete) {
configOnCastComplete := config.OnCastComplete
oldOnCastComplete1 := onCastComplete
configOnCastComplete := config.OnCastComplete
onCastComplete = func(sim *Simulation, target *Unit) {
oldOnCastComplete1(sim, target)
if configOnCastComplete != nil {
Expand All @@ -287,18 +285,15 @@ func (spell *Spell) makeCastFuncWait(config CastConfig, onCastComplete CastFunc)
}
}

if spell.ResourceType == stats.Mana && config.DefaultCast.Cost != 0 {
if spell.Cost != nil {
oldOnCastComplete2 := onCastComplete
onCastComplete = func(sim *Simulation, target *Unit) {
if spell.CurCast.Cost > 0 {
spell.Unit.SpendMana(sim, spell.CurCast.Cost, spell.ResourceMetrics)
spell.Unit.PseudoStats.FiveSecondRuleRefreshTime = sim.CurrentTime + time.Second*5
}
spell.Cost.SpendCost(sim, spell)
oldOnCastComplete2(sim, target)
}
}

if config.DefaultCast.ChannelTime > 0 {
if spell.DefaultCast.ChannelTime > 0 {
return func(sim *Simulation, target *Unit) {
spell.Unit.Hardcast = Hardcast{Expires: sim.CurrentTime + spell.CurCast.ChannelTime, ActionID: spell.ActionID}
if sim.Log != nil && !spell.Flags.Matches(SpellFlagNoLogs) {
Expand All @@ -310,7 +305,7 @@ func (spell *Spell) makeCastFuncWait(config CastConfig, onCastComplete CastFunc)
}
}

if config.DefaultCast.CastTime == 0 {
if spell.DefaultCast.CastTime == 0 {
if spell.Flags.Matches(SpellFlagNoLogs) {
return onCastComplete
} else {
Expand Down Expand Up @@ -362,3 +357,17 @@ func (spell *Spell) makeCastFuncWait(config CastConfig, onCastComplete CastFunc)
}
}
}

// Handles computing the cost of spells and checking whether the Unit
// meets them.
type SpellCost interface {
// Whether the Unit associated with the spell meets the resource cost
// requirements to cast the spell.
MeetsRequirement(*Spell) bool

// Logs a message for when the cast fails due to lack of resources.
LogCostFailure(*Simulation, *Spell)

// Subtracts the resources used from a cast from the Unit.
SpendCost(*Simulation, *Spell)
}
35 changes: 35 additions & 0 deletions sim/core/mana.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,3 +293,38 @@ func (mb *manaBar) reset() {

mb.currentMana = mb.unit.MaxMana()
}

type ManaCostOptions struct {
BaseCost float64
FlatCost float64 // Alternative to BaseCost for giving a flat value.
Multiplier float64 // It's OK to leave this at 0, will default to 1.
}
type ManaCost struct {
ResourceMetrics *ResourceMetrics
}

func newManaCost(spell *Spell, options ManaCostOptions) *ManaCost {
spell.ResourceType = stats.Mana
spell.BaseCost = TernaryFloat64(options.FlatCost > 0, options.FlatCost, options.BaseCost*spell.Unit.BaseMana)
spell.DefaultCast.Cost = spell.BaseCost * TernaryFloat64(options.Multiplier == 0, 1, options.Multiplier)

return &ManaCost{
ResourceMetrics: spell.Unit.NewManaMetrics(spell.ActionID),
}
}

func (mc *ManaCost) MeetsRequirement(spell *Spell) bool {
spell.CurCast.Cost = spell.ApplyCostModifiers(spell.CurCast.Cost)
return spell.Unit.CurrentMana() >= spell.CurCast.Cost
}
func (mc *ManaCost) LogCostFailure(sim *Simulation, spell *Spell) {
spell.Unit.Log(sim,
"Failed casting %s, not enough mana. (Current Mana = %0.03f, Mana Cost = %0.03f)",
spell.ActionID, spell.Unit.CurrentMana(), spell.CurCast.Cost)
}
func (mc *ManaCost) SpendCost(sim *Simulation, spell *Spell) {
if spell.CurCast.Cost > 0 {
spell.Unit.SpendMana(sim, spell.CurCast.Cost, mc.ResourceMetrics)
spell.Unit.PseudoStats.FiveSecondRuleRefreshTime = sim.CurrentTime + time.Second*5
}
}
44 changes: 26 additions & 18 deletions sim/core/spell.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ type SpellConfig struct {
ResourceType stats.Stat
BaseCost float64

Cast CastConfig
ManaCost ManaCostOptions
Cast CastConfig

BonusHitRating float64
BonusCritRating float64
Expand Down Expand Up @@ -79,6 +80,9 @@ type Spell struct {
// are calculated using the base cost.
BaseCost float64

// Cost for the spell.
Cost SpellCost

// Default cast parameters with all static effects applied.
DefaultCast Cast

Expand Down Expand Up @@ -207,23 +211,27 @@ func (unit *Unit) RegisterSpell(config SpellConfig) *Spell {
spell.SchoolIndex = stats.SchoolIndexShadow
}

switch spell.ResourceType {
case stats.Mana:
spell.ResourceMetrics = spell.Unit.NewManaMetrics(spell.ActionID)
case stats.Rage:
spell.ResourceMetrics = spell.Unit.NewRageMetrics(spell.ActionID)
case stats.Energy:
spell.ResourceMetrics = spell.Unit.NewEnergyMetrics(spell.ActionID)
case stats.RunicPower:
spell.ResourceMetrics = spell.Unit.NewRunicPowerMetrics(spell.ActionID)
case stats.BloodRune:
spell.ResourceMetrics = spell.Unit.NewBloodRuneMetrics(spell.ActionID)
case stats.FrostRune:
spell.ResourceMetrics = spell.Unit.NewFrostRuneMetrics(spell.ActionID)
case stats.UnholyRune:
spell.ResourceMetrics = spell.Unit.NewUnholyRuneMetrics(spell.ActionID)
case stats.DeathRune:
spell.ResourceMetrics = spell.Unit.NewDeathRuneMetrics(spell.ActionID)
if config.ManaCost.BaseCost != 0 || config.ManaCost.FlatCost != 0 {
spell.Cost = newManaCost(spell, config.ManaCost)
}

if spell.Cost == nil {
switch spell.ResourceType {
case stats.Rage:
spell.ResourceMetrics = spell.Unit.NewRageMetrics(spell.ActionID)
case stats.Energy:
spell.ResourceMetrics = spell.Unit.NewEnergyMetrics(spell.ActionID)
case stats.RunicPower:
spell.ResourceMetrics = spell.Unit.NewRunicPowerMetrics(spell.ActionID)
case stats.BloodRune:
spell.ResourceMetrics = spell.Unit.NewBloodRuneMetrics(spell.ActionID)
case stats.FrostRune:
spell.ResourceMetrics = spell.Unit.NewFrostRuneMetrics(spell.ActionID)
case stats.UnholyRune:
spell.ResourceMetrics = spell.Unit.NewUnholyRuneMetrics(spell.ActionID)
case stats.DeathRune:
spell.ResourceMetrics = spell.Unit.NewDeathRuneMetrics(spell.ActionID)
}
}

if spell.ResourceType == 0 && spell.DefaultCast.Cost != 0 {
Expand Down
23 changes: 10 additions & 13 deletions sim/druid/faerie_fire.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import (
"time"

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

func (druid *Druid) registerFaerieFireSpell() {
actionID := core.ActionID{SpellID: 770}
resourceType := stats.Mana
baseCost := 0.08 * druid.BaseMana
manaCostOptions := core.ManaCostOptions{
BaseCost: 0.08,
}
gcd := core.GCDDefault
ignoreHaste := false
cd := core.Cooldown{}
Expand All @@ -19,8 +19,7 @@ func (druid *Druid) registerFaerieFireSpell() {

if druid.InForm(Cat | Bear) {
actionID = core.ActionID{SpellID: 16857}
resourceType = 0
baseCost = 0
manaCostOptions = core.ManaCostOptions{}
gcd = time.Second
ignoreHaste = true
flags = core.SpellFlagNone
Expand All @@ -34,17 +33,15 @@ func (druid *Druid) registerFaerieFireSpell() {
druid.FaerieFireAura = core.FaerieFireAura(druid.CurrentTarget, druid.Talents.ImprovedFaerieFire)

druid.FaerieFire = druid.RegisterSpell(core.SpellConfig{
ActionID: actionID,
SpellSchool: core.SpellSchoolNature,
ProcMask: core.ProcMaskSpellDamage,
ResourceType: resourceType,
BaseCost: baseCost,
Flags: flags,
ActionID: actionID,
SpellSchool: core.SpellSchoolNature,
ProcMask: core.ProcMaskSpellDamage,
Flags: flags,

ManaCost: manaCostOptions,
Cast: core.CastConfig{
DefaultCast: core.Cast{
Cost: baseCost,
GCD: gcd,
GCD: gcd,
},
IgnoreHaste: ignoreHaste,
CD: cd,
Expand Down
16 changes: 7 additions & 9 deletions sim/druid/fake_gotw.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,24 @@ package druid
import (
"github.com/wowsims/wotlk/sim/core"
"github.com/wowsims/wotlk/sim/core/proto"
"github.com/wowsims/wotlk/sim/core/stats"
)

// This is 'fake' because it doesnt actually account for any actual buff updating
// this is only used as a 'clearcast fisher' spell
func (druid *Druid) registerFakeGotw() {

baseCost := druid.BaseMana * core.TernaryFloat64(druid.HasMinorGlyph(proto.DruidMinorGlyph_GlyphOfTheWild), 0.32, 0.64)
baseCost := core.TernaryFloat64(druid.HasMinorGlyph(proto.DruidMinorGlyph_GlyphOfTheWild), 0.32, 0.64)

druid.GiftOfTheWild = druid.RegisterSpell(core.SpellConfig{
ActionID: core.ActionID{SpellID: 48470},
Flags: SpellFlagOmenTrigger | core.SpellFlagHelpful,

ResourceType: stats.Mana,
BaseCost: baseCost,
Flags: SpellFlagOmenTrigger | core.SpellFlagHelpful,

ManaCost: core.ManaCostOptions{
BaseCost: baseCost,
Multiplier: 1,
},
Cast: core.CastConfig{
DefaultCast: core.Cast{
GCD: core.GCDDefault,
Cost: baseCost,
GCD: core.GCDDefault,
},
},
})
Expand Down
12 changes: 5 additions & 7 deletions sim/druid/force_of_nature.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,16 @@ func (druid *Druid) registerForceOfNatureCD() {
ActionID: core.ActionID{SpellID: 65861},
Duration: time.Second * 30,
})
baseCost := druid.BaseMana * 0.12
druid.ForceOfNature = druid.RegisterSpell(core.SpellConfig{
ActionID: core.ActionID{SpellID: 65861},

ResourceType: stats.Mana,
BaseCost: baseCost,

ManaCost: core.ManaCostOptions{
BaseCost: 0.12,
Multiplier: 1,
},
Cast: core.CastConfig{
DefaultCast: core.Cast{
GCD: core.GCDDefault,
Cost: baseCost,
GCD: core.GCDDefault,
},
CD: core.Cooldown{
Timer: druid.NewTimer(),
Expand All @@ -36,7 +35,6 @@ func (druid *Druid) registerForceOfNatureCD() {
},

ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) {

druid.Treant1.EnableWithTimeout(sim, druid.Treant1, time.Second*30)
druid.Treant2.EnableWithTimeout(sim, druid.Treant2, time.Second*30)
druid.Treant3.EnableWithTimeout(sim, druid.Treant3, time.Second*30)
Expand Down
Loading

0 comments on commit 81ed3b1

Please sign in to comment.