Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor spell costs #2362

Merged
merged 13 commits into from
Jan 12, 2023
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