Skip to content

Commit fe6f228

Browse files
authored
Merge pull request #99 from wowsims/guardian
Guardian Dev Part 1: Accurate Character Stats
2 parents f81bb0f + 1a5af45 commit fe6f228

12 files changed

Lines changed: 174 additions & 147 deletions

File tree

sim/core/aura_helpers.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,6 @@ func (parentAura *Aura) AttachSpellMod(spellModConfig SpellModConfig) *Aura {
400400
// Attaches a StatDependency to a parent Aura
401401
// Returns parent aura for chaining
402402
func (parentAura *Aura) AttachStatDependency(statDep *stats.StatDependency) *Aura {
403-
404403
parentAura.ApplyOnGain(func(_ *Aura, sim *Simulation) {
405404
parentAura.Unit.EnableBuildPhaseStatDep(sim, statDep)
406405
})
@@ -409,6 +408,10 @@ func (parentAura *Aura) AttachStatDependency(statDep *stats.StatDependency) *Aur
409408
parentAura.Unit.DisableBuildPhaseStatDep(sim, statDep)
410409
})
411410

411+
if parentAura.IsActive() {
412+
parentAura.Unit.StatDependencyManager.EnableDynamicStatDep(statDep)
413+
}
414+
412415
return parentAura
413416
}
414417

sim/core/base_stats.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ var ClassBaseStats = map[proto.Class]stats.Stats{
210210
stats.Strength: 104,
211211
stats.Intellect: 169,
212212
stats.Spirit: 188,
213-
stats.Stamina: 119,
213+
stats.Stamina: 114,
214214
stats.AttackPower: float64(CharacterLevel)*3.0 - 10,
215215
},
216216
proto.Class_ClassMonk: {

sim/core/stats/deps.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ func (sd StatDependency) String() string {
8484
}
8585
}
8686

87+
// Updates the "amount" field for a previously defined dep. Note that if this is
88+
// called after the stats measurement phase, then ApplyStatDependencies() must
89+
// be immediately called afterwards!
90+
func (dep *StatDependency) UpdateValue(newAmount float64) {
91+
dep.amount = newAmount
92+
}
93+
8794
// Manages dependencies between stats.
8895
//
8996
// Some examples:

sim/core/unit.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,21 @@ func (unit *Unit) DisableDynamicStatDep(sim *Simulation, dep *stats.StatDependen
364364
}
365365
}
366366

