From 32d6cd7fa4919d9e03e535d46d52b197c232f948 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 26 Nov 2024 13:05:57 -0700 Subject: [PATCH] First pass at mage --- sim/mage/arcane_barrage.go | 56 ----- sim/mage/arcane_blast.go | 106 --------- sim/mage/arcane_missiles.go | 13 +- sim/mage/arcane_surge.go | 86 -------- sim/mage/armors.go | 29 --- sim/mage/balefire_bolt.go | 96 -------- sim/mage/counterspell.go | 1 + sim/mage/deep_freeze.go | 56 ----- sim/mage/fire_blast.go | 15 +- sim/mage/fireball.go | 3 +- sim/mage/frostfire_bolt.go | 78 ------- sim/mage/frozen_orb.go | 159 -------------- sim/mage/ice_barrier.go | 4 +- sim/mage/ice_lance.go | 57 ----- sim/mage/icy_veins.go | 60 ----- sim/mage/ignite.go | 5 +- sim/mage/living_bomb.go | 93 -------- sim/mage/living_flame.go | 92 -------- sim/mage/mage.go | 70 ------ sim/mage/pyroblast.go | 6 - sim/mage/runes.go | 422 ------------------------------------ sim/mage/spellfrost_bolt.go | 54 ----- sim/mage/talents.go | 93 +++----- 23 files changed, 39 insertions(+), 1615 deletions(-) delete mode 100644 sim/mage/arcane_barrage.go delete mode 100644 sim/mage/arcane_blast.go delete mode 100644 sim/mage/arcane_surge.go delete mode 100644 sim/mage/balefire_bolt.go delete mode 100644 sim/mage/deep_freeze.go delete mode 100644 sim/mage/frostfire_bolt.go delete mode 100644 sim/mage/frozen_orb.go delete mode 100644 sim/mage/ice_lance.go delete mode 100644 sim/mage/icy_veins.go delete mode 100644 sim/mage/living_bomb.go delete mode 100644 sim/mage/living_flame.go delete mode 100644 sim/mage/runes.go delete mode 100644 sim/mage/spellfrost_bolt.go diff --git a/sim/mage/arcane_barrage.go b/sim/mage/arcane_barrage.go deleted file mode 100644 index f2dafab52..000000000 --- a/sim/mage/arcane_barrage.go +++ /dev/null @@ -1,56 +0,0 @@ -package mage - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -func (mage *Mage) registerArcaneBarrageSpell() { - if !mage.HasRune(proto.MageRune_RuneCloakArcaneBarrage) { - return - } - - baseDamageLow := mage.baseRuneAbilityDamage() * 3.58 - baseDamageHigh := mage.baseRuneAbilityDamage() * 4.38 - damageCoef := 0.429 - manaCost := 0.08 - cooldown := time.Second * 3 - - mage.ArcaneBarrage = mage.RegisterSpell(core.SpellConfig{ - SpellCode: SpellCode_MageArcaneBarrage, - ActionID: core.ActionID{SpellID: int32(proto.MageRune_RuneCloakArcaneBarrage)}, - SpellSchool: core.SpellSchoolArcane, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskSpellDamage, - Flags: SpellFlagMage | core.SpellFlagAPL, - MissileSpeed: 24, - - ManaCost: core.ManaCostOptions{ - BaseCost: manaCost, - }, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - CD: core.Cooldown{ - Timer: mage.NewTimer(), - Duration: cooldown, - }, - }, - - BonusCoefficient: damageCoef, - DamageMultiplier: 1, - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcDamage(sim, target, sim.Roll(baseDamageLow, baseDamageHigh), spell.OutcomeMagicHitAndCrit) - - spell.WaitTravelTime(sim, func(sim *core.Simulation) { - spell.DealDamage(sim, result) - }) - }, - }) -} diff --git a/sim/mage/arcane_blast.go b/sim/mage/arcane_blast.go deleted file mode 100644 index bb19f61a1..000000000 --- a/sim/mage/arcane_blast.go +++ /dev/null @@ -1,106 +0,0 @@ -package mage - -import ( - "slices" - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -// TODO: Classic verify Arcane Blast rune numbers -// https://www.wowhead.com/classic/news/patch-1-15-build-52124-ptr-datamining-season-of-discovery-runes-336044#news-post-336044 -// https://www.wowhead.com/classic/spell=400574/arcane-blast -func (mage *Mage) registerArcaneBlastSpell() { - if !mage.HasRune(proto.MageRune_RuneHandsArcaneBlast) { - return - } - - hasLivingFlameRune := mage.HasRune(proto.MageRune_RuneLegsLivingFlame) - - baseLowDamage := mage.baseRuneAbilityDamage() * 4.53 - baseHighDamage := mage.baseRuneAbilityDamage() * 5.27 - spellCoeff := .714 - castTime := time.Millisecond * 2500 - manaCost := .07 - - mage.ArcaneBlastDamageMultiplier = 0.15 - - additiveDamageAffectedSpells := []*core.Spell{} - // Purposefully excluded living flame and arcane missiles ticks because we manually disable the arcane blast aura after the final tick - affectedSpellCodes := []int32{ - SpellCode_MageArcaneBarrage, SpellCode_MageArcaneExplosion, SpellCode_MageArcaneSurge, SpellCode_MageBalefireBolt, SpellCode_MageSpellfrostBolt, - } - - mage.ArcaneBlastAura = mage.GetOrRegisterAura(core.Aura{ - Label: "Arcane Blast Aura", - ActionID: core.ActionID{SpellID: 400573}, - Duration: time.Second * 6, - MaxStacks: 4, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - additiveDamageAffectedSpells = core.FilterSlice( - core.Flatten([][]*core.Spell{ - mage.ArcaneExplosion, - mage.ArcaneMissilesTickSpell, - {mage.ArcaneSurge}, - {mage.SpellfrostBolt}, - {mage.BalefireBolt}, - }), - func(spell *core.Spell) bool { return spell != nil }, - ) - }, - OnStacksChange: func(aura *core.Aura, sim *core.Simulation, oldStacks int32, newStacks int32) { - mage.ArcaneBlast.Cost.Multiplier -= 175 * oldStacks - mage.ArcaneBlast.Cost.Multiplier += 175 * newStacks - - oldMultiplier := mage.ArcaneBlastDamageMultiplier * float64(oldStacks) - newMultiplier := mage.ArcaneBlastDamageMultiplier * float64(newStacks) - core.Each(additiveDamageAffectedSpells, func(spell *core.Spell) { - spell.DamageMultiplierAdditive -= oldMultiplier - spell.DamageMultiplierAdditive += newMultiplier - }) - - if hasLivingFlameRune { - // Living Flame is the only spell buffed multiplicatively for whatever reason - mage.LivingFlame.DamageMultiplier /= 1 + oldMultiplier - mage.LivingFlame.DamageMultiplier *= 1 + newMultiplier - } - }, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - if spell.Flags.Matches(SpellFlagMage) && slices.Contains(affectedSpellCodes, spell.SpellCode) { - aura.Deactivate(sim) - } - }, - }) - - mage.ArcaneBlast = mage.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 400574}, - SpellCode: SpellCode_MageArcaneBlast, - SpellSchool: core.SpellSchoolArcane, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskSpellDamage, - Flags: SpellFlagMage | core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: manaCost, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - CastTime: castTime, - }, - }, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: spellCoeff, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := sim.Roll(baseLowDamage, baseHighDamage) - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - - mage.ArcaneBlastAura.Activate(sim) - mage.ArcaneBlastAura.AddStack(sim) - }, - }) -} diff --git a/sim/mage/arcane_missiles.go b/sim/mage/arcane_missiles.go index c3c9c1072..9e97f5f50 100644 --- a/sim/mage/arcane_missiles.go +++ b/sim/mage/arcane_missiles.go @@ -5,7 +5,6 @@ import ( "time" "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" ) const ArcaneMissilesRanks = 8 @@ -41,9 +40,6 @@ func (mage *Mage) getArcaneMissilesSpellConfig(rank int) core.SpellConfig { numTicks := castTime tickLength := time.Second - hasArcaneBlastRune := mage.HasRune(proto.MageRune_RuneHandsArcaneBlast) - hasMissileBarrageRune := mage.HasRune(proto.MageRune_RuneBeltMissileBarrage) - tickSpell := mage.getArcaneMissilesTickSpell(rank) mage.ArcaneMissilesTickSpell[rank] = tickSpell @@ -74,14 +70,13 @@ func (mage *Mage) getArcaneMissilesSpellConfig(rank int) core.SpellConfig { // TODO: This check is necessary to ensure the final tick occurs before // Arcane Blast stacks are dropped. To fix this, ticks need to reliably // occur before aura expirations. + + //TODO: Test interaction in classic code without aura dot := mage.ArcaneMissiles[rank].Dot(aura.Unit) if dot.TickCount < dot.NumberOfTicks { dot.TickCount++ dot.TickOnce(sim) } - if hasArcaneBlastRune && mage.ArcaneBlastAura.IsActive() { - mage.ArcaneBlastAura.Deactivate(sim) - } }, }, NumberOfTicks: numTicks, @@ -93,10 +88,6 @@ func (mage *Mage) getArcaneMissilesSpellConfig(rank int) core.SpellConfig { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { spell.Dot(target).Apply(sim) - - if hasMissileBarrageRune && mage.MissileBarrageAura.IsActive() { - mage.MissileBarrageAura.Deactivate(sim) - } }, ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, _ bool) *core.SpellResult { return tickSpell.CalcDamage(sim, target, baseTickDamage, spell.OutcomeExpectedMagicHitAndCrit) diff --git a/sim/mage/arcane_surge.go b/sim/mage/arcane_surge.go deleted file mode 100644 index 93692cc90..000000000 --- a/sim/mage/arcane_surge.go +++ /dev/null @@ -1,86 +0,0 @@ -package mage - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -func (mage *Mage) registerArcaneSurgeSpell() { - if !mage.HasRune(proto.MageRune_RuneLegsArcaneSurge) { - return - } - - actionID := core.ActionID{SpellID: int32(proto.MageRune_RuneLegsArcaneSurge)} - baseDamageLow := mage.baseRuneAbilityDamage() * 2.26 - baseDamageHigh := mage.baseRuneAbilityDamage() * 2.64 - spellCoeff := .429 - cooldown := time.Minute * 2 - auraDuration := time.Second * 8 - - manaMetrics := mage.NewManaMetrics(actionID) - - manaAura := mage.GetOrRegisterAura(core.Aura{ - Label: "Arcane Surge", - ActionID: actionID, - Duration: auraDuration, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.PseudoStats.SpiritRegenMultiplier *= 3 - mage.PseudoStats.ForceFullSpiritRegen = true - mage.UpdateManaRegenRates() - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.PseudoStats.SpiritRegenMultiplier /= 3 - mage.PseudoStats.ForceFullSpiritRegen = false - mage.UpdateManaRegenRates() - }, - }) - - mage.ArcaneSurge = mage.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - SpellCode: SpellCode_MageArcaneSurge, - SpellSchool: core.SpellSchoolArcane, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskSpellDamage, - Flags: SpellFlagMage | core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - FlatCost: 0.0, // Drains remaining mana so we have to use ModifyCast - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - CD: core.Cooldown{ - Timer: mage.NewTimer(), - Duration: cooldown, - }, - }, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: spellCoeff, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - damage := sim.Roll(baseDamageLow, baseDamageHigh) - // Damage increased based on remaining mana up to 300% - oldMultiplier := spell.DamageMultiplier - spell.DamageMultiplier *= 1 + mage.CurrentManaPercent()*3 - spell.CalcAndDealDamage(sim, target, damage, spell.OutcomeMagicHitAndCrit) - spell.DamageMultiplier = oldMultiplier - // Because of the 0 base mana cost we have to create resource metrics - mage.SpendMana(sim, mage.CurrentMana(), manaMetrics) - manaAura.Activate(sim) - }, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return mage.CurrentMana() > 0 - }, - }) - - mage.AddMajorCooldown(core.MajorCooldown{ - Spell: mage.ArcaneSurge, - Type: core.CooldownTypeDPS, - }) -} diff --git a/sim/mage/armors.go b/sim/mage/armors.go index 22e3d46d2..d3049fa18 100644 --- a/sim/mage/armors.go +++ b/sim/mage/armors.go @@ -2,7 +2,6 @@ package mage import ( "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" "github.com/wowsims/classic/sim/core/stats" ) @@ -94,31 +93,3 @@ func (mage *Mage) applyMageArmor() { }, })) } - -func (mage *Mage) applyMoltenArmor() { - if !mage.HasRune(proto.MageRune_RuneBracersMoltenArmor) { - return - } - - crit := 5.0 * core.SpellCritRatingPerCritChance - - mage.MoltenArmorAura = core.MakePermanent(mage.RegisterAura(core.Aura{ - Label: "Molten Armor", - ActionID: core.ActionID{SpellID: int32(proto.MageRune_RuneBracersMoltenArmor)}, - BuildPhase: core.CharacterBuildPhaseBuffs, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - if aura.Unit.Env.MeasuringStats && aura.Unit.Env.State != core.Finalized { - mage.AddStat(stats.SpellCrit, crit) - } else { - mage.AddStatDynamic(sim, stats.SpellCrit, crit) - } - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - if aura.Unit.Env.MeasuringStats && aura.Unit.Env.State != core.Finalized { - mage.AddStat(stats.SpellCrit, -crit) - } else { - mage.AddStatDynamic(sim, stats.SpellCrit, -crit) - } - }, - })) -} diff --git a/sim/mage/balefire_bolt.go b/sim/mage/balefire_bolt.go deleted file mode 100644 index 03f5b2a9c..000000000 --- a/sim/mage/balefire_bolt.go +++ /dev/null @@ -1,96 +0,0 @@ -package mage - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" - "github.com/wowsims/classic/sim/core/stats" -) - -// https://www.wowhead.com/classic/spell=428878/balefire-bolt -func (mage *Mage) registerBalefireBoltSpell() { - if !mage.HasRune(proto.MageRune_RuneBracersBalefireBolt) { - return - } - - baseDamageLow := mage.baseRuneAbilityDamage() * 2.8 - baseDamageHigh := mage.baseRuneAbilityDamage() * 4.2 - spellCoeff := .857 - castTime := time.Millisecond * 2500 - buffDuration := time.Second * 30 - manaCost := .20 - maxStacks := 5 - stackMultiplier := 0.20 - - statDeps := make([]*stats.StatDependency, maxStacks+1) // 5 stacks + zero conditions - for i := 1; i < maxStacks+1; i++ { - statDeps[i] = mage.NewDynamicMultiplyStat(stats.Spirit, 1.0-stackMultiplier*float64(i)) - } - - balefireAura := mage.RegisterAura(core.Aura{ - Label: "Balefire Bolt (Stacks)", - ActionID: core.ActionID{SpellID: int32(proto.MageRune_RuneBracersBalefireBolt)}.WithTag(1), - Duration: buffDuration, - MaxStacks: int32(maxStacks), - OnStacksChange: func(aura *core.Aura, sim *core.Simulation, oldStacks int32, newStacks int32) { - mage.BalefireBolt.DamageMultiplierAdditive -= stackMultiplier * float64(oldStacks) - mage.BalefireBolt.DamageMultiplierAdditive += stackMultiplier * float64(newStacks) - - if oldStacks != 0 { - aura.Unit.DisableDynamicStatDep(sim, statDeps[oldStacks]) - } - if newStacks != 0 { - aura.Unit.EnableDynamicStatDep(sim, statDeps[newStacks]) - } - - if newStacks == aura.MaxStacks { - mage.RemoveHealth(sim, mage.CurrentHealth()) - - if sim.Log != nil { - mage.Log(sim, "YOU DIED") - } - - sim.Cleanup() - } - }, - }) - - mage.BalefireBolt = mage.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: int32(proto.MageRune_RuneBracersBalefireBolt)}, - SpellCode: SpellCode_MageBalefireBolt, - SpellSchool: core.SpellSchoolArcane | core.SpellSchoolFire | core.SpellSchoolFrost, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskSpellDamage, - Flags: SpellFlagMage | core.SpellFlagAPL, - // TODO: Verify missile speed - MissileSpeed: 28, - - ManaCost: core.ManaCostOptions{ - BaseCost: manaCost, - }, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - CastTime: castTime, - GCD: core.GCDDefault, - }, - }, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: spellCoeff, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := sim.Roll(baseDamageLow, baseDamageHigh) - result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - - balefireAura.Activate(sim) - balefireAura.AddStack(sim) - - spell.WaitTravelTime(sim, func(sim *core.Simulation) { - spell.DealDamage(sim, result) - }) - }, - }) -} diff --git a/sim/mage/counterspell.go b/sim/mage/counterspell.go index 0c59adb32..1d1ec34b1 100644 --- a/sim/mage/counterspell.go +++ b/sim/mage/counterspell.go @@ -7,6 +7,7 @@ import ( ) // This exists purely so that it can be used to extend the arcane buff from the mage T1 4pc +// Not relevant in classic currently but will keep func (mage *Mage) registerCounterspellSpell() { mage.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 2139}, diff --git a/sim/mage/deep_freeze.go b/sim/mage/deep_freeze.go deleted file mode 100644 index 1261e2559..000000000 --- a/sim/mage/deep_freeze.go +++ /dev/null @@ -1,56 +0,0 @@ -package mage - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -func (mage *Mage) registerDeepFreezeSpell() { - if !mage.HasRune(proto.MageRune_RuneHelmDeepFreeze) { - return - } - - baseDamageLow := mage.baseRuneAbilityDamage() * 4.62 - baseDamageHigh := mage.baseRuneAbilityDamage() * 5.38 - spellCoeff := 2.5 - cooldown := time.Second * 30 - manaCost := .12 - - hasFingersOfFrostRune := mage.HasRune(proto.MageRune_RuneChestFingersOfFrost) - - mage.DeepFreeze = mage.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: int32(proto.MageRune_RuneHelmDeepFreeze)}, - SpellSchool: core.SpellSchoolFrost, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskSpellDamage, - Flags: SpellFlagMage | core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: manaCost, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - CD: core.Cooldown{ - Timer: mage.NewTimer(), - Duration: cooldown, - }, - }, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: spellCoeff, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := sim.Roll(baseDamageLow, baseDamageHigh) - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - }, - - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return hasFingersOfFrostRune && mage.FingersOfFrostAura.IsActive() - }, - }) -} diff --git a/sim/mage/fire_blast.go b/sim/mage/fire_blast.go index 9ffa6bbcc..302f61775 100644 --- a/sim/mage/fire_blast.go +++ b/sim/mage/fire_blast.go @@ -4,7 +4,6 @@ import ( "time" "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" ) const FireBlastRanks = 7 @@ -29,7 +28,6 @@ func (mage *Mage) registerFireBlastSpell() { } func (mage *Mage) newFireBlastSpellConfig(rank int, cdTimer *core.Timer) core.SpellConfig { - hasOverheatRune := mage.HasRune(proto.MageRune_RuneCloakOverheat) spellId := FireBlastSpellId[rank] baseDamageLow := FireBlastBaseDamage[rank][0] @@ -40,11 +38,6 @@ func (mage *Mage) newFireBlastSpellConfig(rank int, cdTimer *core.Timer) core.Sp cooldown := time.Second * 8 flags := SpellFlagMage | core.SpellFlagAPL - if hasOverheatRune { - cooldown = time.Second * 15 - flags |= core.SpellFlagCastTimeNoGCD | core.SpellFlagCastWhileCasting - } - gcd := core.TernaryDuration(hasOverheatRune, 0, core.GCDDefault) return core.SpellConfig{ ActionID: core.ActionID{SpellID: spellId}, @@ -62,7 +55,7 @@ func (mage *Mage) newFireBlastSpellConfig(rank int, cdTimer *core.Timer) core.Sp }, Cast: core.CastConfig{ DefaultCast: core.Cast{ - GCD: gcd, + GCD: core.GCDDefault, }, CD: core.Cooldown{ Timer: cdTimer, @@ -70,15 +63,15 @@ func (mage *Mage) newFireBlastSpellConfig(rank int, cdTimer *core.Timer) core.Sp }, }, - BonusCritRating: core.TernaryFloat64(hasOverheatRune, 100, 2*float64(mage.Talents.Incinerate)) * core.SpellCritRatingPerCritChance, + BonusCritRating: 2 * float64(mage.Talents.Incinerate) * core.SpellCritRatingPerCritChance, DamageMultiplier: 1, ThreatMultiplier: 1, BonusCoefficient: spellCoeff, ExpectedInitialDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, _ bool) *core.SpellResult { - baseDamageCacl := (baseDamageLow + baseDamageHigh) / 2 - return spell.CalcDamage(sim, target, baseDamageCacl, spell.OutcomeExpectedMagicHitAndCrit) + baseDamageCalc := (baseDamageLow + baseDamageHigh) / 2 + return spell.CalcDamage(sim, target, baseDamageCalc, spell.OutcomeExpectedMagicHitAndCrit) }, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := sim.Roll(baseDamageLow, baseDamageHigh) diff --git a/sim/mage/fireball.go b/sim/mage/fireball.go index 506950d9b..42ef8da50 100644 --- a/sim/mage/fireball.go +++ b/sim/mage/fireball.go @@ -75,7 +75,7 @@ func (mage *Mage) newFireballSpellConfig(rank int) core.SpellConfig { NumberOfTicks: numTicks, TickLength: tickLength, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, baseDotDamage+mage.BonusFireballDoTAmount, isRollover) + dot.Snapshot(target, baseDotDamage, isRollover) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) @@ -87,7 +87,6 @@ func (mage *Mage) newFireballSpellConfig(rank int) core.SpellConfig { BonusCoefficient: spellCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - mage.BonusFireballDoTAmount = 0 baseDamage := sim.Roll(baseDamageLow, baseDamageHigh) result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) spell.WaitTravelTime(sim, func(sim *core.Simulation) { diff --git a/sim/mage/frostfire_bolt.go b/sim/mage/frostfire_bolt.go deleted file mode 100644 index 7d9d83992..000000000 --- a/sim/mage/frostfire_bolt.go +++ /dev/null @@ -1,78 +0,0 @@ -package mage - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -func (mage *Mage) registerFrostfireBoltSpell() { - if !mage.HasRune(proto.MageRune_RuneBeltFrostfireBolt) { - return - } - - actionID := core.ActionID{SpellID: int32(proto.MageRune_RuneBeltFrostfireBolt)} - baseDamageLow := mage.baseRuneAbilityDamage() * 3.25 - baseDamageHigh := mage.baseRuneAbilityDamage() * 3.79 - baseDotDamage := mage.baseRuneAbilityDamage() * 0.08 - spellCoeff := 1.0 - castTime := time.Second * 3 - manaCost := .14 - - numTicks := int32(3) + mage.Talents.Permafrost/3 - tickLength := time.Second * 3 - - mage.FrostfireBolt = mage.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - SpellCode: SpellCode_MageFrostfireBolt, - SpellSchool: core.SpellSchoolFrost | core.SpellSchoolFire, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskSpellDamage, - Flags: SpellFlagMage | SpellFlagChillSpell | core.SpellFlagBinary | core.SpellFlagAPL, - MissileSpeed: 28, - - ManaCost: core.ManaCostOptions{ - BaseCost: manaCost, - }, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - CastTime: castTime, - GCD: core.GCDDefault, - }, - }, - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Frostfire Bolt", - ActionID: actionID.WithTag(1), - }, - NumberOfTicks: numTicks, - TickLength: tickLength, - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, baseDotDamage, isRollover) - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) - }, - }, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: spellCoeff, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := sim.Roll(baseDamageLow, baseDamageHigh) - result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - - spell.WaitTravelTime(sim, func(sim *core.Simulation) { - spell.DealDamage(sim, result) - - if result.Landed() { - spell.Dot(target).Apply(sim) - } - }) - }, - }) -} diff --git a/sim/mage/frozen_orb.go b/sim/mage/frozen_orb.go deleted file mode 100644 index 6a1e10b70..000000000 --- a/sim/mage/frozen_orb.go +++ /dev/null @@ -1,159 +0,0 @@ -package mage - -import ( - "fmt" - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" - "github.com/wowsims/classic/sim/core/stats" -) - -func (mage *Mage) registerFrozenOrbCD() { - if !mage.HasRune(proto.MageRune_RuneCloakFrozenOrb) { - return - } - - cooldown := time.Minute - manaCost := 0.11 - - mage.FrozenOrb = mage.RegisterSpell(core.SpellConfig{ - SpellCode: SpellCode_MageFrozenOrb, - ActionID: core.ActionID{SpellID: int32(proto.MageRune_RuneCloakFrozenOrb)}, - SpellSchool: core.SpellSchoolFrost, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: manaCost, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - CD: core.Cooldown{ - Timer: mage.NewTimer(), - Duration: cooldown, - }, - }, - - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { - for _, orb := range mage.frozenOrbPets { - if !orb.IsActive() { - orb.EnableWithTimeout(sim, orb, time.Second*15) - break - } - } - }, - }) - - mage.AddMajorCooldown(core.MajorCooldown{ - Spell: mage.FrozenOrb, - Type: core.CooldownTypeDPS, - }) -} - -type FrozenOrb struct { - core.Pet - - mage *Mage - - FrozenOrbTick *core.Spell - FrozenOrbFingerOfFrost *core.Aura - TickCount int64 -} - -func (mage *Mage) NewFrozenOrbPets() []*FrozenOrb { - // It's possible to have up to 2 Frozen Orbs active at a time because of Cold Snap - return []*FrozenOrb{mage.newFrozenOrb(1), mage.newFrozenOrb(2)} -} - -func (mage *Mage) newFrozenOrb(idx int32) *FrozenOrb { - frozenOrb := &FrozenOrb{ - Pet: core.NewPet(fmt.Sprintf("Frozen Orb %d", idx), &mage.Character, frozenOrbBaseStats, frozenOrbStatInheritance(), false, true), - mage: mage, - TickCount: 0, - } - - mage.AddPet(frozenOrb) - - return frozenOrb -} - -func (orb *FrozenOrb) GetPet() *core.Pet { - return &orb.Pet -} - -func (orb *FrozenOrb) Initialize() { - orb.registerFrozenOrbTickSpell() - - // Frozen Orb seems to benefit from Frost Specialization - orb.PseudoStats.SchoolBonusHitChance = orb.mage.PseudoStats.SchoolBonusHitChance -} - -func (orb *FrozenOrb) Reset(_ *core.Simulation) { -} - -func (orb *FrozenOrb) ExecuteCustomRotation(sim *core.Simulation) { - if success := orb.FrozenOrbTick.Cast(sim, orb.mage.CurrentTarget); !success { - orb.Disable(sim) - } -} - -var frozenOrbBaseStats = stats.Stats{} - -func frozenOrbStatInheritance() core.PetStatInheritance { - return func(ownerStats stats.Stats) stats.Stats { - return stats.Stats{ - stats.SpellHit: ownerStats[stats.SpellHit], - stats.SpellCrit: ownerStats[stats.SpellCrit], - stats.SpellPower: ownerStats[stats.SpellPower], - } - } -} - -func (orb *FrozenOrb) registerFrozenOrbTickSpell() { - hasFOFRune := orb.mage.HasRune(proto.MageRune_RuneChestFingersOfFrost) - baseDamage := orb.mage.baseRuneAbilityDamage() * 0.9 - spellCoef := .129 - - orb.FrozenOrbTick = orb.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 440809}, - SpellSchool: core.SpellSchoolFrost | core.SpellSchoolArcane, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskSpellDamage, - Flags: SpellFlagMage | SpellFlagChillSpell, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: time.Second, - }, - }, - - BonusCoefficient: spellCoef, - DamageMultiplier: 1, - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - for _, aoeTarget := range sim.Encounter.TargetUnits { - spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) - } - orb.TickCount += 1 - if orb.TickCount == 15 { - orb.TickCount = 0 - } - }, - }) - - if hasFOFRune { - orb.FrozenOrbFingerOfFrost = core.MakePermanent(orb.RegisterAura(core.Aura{ - Label: "Frozen Orb FoF", - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell == orb.FrozenOrbTick && orb.TickCount == 0 { - orb.mage.FingersOfFrostAura.Activate(sim) - orb.mage.FingersOfFrostAura.AddStack(sim) - } - }, - })) - } -} diff --git a/sim/mage/ice_barrier.go b/sim/mage/ice_barrier.go index 09f7d2b90..c75a19485 100644 --- a/sim/mage/ice_barrier.go +++ b/sim/mage/ice_barrier.go @@ -40,10 +40,10 @@ func (mage *Mage) newIceBarrierSpellConfig(rank int, cdTimer *core.Timer) core.S Label: fmt.Sprintf("Ice Barrier (Rank %d)", rank), Duration: time.Minute, OnGain: func(aura *core.Aura, sim *core.Simulation) { - // Dummy + // Dummy, needs shield implementation }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { - // Dummy + // Dummy, needs shield implementation }, }) diff --git a/sim/mage/ice_lance.go b/sim/mage/ice_lance.go deleted file mode 100644 index 2ffbaf9fc..000000000 --- a/sim/mage/ice_lance.go +++ /dev/null @@ -1,57 +0,0 @@ -package mage - -import ( - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -// TODO: Classic review ice lance numbers on live -func (mage *Mage) registerIceLanceSpell() { - if !mage.HasRune(proto.MageRune_RuneHandsIceLance) { - return - } - - baseDamageLow := mage.baseRuneAbilityDamage() * .55 - baseDamageHigh := mage.baseRuneAbilityDamage() * .65 - spellCoeff := .143 - manaCost := .08 - - hasFingersOfFrostRune := mage.HasRune(proto.MageRune_RuneChestFingersOfFrost) - - mage.IceLance = mage.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: int32(proto.MageRune_RuneHandsIceLance)}, - SpellSchool: core.SpellSchoolFrost, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskSpellDamage, - Flags: SpellFlagMage | core.SpellFlagAPL, - MissileSpeed: 38, - - ManaCost: core.ManaCostOptions{ - BaseCost: manaCost, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: spellCoeff, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := sim.Roll(baseDamageLow, baseDamageHigh) - - oldMultiplier := spell.DamageMultiplier - if hasFingersOfFrostRune && mage.FingersOfFrostAura.IsActive() { - spell.DamageMultiplier *= 3.0 - } - result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - spell.DamageMultiplier = oldMultiplier - - spell.WaitTravelTime(sim, func(sim *core.Simulation) { - spell.DealDamage(sim, result) - }) - }, - }) -} diff --git a/sim/mage/icy_veins.go b/sim/mage/icy_veins.go deleted file mode 100644 index c8c8bb849..000000000 --- a/sim/mage/icy_veins.go +++ /dev/null @@ -1,60 +0,0 @@ -package mage - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -func (mage *Mage) registerIcyVeinsSpell() { - if !mage.HasRune(proto.MageRune_RuneLegsIceVeins) { - return - } - - actionID := core.ActionID{SpellID: int32(proto.MageRune_RuneLegsIceVeins)} - castSpeedMultiplier := 1.2 - manaCost := .03 - duration := time.Second * 20 - cooldown := time.Minute * 3 - - icyVeinsAura := mage.RegisterAura(core.Aura{ - Label: "Icy Veins", - ActionID: actionID, - Duration: duration, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.MultiplyCastSpeed(castSpeedMultiplier) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.MultiplyCastSpeed(1 / castSpeedMultiplier) - }, - }) - - mage.IcyVeins = mage.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - SpellSchool: core.SpellSchoolFrost, - Flags: core.SpellFlagNoOnCastComplete, - - ManaCost: core.ManaCostOptions{ - BaseCost: manaCost, - }, - Cast: core.CastConfig{ - CD: core.Cooldown{ - Timer: mage.NewTimer(), - Duration: cooldown, - }, - }, - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - // Need to check for icy veins already active in case Cold Snap is used right after. - return !icyVeinsAura.IsActive() - }, - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { - icyVeinsAura.Activate(sim) - }, - }) - - mage.AddMajorCooldown(core.MajorCooldown{ - Spell: mage.IcyVeins, - Type: core.CooldownTypeDPS, - }) -} diff --git a/sim/mage/ignite.go b/sim/mage/ignite.go index 8d2a0d131..f83742f40 100644 --- a/sim/mage/ignite.go +++ b/sim/mage/ignite.go @@ -29,14 +29,11 @@ func (mage *Mage) applyIgnite() { mage.procIgnite(sim, result) } }, - // TODO: Classic verify mechanics match for rune based Living Bomb + // TODO: Classic verify mechanics match for rune based Living Bomb - I believe this can be removed in classic? OnPeriodicDamageDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { if !spell.ProcMask.Matches(core.ProcMaskSpellDamage) { return } - if mage.LivingBomb != nil && result.DidCrit() { - mage.procIgnite(sim, result) - } }, }) diff --git a/sim/mage/living_bomb.go b/sim/mage/living_bomb.go deleted file mode 100644 index 05ca0690e..000000000 --- a/sim/mage/living_bomb.go +++ /dev/null @@ -1,93 +0,0 @@ -package mage - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -// TODO: Classic verify numbers such as aoe caps and base damage -// https://www.wowhead.com/classic/news/patch-1-15-build-52124-ptr-datamining-season-of-discovery-runes-336044#news-post-336044 -// https://www.wowhead.com/classic/spell=400614/living-bomb -// https://www.wowhead.com/classic/spell=400613/living-bomb -func (mage *Mage) registerLivingBombSpell() { - if !mage.HasRune(proto.MageRune_RuneHandsLivingBomb) { - return - } - - actionID := core.ActionID{SpellID: int32(proto.MageRune_RuneHandsLivingBomb)} - baseDotDamage := mage.baseRuneAbilityDamage() * .85 - baseExplosionDamage := mage.baseRuneAbilityDamage() * 1.71 - dotCoeff := .20 - explosionCoeff := .40 - manaCost := .22 - - ticks := int32(4) - tickLength := time.Second * 3 - - livingBombExplosionSpell := mage.RegisterSpell(core.SpellConfig{ - ActionID: actionID.WithTag(1), - SpellCode: SpellCode_MageLivingBombExplosion, - SpellSchool: core.SpellSchoolFire, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskSpellDamage, - Flags: SpellFlagMage | core.SpellFlagPassiveSpell, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: explosionCoeff, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - spell.CalcAndDealDamage(sim, target, baseExplosionDamage, spell.OutcomeMagicCrit) - }, - }) - - mage.LivingBomb = mage.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - SpellSchool: core.SpellSchoolFire, - ProcMask: core.ProcMaskSpellDamage, - Flags: SpellFlagMage | core.SpellFlagAPL | core.SpellFlagPureDot, - - ManaCost: core.ManaCostOptions{ - BaseCost: manaCost, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Living Bomb (DoT)", - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - for _, aoeTarget := range sim.Encounter.TargetUnits { - livingBombExplosionSpell.Cast(sim, aoeTarget) - } - }, - }, - - NumberOfTicks: ticks, - TickLength: tickLength, - BonusCoefficient: dotCoeff, - - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, baseDotDamage, isRollover) - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcAndDealOutcome(sim, target, spell.OutcomeMagicHitNoHitCounter) - if result.Landed() { - spell.Dot(target).Apply(sim) - } - }, - }) -} diff --git a/sim/mage/living_flame.go b/sim/mage/living_flame.go deleted file mode 100644 index 4b9d1ba19..000000000 --- a/sim/mage/living_flame.go +++ /dev/null @@ -1,92 +0,0 @@ -package mage - -import ( - "math" - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -// TODO: Classic verify numbers / snapshot / travel time -// https://www.wowhead.com/classic/news/patch-1-15-build-52124-ptr-datamining-season-of-discovery-runes-336044#news-post-336044 -// https://www.wowhead.com/classic/spell=401558/living-flame -func (mage *Mage) registerLivingFlameSpell() { - if !mage.HasRune(proto.MageRune_RuneLegsLivingFlame) { - return - } - - baseDamage := mage.baseRuneAbilityDamage() * 1 - spellCoeff := .143 - manaCost := .11 - cooldown := time.Second * 30 - - ticks := int32(10) - tickLength := time.Second * 1 - - hasArcaneBlastRune := mage.HasRune(proto.MageRune_RuneHandsArcaneBlast) - - mage.LivingFlame = mage.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: int32(proto.MageRune_RuneLegsLivingFlame)}, - SpellCode: SpellCode_MageLivingFlame, - SpellSchool: core.SpellSchoolArcane | core.SpellSchoolFire, - ProcMask: core.ProcMaskSpellDamage, - Flags: SpellFlagMage | core.SpellFlagAPL | core.SpellFlagPureDot, - MissileSpeed: 6.02, - - ManaCost: core.ManaCostOptions{ - BaseCost: manaCost, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - CD: core.Cooldown{ - Timer: mage.NewTimer(), - Duration: cooldown, - }, - }, - - // Not affected by hit - DamageMultiplier: 1, - ThreatMultiplier: 1, - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Living Flame", - }, - - NumberOfTicks: ticks, - TickLength: tickLength, - BonusCoefficient: spellCoeff, - - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.Snapshot(target, baseDamage, isRollover) - - // We have to deactivate AB here because otherwise the stacks are removed before the snapshot is calculated - if hasArcaneBlastRune && mage.ArcaneBlastAura.IsActive() { - mage.ArcaneBlastAura.Deactivate(sim) - } - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - for _, aoeTarget := range sim.Encounter.TargetUnits { - dot.CalcAndDealPeriodicSnapshotDamage(sim, aoeTarget, dot.OutcomeTick) - } - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - dot := spell.Dot(target) - // Ticks lost to travel time - dot.NumberOfTicks = ticks - int32(math.Floor(spell.TravelTime().Seconds())) - spell.WaitTravelTime(sim, func(sim *core.Simulation) { - dot.Apply(sim) - }) - }, - }) - - mage.AddMajorCooldown(core.MajorCooldown{ - Spell: mage.LivingFlame, - Type: core.CooldownTypeDPS, - }) -} diff --git a/sim/mage/mage.go b/sim/mage/mage.go index 9d75ffe68..312c092da 100644 --- a/sim/mage/mage.go +++ b/sim/mage/mage.go @@ -14,25 +14,15 @@ const ( const ( SpellCode_MageNone int32 = iota - SpellCode_MageArcaneBarrage - SpellCode_MageArcaneBlast SpellCode_MageArcaneExplosion SpellCode_MageArcaneMissiles SpellCode_MageArcaneMissilesTick - SpellCode_MageArcaneSurge - SpellCode_MageBalefireBolt SpellCode_MageBlastWave SpellCode_MageFireball SpellCode_MageFireBlast SpellCode_MageFrostbolt - SpellCode_MageFrostfireBolt - SpellCode_MageFrozenOrb SpellCode_MageIgnite - SpellCode_MageLivingBomb - SpellCode_MageLivingBombExplosion - SpellCode_MageLivingFlame SpellCode_MageScorch - SpellCode_MageSpellfrostBolt ) var TalentTreeSizes = [3]int{16, 16, 17} @@ -61,59 +51,30 @@ type Mage struct { Options *proto.Mage_Options activeBarrier *core.Aura - frozenOrbPets []*FrozenOrb - ArcaneBarrage *core.Spell - ArcaneBlast *core.Spell ArcaneExplosion []*core.Spell ArcaneMissiles []*core.Spell ArcaneMissilesTickSpell []*core.Spell - ArcaneSurge *core.Spell - BalefireBolt *core.Spell BlastWave []*core.Spell Blizzard []*core.Spell - DeepFreeze *core.Spell Fireball []*core.Spell FireBlast []*core.Spell Flamestrike []*core.Spell Frostbolt []*core.Spell - FrostfireBolt *core.Spell - FrozenOrb *core.Spell IceBarrier []*core.Spell - IceLance *core.Spell Ignite *core.Spell - LivingBomb *core.Spell - LivingFlame *core.Spell ManaGem []*core.Spell PresenceOfMind *core.Spell Pyroblast []*core.Spell Scorch []*core.Spell - SpellfrostBolt *core.Spell - IcyVeins *core.Spell - - ArcaneBlastAura *core.Aura - ArcanePotencyAura *core.Aura ArcanePowerAura *core.Aura ClearcastingAura *core.Aura CombustionAura *core.Aura - FingersOfFrostAura *core.Aura - HotStreakAura *core.Aura IceArmorAura *core.Aura IceBarrierAuras []*core.Aura ImprovedScorchAuras core.AuraArray MageArmorAura *core.Aura - MissileBarrageAura *core.Aura - MoltenArmorAura *core.Aura - - ArcaneBlastMissileBarrageChance float64 - BonusFireballDoTAmount float64 - FingersOfFrostProcChance float64 - - // Variables for telling the mage to try to maintain the Fireball DoT with T2 Fire 6pc - ArcaneBlastDamageMultiplier float64 - FireballMissileActive bool // Whether Fireball has been cast but has not hit to avoid chain-casting - MaintainFireballDoT bool } // Agent is a generic way to access underlying mage on any of the agents. @@ -154,10 +115,6 @@ func (mage *Mage) Initialize() { } func (mage *Mage) Reset(sim *core.Simulation) { - mage.BonusFireballDoTAmount = 0 - for _, orb := range mage.frozenOrbPets { - orb.TickCount = 0 - } } func NewMage(character *core.Character, options *proto.Player) *Mage { @@ -175,26 +132,11 @@ func NewMage(character *core.Character, options *proto.Player) *Mage { mage.AddStatDependency(stats.Strength, stats.AttackPower, core.APPerStrength[character.Class]) mage.AddStatDependency(stats.Intellect, stats.SpellCrit, core.CritPerIntAtLevel[mage.Class][int(mage.Level)]*core.SpellCritRatingPerCritChance) - switch mage.Consumes.MageScroll { - case proto.MageScroll_MageScrollArcaneRecovery: - mage.AddStat(stats.MP5, 8) - case proto.MageScroll_MageScrollArcaneAccuracy: - mage.AddStat(stats.SpellHit, core.SpellHitRatingPerHitChance) - case proto.MageScroll_MageScrollArcanePower: - mage.AddStat(stats.SpellCrit, core.SpellCritRatingPerCritChance) - case proto.MageScroll_MageScrollFireProtection: - mage.AddStat(stats.FireResistance, 20) - case proto.MageScroll_MageScrollFrostProtection: - mage.AddStat(stats.FrostResistance, 20) - } - switch mage.Options.Armor { case proto.Mage_Options_IceArmor: mage.applyFrostIceArmor() case proto.Mage_Options_MageArmor: mage.applyMageArmor() - case proto.Mage_Options_MoltenArmor: - mage.applyMoltenArmor() } // Set mana regen to 12.5 + Spirit/4 each 2s tick @@ -202,19 +144,7 @@ func NewMage(character *core.Character, options *proto.Player) *Mage { return 6.25 + mage.GetStat(stats.Spirit)/8 } - if mage.HasRune(proto.MageRune_RuneCloakFrozenOrb) { - mage.frozenOrbPets = mage.NewFrozenOrbPets() - } - guardians.ConstructGuardians(&mage.Character) return mage } - -func (mage *Mage) HasRune(rune proto.MageRune) bool { - return false // mage.HasRuneById(int32(rune)) -} - -func (mage *Mage) baseRuneAbilityDamage() float64 { - return 13.828124 + 0.018012*float64(mage.Level) + 0.044141*float64(mage.Level*mage.Level) -} diff --git a/sim/mage/pyroblast.go b/sim/mage/pyroblast.go index 54a710bb2..22c5c49bf 100644 --- a/sim/mage/pyroblast.go +++ b/sim/mage/pyroblast.go @@ -5,7 +5,6 @@ import ( "time" "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" ) const PyroblastRanks = 8 @@ -33,7 +32,6 @@ func (mage *Mage) registerPyroblastSpell() { } func (mage *Mage) newPyroblastSpellConfig(rank int) core.SpellConfig { - hasHotStreakRune := mage.HasRune(proto.MageRune_RuneHelmHotStreak) numTicks := int32(4) tickLength := time.Second * 3 @@ -96,10 +94,6 @@ func (mage *Mage) newPyroblastSpellConfig(rank int) core.SpellConfig { baseDamage := sim.Roll(baseDamageLow, baseDamageHigh) result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - if hasHotStreakRune && mage.HotStreakAura.IsActive() { - mage.HotStreakAura.Deactivate(sim) - } - spell.WaitTravelTime(sim, func(sim *core.Simulation) { spell.DealDamage(sim, result) diff --git a/sim/mage/runes.go b/sim/mage/runes.go deleted file mode 100644 index 9d5a713a0..000000000 --- a/sim/mage/runes.go +++ /dev/null @@ -1,422 +0,0 @@ -package mage - -import ( - "slices" - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" - "github.com/wowsims/classic/sim/core/stats" -) - -func (mage *Mage) ApplyRunes() { - // Helm - mage.registerDeepFreezeSpell() - - // CLoak - mage.registerArcaneBarrageSpell() - // mage.applyOverheat() - mage.registerFrozenOrbCD() - - // Chest - mage.applyBurnout() - mage.applyEnlightenment() - mage.applyFingersOfFrost() - - // Bracers - mage.registerBalefireBoltSpell() - - // Hands - mage.registerArcaneBlastSpell() - mage.registerIceLanceSpell() - mage.registerLivingBombSpell() - - // Waist - mage.registerFrostfireBoltSpell() - mage.applyHotStreak() - mage.applyMissileBarrage() - mage.registerSpellfrostBoltSpell() - - // Legs - mage.registerArcaneSurgeSpell() - mage.registerIcyVeinsSpell() - mage.registerLivingFlameSpell() - - // Feet - mage.applyBrainFreeze() - mage.applySpellPower() -} - -func (mage *Mage) applyBurnout() { - if !mage.HasRune(proto.MageRune_RuneChestBurnout) { - return - } - - actionID := core.ActionID{SpellID: int32(proto.MageRune_RuneChestBurnout)} - metric := mage.NewManaMetrics(actionID) - - mage.AddStat(stats.SpellCrit, 15*core.SpellCritRatingPerCritChance) - - mage.RegisterAura(core.Aura{ - Label: "Burnout", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if !spell.Flags.Matches(SpellFlagMage) && !result.DidCrit() { - return - } - aura.Unit.SpendMana(sim, aura.Unit.BaseMana*0.01, metric) - }, - }) -} - -func (mage *Mage) applyEnlightenment() { - if !mage.HasRune(proto.MageRune_RuneChestEnlightenment) { - return - } - - damageAuraThreshold := .70 - manaAuraThreshold := .30 - - // https://www.wowhead.com/classic/spell=412326/enlightenment - damageAura := mage.RegisterAura(core.Aura{ - Label: "Enlightenment (Damage)", - ActionID: core.ActionID{SpellID: 412326}, - Duration: core.NeverExpires, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.PseudoStats.DamageDealtMultiplier *= 1.1 - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.PseudoStats.DamageDealtMultiplier /= 1.1 - }, - }) - - // https://www.wowhead.com/classic/spell=412325/enlightenment - manaAura := mage.RegisterAura(core.Aura{ - Label: "Enlightenment (Mana)", - ActionID: core.ActionID{SpellID: 412325}, - Duration: core.NeverExpires, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.PseudoStats.SpiritRegenRateCasting += 0.10 - mage.UpdateManaRegenRates() - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.PseudoStats.SpiritRegenRateCasting -= .10 - mage.UpdateManaRegenRates() - }, - }) - - mage.RegisterAura(core.Aura{ - Label: "Enlightenment", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - damageAura.Activate(sim) - }, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - percentMana := aura.Unit.CurrentManaPercent() - - if percentMana > damageAuraThreshold && !damageAura.IsActive() { - damageAura.Activate(sim) - } else if percentMana <= damageAuraThreshold { - damageAura.Deactivate(sim) - } - - if percentMana < manaAuraThreshold && !manaAura.IsActive() { - manaAura.Activate(sim) - } else if percentMana >= manaAuraThreshold { - manaAura.Deactivate(sim) - } - }, - }) -} - -func (mage *Mage) applyFingersOfFrost() { - if !mage.HasRune(proto.MageRune_RuneChestFingersOfFrost) { - return - } - - mage.FingersOfFrostProcChance += 0.15 - bonusCrit := 10 * float64(mage.Talents.Shatter) * core.SpellCritRatingPerCritChance - - mage.FingersOfFrostAura = mage.RegisterAura(core.Aura{ - Label: "Fingers of Frost Proc", - ActionID: core.ActionID{SpellID: int32(proto.MageRune_RuneChestFingersOfFrost)}, - Duration: time.Second * 15, - MaxStacks: 2, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - mage.AddStatDynamic(sim, stats.SpellCrit, bonusCrit) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - mage.AddStatDynamic(sim, stats.SpellCrit, -bonusCrit) - }, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - // OnCastComplete is called after OnSpellHitDealt / etc, so don't deactivate if it was just activated. - if aura.RemainingDuration(sim) == aura.Duration { - return - } - - if !spell.ProcMask.Matches(core.ProcMaskSpellDamage) || !spell.SpellSchool.Matches(core.SpellSchoolFrost) { - return - } - - if aura.GetStacks() == 1 { - // Brain freeze can be batched with 2x FFBs into Deep Freeze - core.StartDelayedAction(sim, core.DelayedActionOptions{ - DoAt: sim.CurrentTime + core.SpellBatchWindow, - OnAction: func(sim *core.Simulation) { - if aura.IsActive() { - aura.RemoveStack(sim) - } - }, - }) - } else { - aura.RemoveStack(sim) - } - }, - }) - - mage.RegisterAura(core.Aura{ - Label: "Fingers of Frost Trigger", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.Flags.Matches(SpellFlagChillSpell) && sim.RandomFloat("Fingers of Frost") < mage.FingersOfFrostProcChance { - mage.FingersOfFrostAura.Activate(sim) - mage.FingersOfFrostAura.SetStacks(sim, 2) - } - }, - }) -} - -func (mage *Mage) applyHotStreak() { - if !mage.HasRune(proto.MageRune_RuneHelmHotStreak) { - return - } - - actionID := core.ActionID{SpellID: 48108} - - pyroblastSpells := []*core.Spell{} - triggerSpellCodes := []int32{SpellCode_MageFireball, SpellCode_MageFrostfireBolt, SpellCode_MageBalefireBolt, SpellCode_MageFireBlast, SpellCode_MageScorch, SpellCode_MageLivingBombExplosion} - - mage.HotStreakAura = mage.RegisterAura(core.Aura{ - Label: "Hot Streak", - ActionID: actionID, - Duration: time.Second * 10, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - pyroblastSpells = core.FilterSlice(mage.Pyroblast, func(spell *core.Spell) bool { return spell != nil }) - }, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - core.Each(pyroblastSpells, func(spell *core.Spell) { - spell.CastTimeMultiplier -= 1 - spell.Cost.Multiplier -= 100 - }) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - core.Each(pyroblastSpells, func(spell *core.Spell) { - spell.CastTimeMultiplier += 1 - spell.Cost.Multiplier += 100 - }) - }, - }) - - heatingUpAura := mage.RegisterAura(core.Aura{ - Label: "Heating Up", - ActionID: actionID.WithTag(1), - Duration: time.Hour, - }) - - mage.RegisterAura(core.Aura{ - Label: "Hot Streak Trigger", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if !slices.Contains(triggerSpellCodes, spell.SpellCode) { - return - } - - if !result.DidCrit() { - if heatingUpAura.IsActive() { - heatingUpAura.Deactivate(sim) - } - - return - } - - if heatingUpAura.IsActive() { - heatingUpAura.Deactivate(sim) - mage.HotStreakAura.Activate(sim) - } else if mage.HotStreakAura.IsActive() { - // When batching a Scorch crit into an instant Pyro, the Pyro consumes Hot Streak before the Scorch hits, so the Scorch re-applies Heating Up - // We can replicate this by adding a 1ms delay then checking the state of the auras again. - core.StartDelayedAction(sim, core.DelayedActionOptions{ - DoAt: sim.CurrentTime + core.SpellBatchWindow, - OnAction: func(sim *core.Simulation) { - if heatingUpAura.IsActive() { - heatingUpAura.Deactivate(sim) - mage.HotStreakAura.Activate(sim) - } else if !mage.HotStreakAura.IsActive() { - heatingUpAura.Activate(sim) - } - }, - }) - } else { - heatingUpAura.Activate(sim) - } - }, - }) -} - -func (mage *Mage) applyMissileBarrage() { - if !mage.HasRune(proto.MageRune_RuneBeltMissileBarrage) { - return - } - - procChance := .20 - mage.ArcaneBlastMissileBarrageChance = .40 - buffDuration := time.Second * 15 - - arcaneMissilesSpells := []*core.Spell{} - affectedSpellCodes := []int32{SpellCode_MageArcaneBarrage, SpellCode_MageArcaneBlast, SpellCode_MageFireball, SpellCode_MageFrostbolt} - - mage.MissileBarrageAura = mage.RegisterAura(core.Aura{ - Label: "Missile Barrage", - ActionID: core.ActionID{SpellID: 400589}, - Duration: buffDuration, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - arcaneMissilesSpells = core.FilterSlice(mage.ArcaneMissiles, func(spell *core.Spell) bool { return spell != nil }) - }, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - core.Each(arcaneMissilesSpells, func(spell *core.Spell) { - spell.Cost.Multiplier -= 10000 - for _, target := range sim.Encounter.TargetUnits { - spell.Dot(target).TickLength /= 2 - } - }) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - core.Each(arcaneMissilesSpells, func(spell *core.Spell) { - spell.Cost.Multiplier += 10000 - for _, target := range sim.Encounter.TargetUnits { - spell.Dot(target).TickLength *= 2 - } - }) - }, - }) - - mage.RegisterAura(core.Aura{ - Label: "Missile Barrage Talent", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - if !slices.Contains(affectedSpellCodes, spell.SpellCode) { - return - } - - procChance := procChance - if spell.SpellCode == SpellCode_MageArcaneBlast { - procChance = mage.ArcaneBlastMissileBarrageChance - } - - if sim.RandomFloat("Missile Barrage") < procChance { - mage.MissileBarrageAura.Activate(sim) - } - }, - }) -} - -func (mage *Mage) applyBrainFreeze() { - if !mage.HasRune(proto.MageRune_RuneFeetBrainFreeze) { - return - } - - procChance := .20 - buffDuration := time.Second * 15 - - affectedSpells := []*core.Spell{} - triggerSpellCodes := []int32{SpellCode_MageFireball, SpellCode_MageFrostfireBolt, SpellCode_MageSpellfrostBolt, SpellCode_MageBalefireBolt} - - procAura := mage.RegisterAura(core.Aura{ - Label: "Brain Freeze", - ActionID: core.ActionID{SpellID: 400730}, - Duration: buffDuration, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - affectedSpells = core.FilterSlice( - core.Flatten([][]*core.Spell{ - mage.Fireball, - {mage.FrostfireBolt}, - {mage.SpellfrostBolt}, - {mage.BalefireBolt}, - }), - func(spell *core.Spell) bool { return spell != nil }, - ) - }, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - core.Each(affectedSpells, func(spell *core.Spell) { - spell.CastTimeMultiplier -= 1 - spell.Cost.Multiplier -= 100 - }) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - core.Each(affectedSpells, func(spell *core.Spell) { - spell.CastTimeMultiplier += 1 - spell.Cost.Multiplier += 100 - }) - }, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - // OnCastComplete is called after OnSpellHitDealt / etc, so don't deactivate if it was just activated. - if aura.RemainingDuration(sim) == aura.Duration { - return - } - - if slices.Contains(triggerSpellCodes, spell.SpellCode) && spell.CurCast.CastTime == 0 { - aura.Deactivate(sim) - } - }, - }) - - units := []*core.Unit{&mage.Unit} - // Can also proc from Frozen Orb hits - if mage.HasRune(proto.MageRune_RuneCloakFrozenOrb) { - units = append(units, core.MapSlice(mage.frozenOrbPets, func(orb *FrozenOrb) *core.Unit { return &orb.Unit })...) - } - - for _, unit := range units { - core.MakePermanent(unit.RegisterAura(core.Aura{ - Label: "Brain Freeze Trigger", - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.Flags.Matches(SpellFlagChillSpell) && result.Landed() && sim.Proc(procChance, "Brain Freeze") { - procAura.Activate(sim) - } - }, - })) - } -} - -func (mage *Mage) applySpellPower() { - if !mage.HasRune(proto.MageRune_RuneFeetSpellPower) { - return - } - - units := []*core.Unit{&mage.Unit} - // Can also proc from Frozen Orb hits - if mage.HasRune(proto.MageRune_RuneCloakFrozenOrb) { - units = append(units, core.MapSlice(mage.frozenOrbPets, func(orb *FrozenOrb) *core.Unit { return &orb.Unit })...) - } - - for _, unit := range units { - unit.OnSpellRegistered(func(spell *core.Spell) { - if spell.Flags.Matches(SpellFlagMage) { - spell.CritDamageBonus += 0.5 - } - }) - } -} diff --git a/sim/mage/spellfrost_bolt.go b/sim/mage/spellfrost_bolt.go deleted file mode 100644 index cfcd49cc5..000000000 --- a/sim/mage/spellfrost_bolt.go +++ /dev/null @@ -1,54 +0,0 @@ -package mage - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -func (mage *Mage) registerSpellfrostBoltSpell() { - if !mage.HasRune(proto.MageRune_RuneBeltSpellfrostBolt) { - return - } - - baseDamageLow := mage.baseRuneAbilityDamage() * 3.04 - baseDamageHigh := mage.baseRuneAbilityDamage() * 3.55 - spellCoeff := .814 - castTime := time.Millisecond * 2500 - manaCost := .12 - - mage.SpellfrostBolt = mage.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: int32(proto.MageRune_RuneBeltSpellfrostBolt)}, - SpellCode: SpellCode_MageSpellfrostBolt, - SpellSchool: core.SpellSchoolArcane | core.SpellSchoolFrost, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskSpellDamage, - Flags: SpellFlagMage | SpellFlagChillSpell | core.SpellFlagBinary | core.SpellFlagAPL, - MissileSpeed: 28, - - ManaCost: core.ManaCostOptions{ - BaseCost: manaCost, - }, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - CastTime: castTime, - GCD: core.GCDDefault, - }, - }, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: spellCoeff, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := sim.Roll(baseDamageLow, baseDamageHigh) - result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - - spell.WaitTravelTime(sim, func(sim *core.Simulation) { - spell.DealDamage(sim, result) - }) - }, - }) -} diff --git a/sim/mage/talents.go b/sim/mage/talents.go index eb2214e8d..ba15bb390 100644 --- a/sim/mage/talents.go +++ b/sim/mage/talents.go @@ -5,7 +5,6 @@ import ( "time" "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" "github.com/wowsims/classic/sim/core/stats" ) @@ -20,13 +19,6 @@ func (mage *Mage) applyArcaneTalents() { mage.registerPresenceOfMindCD() mage.registerArcanePowerCD() - // For talents that benefit both the mage and frozen orbs - units := []*core.Unit{&mage.Unit} - // Frozen Orb also benefits - if mage.HasRune(proto.MageRune_RuneCloakFrozenOrb) { - units = append(units, core.MapSlice(mage.frozenOrbPets, func(orb *FrozenOrb) *core.Unit { return &orb.Unit })...) - } - // Arcane Subtlety if mage.Talents.ArcaneSubtlety > 0 { threatMultiplier := 1 - .20*float64(mage.Talents.ArcaneSubtlety) @@ -65,14 +57,12 @@ func (mage *Mage) applyArcaneTalents() { bonusDamageMultiplierAdditive := .01 * float64(mage.Talents.ArcaneInstability) bonusCritRating := 1 * float64(mage.Talents.ArcaneInstability) * core.SpellCritRatingPerCritChance - for _, unit := range units { - unit.OnSpellRegistered(func(spell *core.Spell) { - if spell.Flags.Matches(SpellFlagMage) { - spell.DamageMultiplierAdditive += bonusDamageMultiplierAdditive - spell.BonusCritRating += bonusCritRating - } - }) - } + mage.OnSpellRegistered(func(spell *core.Spell) { + if spell.Flags.Matches(SpellFlagMage) { + spell.DamageMultiplierAdditive += bonusDamageMultiplierAdditive + spell.BonusCritRating += bonusCritRating + } + }) } } @@ -120,50 +110,37 @@ func (mage *Mage) applyFrostTalents() { mage.registerIceBarrierSpell() mage.applyWintersChill() - // For talents that benefit both the mage and frozen orbs - units := []*core.Unit{&mage.Unit} - // Frozen Orb also benefits - if mage.HasRune(proto.MageRune_RuneCloakFrozenOrb) { - units = append(units, core.MapSlice(mage.frozenOrbPets, func(orb *FrozenOrb) *core.Unit { return &orb.Unit })...) - } - // Elemental Precision if mage.Talents.ElementalPrecision > 0 { bonusHit := 2 * float64(mage.Talents.ElementalPrecision) * core.SpellHitRatingPerHitChance - for _, unit := range units { - unit.OnSpellRegistered(func(spell *core.Spell) { - if spell.Flags.Matches(SpellFlagMage) && (spell.SpellSchool.Matches(core.SpellSchoolFire) || spell.SpellSchool.Matches(core.SpellSchoolFrost)) { - spell.BonusHitRating += bonusHit - } - }) - } + mage.OnSpellRegistered(func(spell *core.Spell) { + if spell.Flags.Matches(SpellFlagMage) && (spell.SpellSchool.Matches(core.SpellSchoolFire) || spell.SpellSchool.Matches(core.SpellSchoolFrost)) { + spell.BonusHitRating += bonusHit + } + }) } // Ice Shards if mage.Talents.IceShards > 0 { critBonus := .20 * float64(mage.Talents.IceShards) - for _, unit := range units { - unit.OnSpellRegistered(func(spell *core.Spell) { - if spell.SpellSchool.Matches(core.SpellSchoolFrost) && spell.Flags.Matches(SpellFlagMage) { - spell.CritDamageBonus += critBonus - } - }) - } + mage.OnSpellRegistered(func(spell *core.Spell) { + if spell.SpellSchool.Matches(core.SpellSchoolFrost) && spell.Flags.Matches(SpellFlagMage) { + spell.CritDamageBonus += critBonus + } + }) } // Piercing Ice if mage.Talents.PiercingIce > 0 { bonusDamageMultiplierAdditive := 0.02 * float64(mage.Talents.PiercingIce) - for _, unit := range units { - unit.OnSpellRegistered(func(spell *core.Spell) { - if spell.SpellSchool.Matches(core.SpellSchoolFrost) && spell.Flags.Matches(SpellFlagMage) { - spell.DamageMultiplierAdditive += bonusDamageMultiplierAdditive - } - }) - } + mage.OnSpellRegistered(func(spell *core.Spell) { + if spell.SpellSchool.Matches(core.SpellSchoolFrost) && spell.Flags.Matches(SpellFlagMage) { + spell.DamageMultiplierAdditive += bonusDamageMultiplierAdditive + } + }) } // Frost Channeling @@ -230,9 +207,6 @@ func (mage *Mage) applyArcaneConcentration() { if sim.Proc(procChance, "Arcane Concentration") { mage.ClearcastingAura.Activate(sim) - if mage.ArcanePotencyAura != nil { - mage.ArcanePotencyAura.Activate(sim) - } } }, }) @@ -306,24 +280,15 @@ func (mage *Mage) registerArcanePowerCD() { return } - // For talents that benefit both the mage and frozen orbs - units := []*core.Unit{&mage.Unit} - // Frozen Orb also benefits - if mage.HasRune(proto.MageRune_RuneCloakFrozenOrb) { - units = append(units, core.MapSlice(mage.frozenOrbPets, func(orb *FrozenOrb) *core.Unit { return &orb.Unit })...) - } - actionID := core.ActionID{SpellID: 12042} affectedSpells := []*core.Spell{} - for _, unit := range units { - unit.OnSpellRegistered(func(spell *core.Spell) { - if spell.Flags.Matches(SpellFlagMage) { - affectedSpells = append(affectedSpells, spell) - } - }) - } + mage.OnSpellRegistered(func(spell *core.Spell) { + if spell.Flags.Matches(SpellFlagMage) { + affectedSpells = append(affectedSpells, spell) + } + }) mage.ArcanePowerAura = mage.RegisterAura(core.Aura{ Label: "Arcane Power", @@ -411,8 +376,6 @@ func (mage *Mage) registerCombustionCD() { return } - hasOverheatRune := mage.HasRune(proto.MageRune_RuneCloakOverheat) - actionID := core.ActionID{SpellID: 11129} cd := core.Cooldown{ Timer: mage.NewTimer(), @@ -453,8 +416,8 @@ func (mage *Mage) registerCombustionCD() { } // Ignite, Living Bomb explosions, and Fire Blast with Overheart don't consume crit stacks - if spell.SpellCode == SpellCode_MageIgnite || - spell.SpellCode == SpellCode_MageLivingBombExplosion || (hasOverheatRune && spell.SpellCode == SpellCode_MageFireBlast) { + // To Do: Classic - I don't believe ignite can crit so can probably remove this check? + if spell.SpellCode == SpellCode_MageIgnite { return }