367+
func (unit *Unit) UpdateDynamicStatDep(sim *Simulation, dep *stats.StatDependency, newAmount float64) {
368+
dep.UpdateValue(newAmount)
369+
370+
if unit.Env.IsFinalized() {
371+
oldStats := unit.stats
372+
unit.stats = unit.ApplyStatDependencies(unit.statsWithoutDeps)
373+
statsChange := unit.stats.Subtract(oldStats)
374+
unit.processDynamicBonus(sim, statsChange)
375+
376+
if sim.Log != nil {
377+
unit.Log(sim, "Dynamic dep updated (%s): %s", dep.String(), statsChange.FlatString())
378+
}
379+
}
380+
}
381+
367382
func (unit *Unit) EnableBuildPhaseStatDep(sim *Simulation, dep *stats.StatDependency) {
368383
if unit.Env.MeasuringStats && unit.Env.State != Finalized {
369384
unit.StatDependencyManager.EnableDynamicStatDep(dep)

sim/druid/_enrage.go

Lines changed: 0 additions & 68 deletions
This file was deleted.

sim/druid/druid.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ type Druid struct {
4040
Berserk *DruidSpell
4141
CatCharge *DruidSpell
4242
DemoralizingRoar *DruidSpell
43-
Enrage *DruidSpell
4443
FaerieFire *DruidSpell
4544
FerociousBite *DruidSpell
4645
ForceOfNature *DruidSpell
@@ -88,7 +87,6 @@ type Druid struct {
8887
CatFormAura *core.Aura
8988
ClearcastingAura *core.Aura
9089
DemoralizingRoarAuras core.AuraArray
91-
EnrageAura *core.Aura
9290
FaerieFireAuras core.AuraArray
9391
FrenziedRegenerationAura *core.Aura
9492
LunarEclipseProcAura *core.Aura
@@ -383,11 +381,12 @@ func New(char *core.Character, form DruidForm, selfBuffs SelfBuffs, talents stri
383381
druid.AddStatDependency(stats.Strength, stats.AttackPower, 1)
384382
druid.AddStatDependency(stats.BonusArmor, stats.Armor, 1)
385383
druid.AddStatDependency(stats.Agility, stats.PhysicalCritPercent, core.CritPerAgiMaxLevel[char.Class])
386-
// Druids get 0.0041 dodge per agi (before dr), roughly 1% per 244
387-
druid.AddStatDependency(stats.Agility, stats.DodgeRating, 0.00410000*core.DodgeRatingPerDodgePercent)
384+
385+
// Druids get roughly 1% Dodge per 951.16 Agi at level 90
386+
druid.AddStatDependency(stats.Agility, stats.DodgeRating, 0.00105135 * core.DodgeRatingPerDodgePercent)
388387

389388
// Base dodge is unaffected by Diminishing Returns
390-
druid.PseudoStats.BaseDodgeChance += 0.04951
389+
druid.PseudoStats.BaseDodgeChance += 0.03
391390

392391
druid.RegisterLeatherSpecialization()
393392

sim/druid/forms.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ const (
2222
// Converts from 0.009327 to 0.0085
2323
const AnimalSpiritRegenSuppression = 0.911337
2424

25+
// Thick Hide contribution handled separately in talents code for cleanliness
26+
// and UI stats display.
27+
const BaseBearArmorMulti = 2.2
28+
2529
func (form DruidForm) Matches(other DruidForm) bool {
2630
return (form & other) != 0
2731
}
@@ -230,7 +234,9 @@ func (druid *Druid) RegisterBearFormAura() {
230234
}
231235

232236
agiApDep := druid.NewDynamicStatDependency(stats.Agility, stats.AttackPower, 2)
233-
stamDep := druid.NewDynamicMultiplyStat(stats.Stamina, 1.2)
237+
stamDep := druid.NewDynamicMultiplyStat(stats.Stamina, 1.4)
238+
critDep := druid.NewDynamicMultiplyStat(stats.CritRating, 1.5) // TODO: Should this be implemented as EquipScaling instead? Need to check elixirs and procs.
239+
hasteDep := druid.NewDynamicMultiplyStat(stats.HasteRating, 1.5) // TODO: Should this be implemented as EquipScaling instead? Need to check elixirs and procs.
234240
leatherSpecDep := druid.NewDynamicMultiplyStat(stats.Stamina, 1.05)
235241

236242
// Need redundant enabling/disabling of the dep both here and below
@@ -249,7 +255,6 @@ func (druid *Druid) RegisterBearFormAura() {
249255
})
250256

251257
clawWeapon := druid.GetBearWeapon()
252-
baseBearArmorMulti := 2.2 // Thick Hide contribution handled separately in talents code for cleanliness and UI stats display.
253258

254259
druid.BearFormAura = druid.RegisterAura(core.Aura{
255260
Label: "Bear Form",
@@ -267,8 +272,10 @@ func (druid *Druid) RegisterBearFormAura() {
267272
druid.PseudoStats.SpiritRegenMultiplier *= AnimalSpiritRegenSuppression
268273

269274
druid.AddStatsDynamic(sim, statBonus)
270-
druid.ApplyDynamicEquipScaling(sim, stats.Armor, baseBearArmorMulti)
275+
druid.ApplyDynamicEquipScaling(sim, stats.Armor, BaseBearArmorMulti)
271276
druid.EnableBuildPhaseStatDep(sim, agiApDep)
277+
druid.EnableBuildPhaseStatDep(sim, critDep)
278+
druid.EnableBuildPhaseStatDep(sim, hasteDep)
272279

273280
// Preserve fraction of max health when shifting
274281
healthFrac := druid.CurrentHealth() / druid.MaxHealth()
@@ -294,8 +301,10 @@ func (druid *Druid) RegisterBearFormAura() {
294301
druid.PseudoStats.SpiritRegenMultiplier /= AnimalSpiritRegenSuppression
295302

296303
druid.AddStatsDynamic(sim, statBonus.Invert())
297-
druid.RemoveDynamicEquipScaling(sim, stats.Armor, baseBearArmorMulti)
304+
druid.RemoveDynamicEquipScaling(sim, stats.Armor, BaseBearArmorMulti)
298305
druid.DisableBuildPhaseStatDep(sim, agiApDep)
306+
druid.DisableBuildPhaseStatDep(sim, critDep)
307+
druid.DisableBuildPhaseStatDep(sim, hasteDep)
299308

300309
healthFrac := druid.CurrentHealth() / druid.MaxHealth()
301310
druid.DisableBuildPhaseStatDep(sim, stamDep)
@@ -311,7 +320,6 @@ func (druid *Druid) RegisterBearFormAura() {
311320
druid.AutoAttacks.SetMH(druid.WeaponFromMainHand(druid.DefaultCritMultiplier()))
312321
druid.AutoAttacks.EnableAutoSwing(sim)
313322
druid.UpdateManaRegenRates()
314-
druid.EnrageAura.Deactivate(sim)
315323

316324
if druid.PulverizeAura.IsActive() {
317325
druid.PulverizeAura.Deactivate(sim)

sim/druid/guardian/enrage.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package guardian
2+
3+
import (
4+
"time"
5+
6+
"github.com/wowsims/mop/sim/core"
7+
"github.com/wowsims/mop/sim/druid"
8+
)
9+
10+
func (bear *GuardianDruid) registerEnrageSpell() {
11+
actionID := core.ActionID{SpellID: 5229}
12+
rageMetrics := bear.NewRageMetrics(actionID)
13+
14+
bear.EnrageAura = bear.RegisterAura(core.Aura{
15+
Label: "Enrage",
16+
ActionID: actionID,
17+
Duration: 10 * time.Second + 1, // add 1 ns duration offset in order to guarantee that the final tick fires
18+
19+
OnGain: func(aura *core.Aura, sim *core.Simulation) {
20+
core.StartPeriodicAction(sim, core.PeriodicActionOptions{
21+
Period: time.Second,
22+
NumTicks: 10,
23+
Priority: core.ActionPriorityRegen,
24+
25+
OnAction: func(sim *core.Simulation) {
26+
if aura.IsActive() {
27+
bear.AddRage(sim, 1, rageMetrics)
28+
}
29+
},
30+
})
31+
},
32+
})
33+
34+
bear.BearFormAura.ApplyOnExpire(func(_ *core.Aura, sim *core.Simulation) {
35+
if !bear.Env.MeasuringStats {
36+
bear.EnrageAura.Deactivate(sim)
37+
}
38+
})
39+
40+
bear.Enrage = bear.RegisterSpell(druid.Bear, core.SpellConfig{
41+
ActionID: actionID,
42+
Flags: core.SpellFlagAPL,
43+
44+
Cast: core.CastConfig{
45+
DefaultCast: core.Cast{
46+
GCD: core.GCDDefault,
47+
},
48+
49+
CD: core.Cooldown{
50+
Timer: bear.NewTimer(),
51+
Duration: time.Minute,
52+
},
53+
54+
IgnoreHaste: true,
55+
},
56+
57+
ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) {
58+
bear.AddRage(sim, 20, rageMetrics)
59+
bear.EnrageAura.Activate(sim)
60+
},
61+
})
62+
63+
bear.AddMajorCooldown(core.MajorCooldown{
64+
Spell: bear.Enrage.Spell,
65+
Type: core.CooldownTypeDPS,
66+
})
67+
}

sim/druid/guardian/tank.go

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,23 +55,73 @@ type GuardianDruid struct {
5555

5656
Options *proto.GuardianDruid_Options
5757
vengeance *core.VengeanceTracker
58+
59+
// Aura references
60+
EnrageAura *core.Aura
61+
62+
// Spell references
63+
Enrage *druid.DruidSpell
5864
}
5965

6066
func (bear *GuardianDruid) GetDruid() *druid.Druid {
6167
return bear.Druid
6268
}
6369

64-
func (bear *GuardianDruid) Initialize() {
65-
bear.Druid.Initialize()
66-
bear.RegisterFeralTankSpells()
70+
func (bear *GuardianDruid) AddRaidBuffs(raidBuffs *proto.RaidBuffs) {
71+
raidBuffs.LeaderOfThePack = true
6772
}
6873

6974
func (bear *GuardianDruid) ApplyTalents() {
7075
// bear.Druid.ApplyTalents()
71-
bear.MultiplyStat(stats.AttackPower, 1.25) // Aggression passive
76+
bear.applyMastery()
77+
bear.applyThickHide()
7278
core.ApplyVengeanceEffect(&bear.Character, bear.vengeance, 84840)
7379
}
7480

81+
func (bear *GuardianDruid) applyMastery() {
82+
const baseMasteryMod = 1.16
83+
const masteryModPerPoint = 0.02
84+
85+
armorMultiplierDep := bear.NewDynamicMultiplyStat(stats.Armor, baseMasteryMod + masteryModPerPoint * bear.GetMasteryPoints())
86+
87+
bear.AddOnMasteryStatChanged(func(sim *core.Simulation, _ float64, newMasteryRating float64) {
88+
bear.UpdateDynamicStatDep(sim, armorMultiplierDep, baseMasteryMod + masteryModPerPoint * core.MasteryRatingToMasteryPoints(newMasteryRating))
89+
})
90+
91+
bear.BearFormAura.AttachStatDependency(armorMultiplierDep)
92+
}
93+
94+
func (bear *GuardianDruid) applyThickHide() {
95+
// Back out the additional multiplier needed to reach 4.3x total (+330%)
96+
const thickHideBearMulti = 4.3 / druid.BaseBearArmorMulti
97+
bear.BearFormAura.ApplyOnGain(func(_ *core.Aura, sim *core.Simulation) {
98+
bear.ApplyDynamicEquipScaling(sim, stats.Armor, thickHideBearMulti)
99+
})
100+
bear.BearFormAura.ApplyOnExpire(func(_ *core.Aura, sim *core.Simulation) {
101+
bear.RemoveDynamicEquipScaling(sim, stats.Armor, thickHideBearMulti)
102+
})
103+
bear.ApplyEquipScaling(stats.Armor, thickHideBearMulti)
104+
105+
// Magical DR
106+
bear.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexArcane] *= 0.75
107+
bear.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFire] *= 0.75
108+
bear.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFrost] *= 0.75
109+
bear.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexHoly] *= 0.75
110+
bear.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexNature] *= 0.75
111+
bear.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexShadow] *= 0.75
112+
113+
// Physical DR
114+
bear.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexPhysical] *= 0.88
115+
116+
// Crit immunity
117+
bear.PseudoStats.ReducedCritTakenChance += 0.06
118+
}
119+
120+
func (bear *GuardianDruid) Initialize() {
121+
bear.Druid.Initialize()
122+
bear.RegisterFeralTankSpells()
123+
}
124+
75125
func (bear *GuardianDruid) Reset(sim *core.Simulation) {
76126
bear.Druid.Reset(sim)
77127
bear.Druid.ClearForm(sim)

ui/core/constants/mechanics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const masteryPercentPerPoint: Map<Spec, number> = new Map([
2424
[Spec.SpecUnholyDeathKnight, 2.5],
2525
[Spec.SpecBalanceDruid, 2.0],
2626
[Spec.SpecFeralDruid, 3.125],
27-
[Spec.SpecGuardianDruid, 4.0],
27+
[Spec.SpecGuardianDruid, 2.0],
2828
[Spec.SpecRestorationDruid, 1.25],
2929
[Spec.SpecHolyPaladin, 1.5],
3030
[Spec.SpecProtectionPaladin, 2.25],

0 commit comments

Comments
 (0)