diff --git a/sim/core/buffs.go b/sim/core/buffs.go index 16395d91ec..5bf866391d 100644 --- a/sim/core/buffs.go +++ b/sim/core/buffs.go @@ -464,11 +464,7 @@ func registerExclusiveSpellHaste(aura *Aura, spellHastePercent float64) { } func MoonkinAura(unit *Unit) *Aura { - aura := makeExclusiveBuff(unit, BuffConfig{ - "Moonkin Aura", - ActionID{SpellID: 24907}, - []StatConfig{}}) - + aura := makeExclusiveBuff(unit, BuffConfig{"Moonkin Aura", ActionID{SpellID: 24907}, nil}) registerExclusiveSpellHaste(aura, 0.05) return aura } diff --git a/sim/core/spell_outcome.go b/sim/core/spell_outcome.go index 61673f8d88..4254c523ac 100644 --- a/sim/core/spell_outcome.go +++ b/sim/core/spell_outcome.go @@ -771,7 +771,8 @@ func (result *SpellResult) applyAttackTableHit(spell *Spell, countHits bool) { } func (result *SpellResult) applyEnemyAttackTableMiss(spell *Spell, attackTable *AttackTable, roll float64, chance *float64) bool { - missChance := result.Target.GetTotalChanceToBeMissedAsDefender(attackTable) + spell.Unit.PseudoStats.IncreasedMissChance + missChance := result.Target.GetTotalChanceToBeMissedAsDefender(attackTable) + if spell.Unit.AutoAttacks.IsDualWielding && !spell.Unit.PseudoStats.DisableDWMissPenalty { missChance += 0.19 } diff --git a/sim/core/stats/stats.go b/sim/core/stats/stats.go index 88f797fdc6..824d4fb68e 100644 --- a/sim/core/stats/stats.go +++ b/sim/core/stats/stats.go @@ -390,8 +390,7 @@ type PseudoStats struct { BonusOHDps float64 BonusRangedDps float64 - DisableDWMissPenalty bool // Used by Heroic Strike and Cleave - IncreasedMissChance float64 // Insect Swarm and Scorpid Sting + DisableDWMissPenalty bool // Used by Heroic Strike and Cleave ThreatMultiplier float64 // Modulates the threat generated. Affected by things like salv. diff --git a/sim/druid/_glyphs.go b/sim/druid/_glyphs.go deleted file mode 100644 index 73972c25a0..0000000000 --- a/sim/druid/_glyphs.go +++ /dev/null @@ -1,70 +0,0 @@ -package druid - -import ( - "time" - - "github.com/wowsims/mop/sim/core" - "github.com/wowsims/mop/sim/core/proto" -) - -func (druid *Druid) ApplyGlyphs() { - - if druid.HasPrimeGlyph(proto.DruidPrimeGlyph_GlyphOfMoonfire) { - druid.AddStaticMod(core.SpellModConfig{ - ClassMask: DruidSpellMoonfireDoT | DruidSpellSunfireDoT, - FloatValue: 0.2, - Kind: core.SpellMod_DamageDone_Flat, - }) - } - - if druid.HasPrimeGlyph(proto.DruidPrimeGlyph_GlyphOfInsectSwarm) { - druid.AddStaticMod(core.SpellModConfig{ - ClassMask: DruidSpellInsectSwarm, - FloatValue: 0.3, - Kind: core.SpellMod_DamageDone_Flat, - }) - } - - if druid.HasPrimeGlyph(proto.DruidPrimeGlyph_GlyphOfWrath) { - druid.AddStaticMod(core.SpellModConfig{ - ClassMask: DruidSpellWrath, - FloatValue: 0.1, - Kind: core.SpellMod_DamageDone_Flat, - }) - } - - if druid.HasMajorGlyph(proto.DruidMajorGlyph_GlyphOfStarfall) { - druid.AddStaticMod(core.SpellModConfig{ - ClassMask: DruidSpellStarfall, - Kind: core.SpellMod_Cooldown_Flat, - TimeValue: time.Second * -30, - }) - } - - if druid.HasMajorGlyph(proto.DruidMajorGlyph_GlyphOfFocus) { - druid.AddStaticMod(core.SpellModConfig{ - ClassMask: DruidSpellStarfall, - Kind: core.SpellMod_DamageDone_Flat, - FloatValue: 0.1, - }) - - // range mod? - } - - if druid.HasPrimeGlyph(proto.DruidPrimeGlyph_GlyphOfStarsurge) { - druid.RegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 62971}, - Label: "Glyph of Starsurge", - 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.ClassSpellMask == DruidSpellStarsurge && !druid.Starfall.CD.IsReady(sim) { - druid.Starfall.CD.Reduce(time.Second * 5) - } - }, - }) - } -} diff --git a/sim/druid/_items.go b/sim/druid/_items.go deleted file mode 100644 index 83e19390bf..0000000000 --- a/sim/druid/_items.go +++ /dev/null @@ -1,339 +0,0 @@ -package druid - -import ( - "time" - - cata "github.com/wowsims/mop/sim/common/cata" - "github.com/wowsims/mop/sim/core" - "github.com/wowsims/mop/sim/core/stats" -) - -// T11 Feral -var ItemSetStormridersBattlegarb = core.NewItemSet(core.ItemSet{ - Name: "Stormrider's Battlegarb", - Bonuses: map[int32]core.ApplySetBonus{ - 2: func(agent core.Agent, setBonusAura *core.Aura) { - // Implemented in rake.go and lacerate.go - druid := agent.(DruidAgent).GetDruid() - druid.T11Feral2pBonus = setBonusAura - }, - 4: func(agent core.Agent, setBonusAura *core.Aura) { - druid := agent.(DruidAgent).GetDruid() - var apDepByStackCount = map[int32]*stats.StatDependency{} - - for i := 1; i <= 3; i++ { - apDepByStackCount[int32(i)] = druid.NewDynamicMultiplyStat(stats.AttackPower, 1.0+0.01*float64(i)) - } - - druid.StrengthOfThePantherAura = druid.RegisterAura(core.Aura{ - Label: "Strength of the Panther", - ActionID: core.ActionID{SpellID: 90166}, - Duration: time.Second * 30, - MaxStacks: 3, - - OnStacksChange: func(aura *core.Aura, sim *core.Simulation, oldStacks int32, newStacks int32) { - if oldStacks > 0 { - druid.DisableDynamicStatDep(sim, apDepByStackCount[oldStacks]) - } - - if newStacks > 0 { - druid.EnableDynamicStatDep(sim, apDepByStackCount[newStacks]) - } - }, - }) - - druid.T11Feral4pBonus = setBonusAura - }, - }, -}) - -// T11 Balance -var ItemSetStormridersRegalia = core.NewItemSet(core.ItemSet{ - Name: "Stormrider's Regalia", - Bonuses: map[int32]core.ApplySetBonus{ - // Increases the critical strike chance of your Insect Swarm and Moonfire spells by 5% - 2: func(_ core.Agent, setBonusAura *core.Aura) { - setBonusAura.AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_BonusCrit_Percent, - FloatValue: 5, - ClassMask: DruidSpellDoT | DruidSpellMoonfire | DruidSpellSunfire, - }) - }, - // Whenever Eclipse triggers, your critical strike chance with spells is increased by 15% for 8 sec. Each critical strike you achieve reduces that bonus by 5% - 4: func(agent core.Agent, setBonusAura *core.Aura) { - druid := agent.(DruidAgent).GetDruid() - - tierSet4pMod := druid.AddDynamicMod(core.SpellModConfig{ - School: core.SpellSchoolArcane | core.SpellSchoolNature, - Kind: core.SpellMod_BonusCrit_Percent, - }) - - tierSet4pAura := druid.RegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 90163}, - Label: "Druid T11 Balance 4P Bonus", - Duration: time.Second * 8, - MaxStacks: 3, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - aura.SetStacks(sim, aura.MaxStacks) - - tierSet4pMod.UpdateFloatValue(float64(aura.GetStacks()) * 5) - tierSet4pMod.Activate() - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - tierSet4pMod.Deactivate() - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if result.DidCrit() && aura.GetStacks() > 0 { - aura.RemoveStack(sim) - tierSet4pMod.UpdateFloatValue(float64(aura.GetStacks()) * 5) - } - }, - }) - - druid.AddEclipseCallback(func(_ Eclipse, gained bool, sim *core.Simulation) { - if setBonusAura.IsActive() { - if gained { - tierSet4pAura.Activate(sim) - } else { - tierSet4pAura.Deactivate(sim) - } - } - }) - }, - }, -}) - -// T12 Feral -var ItemSetObsidianArborweaveBattlegarb = core.NewItemSet(core.ItemSet{ - Name: "Obsidian Arborweave Battlegarb", - Bonuses: map[int32]core.ApplySetBonus{ - 2: func(agent core.Agent, setBonusAura *core.Aura) { - // TODO: Verify behavior after PTR testing - druid := agent.(DruidAgent).GetDruid() - cata.RegisterIgniteEffect(&druid.Unit, cata.IgniteConfig{ - ActionID: core.ActionID{SpellID: 99002}, - DotAuraLabel: "Fiery Claws", - IncludeAuraDelay: true, - ParentAura: setBonusAura, - - ProcTrigger: core.ProcTrigger{ - Name: "Fiery Claws Trigger", - Callback: core.CallbackOnSpellHitDealt, - ClassSpellMask: DruidSpellMangle | DruidSpellMaul | DruidSpellShred, - Outcome: core.OutcomeLanded, - }, - - DamageCalculator: func(result *core.SpellResult) float64 { - return result.Damage * 0.1 - }, - }) - }, - 4: func(agent core.Agent, setBonusAura *core.Aura) { - // Full implementation in berserk.go and barkskin.go - druid := agent.(DruidAgent).GetDruid() - druid.T12Feral4pBonus = setBonusAura - - if !druid.InForm(Bear) { - return - } - - druid.SmokescreenAura = druid.RegisterAura(core.Aura{ - Label: "Smokescreen", - ActionID: core.ActionID{SpellID: 99011}, - Duration: time.Second * 12, - - OnGain: func(_ *core.Aura, _ *core.Simulation) { - druid.PseudoStats.BaseDodgeChance += 0.1 - }, - - OnExpire: func(_ *core.Aura, _ *core.Simulation) { - druid.PseudoStats.BaseDodgeChance -= 0.1 - }, - }) - }, - }, -}) - -// T12 Balance -var ItemSetObsidianArborweaveRegalia = core.NewItemSet(core.ItemSet{ - Name: "Obsidian Arborweave Regalia", - Bonuses: map[int32]core.ApplySetBonus{ - // You have a chance to summon a Burning Treant to assist you in battle for 15 sec when you cast Wrath or Starfire. (Proc chance: 20%, 45s cooldown) - 2: func(agent core.Agent, setBonusAura *core.Aura) { - druid := agent.(DruidAgent).GetDruid() - - setBonusAura.AttachProcTrigger(core.ProcTrigger{ - ActionID: core.ActionID{SpellID: 99019}, - Name: "Item - Druid T12 Balance 2P Bonus", - Callback: core.CallbackOnCastComplete, - ClassSpellMask: DruidSpellWrath | DruidSpellStarfire, - ProcChance: 0.20, - ICD: time.Second * 45, - Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - druid.BurningTreant.EnableWithTimeout(sim, druid.BurningTreant, time.Second*15) - }, - }) - }, - // While not in an Eclipse state, your Wrath generates 3 additional Lunar Energy and your Starfire generates 5 additional Solar Energy. - 4: func(agent core.Agent, setBonusAura *core.Aura) { - druid := agent.(DruidAgent).GetDruid() - - setBonusAura.AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_Custom, - ClassMask: DruidSpellWrath, - ApplyCustom: func(mod *core.SpellMod, spell *core.Spell) { - druid.SetSpellEclipseEnergy(DruidSpellWrath, WrathBaseEnergyGain, Wrath4PT12EnergyGain) - }, - RemoveCustom: func(mod *core.SpellMod, spell *core.Spell) { - druid.SetSpellEclipseEnergy(DruidSpellWrath, WrathBaseEnergyGain, WrathBaseEnergyGain) - }, - }) - - setBonusAura.AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_Custom, - ClassMask: DruidSpellStarfire, - ApplyCustom: func(mod *core.SpellMod, spell *core.Spell) { - druid.SetSpellEclipseEnergy(DruidSpellStarfire, StarfireBaseEnergyGain, Starfire4PT12EnergyGain) - }, - RemoveCustom: func(mod *core.SpellMod, spell *core.Spell) { - druid.SetSpellEclipseEnergy(DruidSpellStarfire, StarfireBaseEnergyGain, StarfireBaseEnergyGain) - }, - }) - - setBonusAura.ExposeToAPL(99049) - }, - }, -}) - -// T13 Feral -var ItemSetDeepEarthBattlegarb = core.NewItemSet(core.ItemSet{ - Name: "Deep Earth Battlegarb", - Bonuses: map[int32]core.ApplySetBonus{ - 2: func(agent core.Agent, setBonusAura *core.Aura) { - druid := agent.(DruidAgent).GetDruid() - - if druid.InForm(Bear) { - setBonusAura.AttachProcTrigger(core.ProcTrigger{ - Name: "T13 Savage Defense Trigger", - Callback: core.CallbackOnSpellHitDealt, - ClassSpellMask: DruidSpellMangleBear, - Outcome: core.OutcomeCrit, - - Handler: func(sim *core.Simulation, _ *core.Spell, _ *core.SpellResult) { - if druid.PulverizeAura.IsActive() { - druid.SavageDefenseAura.Activate(sim) - } - }, - }) - } - - if !druid.InForm(Cat) { - return - } - - // Rather than creating a whole extra Execute phase category just for this bonus, we will instead scale up ExecuteProportion_25 using linear interpolation. Note that we use ExecuteProportion_90 for Predatory Strikes (< 80%), which is why the math below looks funny. - oldExecuteProportion_25 := druid.Env.Encounter.ExecuteProportion_25 - oldExecuteProportion_35 := druid.Env.Encounter.ExecuteProportion_35 - newExecuteProportion_25 := oldExecuteProportion_35*(1.0-(60.0-35.0)/(80.0-35.0)) + druid.Env.Encounter.ExecuteProportion_90*((60.0-35.0)/(80.0-35.0)) - newExecuteProportion_35 := 0.5 * (newExecuteProportion_25 + druid.Env.Encounter.ExecuteProportion_90) // We don't use this field anywhere, just need it to be any value above ExecuteProportion_25 but below ExecuteProportion_90 so that the transitions work properly. - - setBonusAura.ApplyOnGain(func(_ *core.Aura, _ *core.Simulation) { - druid.Env.Encounter.ExecuteProportion_35 = newExecuteProportion_35 - druid.Env.Encounter.ExecuteProportion_25 = newExecuteProportion_25 - }) - - setBonusAura.ApplyOnExpire(func(_ *core.Aura, _ *core.Simulation) { - druid.Env.Encounter.ExecuteProportion_25 = oldExecuteProportion_25 - druid.Env.Encounter.ExecuteProportion_35 = oldExecuteProportion_35 - }) - }, - 4: func(agent core.Agent, setBonusAura *core.Aura) { - // Implemented in tigers_fury.go - druid := agent.(DruidAgent).GetDruid() - druid.T13Feral4pBonus = setBonusAura - }, - }, -}) - -// T13 Balance -var ItemSetDeepEarthRegalia = core.NewItemSet(core.ItemSet{ - Name: "Deep Earth Regalia", - Bonuses: map[int32]core.ApplySetBonus{ - // Insect Swarm increases all damage done by your Starfire, Starsurge, and Wrath spells against that target by 3% - 2: func(agent core.Agent, setBonusAura *core.Aura) { - druid := agent.(DruidAgent).GetDruid() - - t13InsectSwarmBonus := func(_ *core.Simulation, spell *core.Spell, _ *core.AttackTable) float64 { - if spell.Matches(DruidSpellStarsurge | DruidSpellStarfire | DruidSpellWrath) { - return 1.03 - } - - return 1.0 - } - - t13InsectSwarmBonusDummyAuras := druid.NewEnemyAuraArray(func(target *core.Unit) *core.Aura { - return target.GetOrRegisterAura(core.Aura{ - ActionID: core.ActionID{SpellID: 105722}, - Label: "Item - Druid T13 Balance 2P Bonus (Insect Swarm) - " + druid.Label, - Duration: core.NeverExpires, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - druid.AttackTables[aura.Unit.UnitIndex].DamageDoneByCasterMultiplier = t13InsectSwarmBonus - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - druid.AttackTables[aura.Unit.UnitIndex].DamageDoneByCasterMultiplier = nil - }, - }) - }) - - druid.OnSpellRegistered(func(spell *core.Spell) { - if !spell.Matches(DruidSpellInsectSwarm) { - return - } - - for _, target := range druid.Env.Encounter.TargetUnits { - spell.Dot(target).ApplyOnGain(func(aura *core.Aura, sim *core.Simulation) { - if setBonusAura.IsActive() { - t13InsectSwarmBonusDummyAuras.Get(aura.Unit).Activate(sim) - } - }) - - spell.Dot(target).ApplyOnExpire(func(aura *core.Aura, sim *core.Simulation) { - t13InsectSwarmBonusDummyAuras.Get(aura.Unit).Deactivate(sim) - }) - } - }) - }, - // Reduces the cooldown of Starsurge by 5 sec and increases its damage by 10% - 4: func(_ core.Agent, setBonusAura *core.Aura) { - setBonusAura.AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.1, - ClassMask: DruidSpellStarsurge, - }) - - setBonusAura.AttachSpellMod(core.SpellModConfig{ - Kind: core.SpellMod_Cooldown_Flat, - TimeValue: time.Second * -5, - ClassMask: DruidSpellStarsurge, - }) - }, - }, -}) - -// PvP Feral -var ItemSetGladiatorsSanctuary = core.NewItemSet(core.ItemSet{ - ID: 922, - Name: "Gladiator's Sanctuary", - Bonuses: map[int32]core.ApplySetBonus{ - 2: func(agent core.Agent, setBonusAura *core.Aura) { - setBonusAura.AttachStatBuff(stats.Agility, 70) - - }, - 4: func(agent core.Agent, setBonusAura *core.Aura) { - setBonusAura.AttachStatBuff(stats.Agility, 90) - }, - }, -}) - -func init() { -} diff --git a/sim/druid/_starfall.go b/sim/druid/_starfall.go deleted file mode 100644 index 3a658bb712..0000000000 --- a/sim/druid/_starfall.go +++ /dev/null @@ -1,73 +0,0 @@ -package druid - -import ( - "time" - - "github.com/wowsims/mop/sim/core" - "github.com/wowsims/mop/sim/core/proto" -) - -func (druid *Druid) registerStarfallSpell() { - if !druid.Talents.Starfall { - return - } - - numberOfTicks := core.TernaryInt32(druid.Env.GetNumTargets() > 1, 20, 10) - tickLength := time.Second - - starfallTickSpell := druid.RegisterSpell(Humanoid|Moonkin, core.SpellConfig{ - ActionID: core.ActionID{SpellID: 50288}, - SpellSchool: core.SpellSchoolArcane, - ProcMask: core.ProcMaskSpellDamage, - ClassSpellMask: DruidSpellStarfall, - Flags: SpellFlagOmenTrigger, - - DamageMultiplier: 1, - CritMultiplier: druid.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: 0.247, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - min, max := core.CalcScalingSpellEffectVarianceMinMax(proto.Class_ClassDruid, 0.404, 0.15) - baseDamage := sim.Roll(min, max) - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - }, - }) - - druid.Starfall = druid.RegisterSpell(Humanoid|Moonkin, core.SpellConfig{ - ActionID: core.ActionID{SpellID: 48505}, - SpellSchool: core.SpellSchoolArcane, - ProcMask: core.ProcMaskSpellProc, - Flags: core.SpellFlagAPL, - ManaCost: core.ManaCostOptions{ - BaseCostPercent: 35, - PercentModifier: 100, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - CD: core.Cooldown{ - Timer: druid.NewTimer(), - Duration: time.Second * 90, - }, - }, - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Starfall", - }, - NumberOfTicks: numberOfTicks, - TickLength: tickLength, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - starfallTickSpell.Cast(sim, target) - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcAndDealOutcome(sim, target, spell.OutcomeMagicHit) - if result.Landed() { - spell.Dot(target).Apply(sim) - } - }, - }) -} diff --git a/sim/druid/_starfire.go b/sim/druid/_starfire.go deleted file mode 100644 index 3d432fecb6..0000000000 --- a/sim/druid/_starfire.go +++ /dev/null @@ -1,79 +0,0 @@ -package druid - -import ( - "time" - - "github.com/wowsims/mop/sim/core" - "github.com/wowsims/mop/sim/core/proto" -) - -func (druid *Druid) registerStarfireSpell() { - druid.SetSpellEclipseEnergy(DruidSpellStarfire, StarfireBaseEnergyGain, StarfireBaseEnergyGain) - - hasStarfireGlyph := druid.HasMajorGlyph(proto.DruidMajorGlyph(proto.DruidPrimeGlyph_GlyphOfStarfire)) - - starfireGlyphSpell := druid.RegisterSpell(Humanoid|Moonkin, core.SpellConfig{ - ActionID: core.ActionID{SpellID: 54845}, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagNoLogs, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - moonfireDot := druid.Moonfire.Dot(target) - sunfireDot := druid.Sunfire.Dot(target) - - tryExtendDot(moonfireDot, &druid.ExtendingMoonfireStacks) - tryExtendDot(sunfireDot, &druid.ExtendingMoonfireStacks) - }, - }) - - druid.Starfire = druid.RegisterSpell(Humanoid|Moonkin, core.SpellConfig{ - ActionID: core.ActionID{SpellID: 2912}, - SpellSchool: core.SpellSchoolArcane, - ProcMask: core.ProcMaskSpellDamage, - ClassSpellMask: DruidSpellStarfire, - Flags: core.SpellFlagAPL | SpellFlagOmenTrigger, - - ManaCost: core.ManaCostOptions{ - BaseCostPercent: 11, - PercentModifier: 100, - }, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - CastTime: time.Millisecond * 3200, - }, - }, - - BonusCoefficient: 1.231, - - // TODO: Was the value of 1 here incorrect to begin with? - BonusCritPercent: 1 / core.CritRatingPerCritPercent, - - DamageMultiplier: 1, - - CritMultiplier: druid.DefaultCritMultiplier(), - - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - min, max := core.CalcScalingSpellEffectVarianceMinMax(proto.Class_ClassDruid, 1.383, 0.22) - baseDamage := sim.Roll(min, max) - result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - - if result.Landed() { - if hasStarfireGlyph { - starfireGlyphSpell.Cast(sim, target) - } - - spell.DealDamage(sim, result) - } - }, - }) -} - -func tryExtendDot(dot *core.Dot, extendingStacks *int) { - if dot.IsActive() && *extendingStacks > 0 { - *extendingStacks -= 1 - dot.UpdateExpires(dot.ExpiresAt() + time.Second*3) - } -} diff --git a/sim/druid/balance/TestBalance.results b/sim/druid/balance/TestBalance.results new file mode 100644 index 0000000000..8b0373b063 --- /dev/null +++ b/sim/druid/balance/TestBalance.results @@ -0,0 +1,520 @@ +character_stats_results: { + key: "TestBalance-CharacterStats-Default" + value: { + final_stats: 189 + final_stats: 199.227 + final_stats: 16901.17 + final_stats: 16238.67 + final_stats: 4679 + final_stats: 5553 + final_stats: 950 + final_stats: 4539 + final_stats: 0 + final_stats: 185.36972 + final_stats: 0 + final_stats: 5246 + final_stats: 493.9 + final_stats: 0 + final_stats: 24244.737 + final_stats: 0 + final_stats: 0 + final_stats: 9733.2 + final_stats: 0 + final_stats: 383019.38 + final_stats: 300000 + final_stats: 3000 + final_stats: 16.33235 + final_stats: 16.33235 + final_stats: 14.22151 + final_stats: 14.84257 + final_stats: 0 + } +} +dps_results: { + key: "TestBalance-AllItems-AgilePrimalDiamond" + value: { + dps: 78668.90109 + tps: 79783.05887 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-AssuranceofConsequence-105472" + value: { + dps: 76455.97031 + tps: 77610.52083 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-AusterePrimalDiamond" + value: { + dps: 78174.22584 + tps: 79295.26729 + hps: 4892.68529 + } +} +dps_results: { + key: "TestBalance-AllItems-BurningPrimalDiamond" + value: { + dps: 79626.66551 + tps: 80731.62887 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-CapacitivePrimalDiamond" + value: { + dps: 78770.59583 + tps: 79898.02228 + hps: 4825.08664 + } +} +dps_results: { + key: "TestBalance-AllItems-CourageousPrimalDiamond" + value: { + dps: 79699.72578 + tps: 80803.19932 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-DelicateVialoftheSanguinaire-96895" + value: { + dps: 76455.97031 + tps: 77610.52083 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-DestructivePrimalDiamond" + value: { + dps: 78775.77218 + tps: 79911.40862 + hps: 4825.08664 + } +} +dps_results: { + key: "TestBalance-AllItems-EffulgentPrimalDiamond" + value: { + dps: 78174.22584 + tps: 79295.26729 + hps: 4892.68529 + } +} +dps_results: { + key: "TestBalance-AllItems-EmberPrimalDiamond" + value: { + dps: 79122.02643 + tps: 80233.93255 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-EnchantWeapon-BloodyDancingSteel-5125" + value: { + dps: 77098.20049 + tps: 78256.0123 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-EnchantWeapon-Colossus-4445" + value: { + dps: 77098.20049 + tps: 78256.0123 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-EnchantWeapon-DancingSteel-4444" + value: { + dps: 77098.20049 + tps: 78256.0123 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-EnchantWeapon-River'sSong-4446" + value: { + dps: 77098.20049 + tps: 78256.0123 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-EnchantWeapon-SpiritofConquest-5124" + value: { + dps: 77098.20049 + tps: 78256.0123 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-EnchantWeapon-Windsong-4441" + value: { + dps: 78004.68266 + tps: 79230.65004 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-EnigmaticPrimalDiamond" + value: { + dps: 78775.77218 + tps: 79911.40862 + hps: 4825.08664 + } +} +dps_results: { + key: "TestBalance-AllItems-EternalPrimalDiamond" + value: { + dps: 78174.22584 + tps: 79295.26729 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-EvilEyeofGalakras-105491" + value: { + dps: 76455.97031 + tps: 77610.52083 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-FabledFeatherofJi-Kun-96842" + value: { + dps: 76455.97031 + tps: 77610.52083 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-FleetPrimalDiamond" + value: { + dps: 78791.54431 + tps: 79912.58575 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-ForlornPrimalDiamond" + value: { + dps: 79122.02643 + tps: 80233.93255 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-GazeoftheTwins-96915" + value: { + dps: 78556.99892 + tps: 79684.8494 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-Horridon'sLastGasp-96757" + value: { + dps: 83372.09829 + tps: 84466.70288 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-ImpassivePrimalDiamond" + value: { + dps: 78775.77218 + tps: 79911.40862 + hps: 4825.08664 + } +} +dps_results: { + key: "TestBalance-AllItems-IndomitablePrimalDiamond" + value: { + dps: 78174.22584 + tps: 79295.26729 + hps: 4892.68529 + } +} +dps_results: { + key: "TestBalance-AllItems-InscribedBagofHydra-Spawn-96828" + value: { + dps: 76455.97031 + tps: 77607.65085 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-Ji-Kun'sRisingWinds-96843" + value: { + dps: 76455.97031 + tps: 77610.52083 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-PhaseFingers-4697" + value: { + dps: 79728.10908 + tps: 80820.63319 + hps: 4825.08664 + } +} +dps_results: { + key: "TestBalance-AllItems-PowerfulPrimalDiamond" + value: { + dps: 78174.22584 + tps: 79295.26729 + hps: 4892.68529 + } +} +dps_results: { + key: "TestBalance-AllItems-PriceofProgress-81266" + value: { + dps: 79626.66551 + tps: 80731.62887 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-Primordius'TalismanofRage-96873" + value: { + dps: 78966.26943 + tps: 80074.75843 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-RegaliaoftheEternalBlossom" + value: { + dps: 85353.18141 + tps: 86630.16466 + hps: 5377.68438 + } +} +dps_results: { + key: "TestBalance-AllItems-RegaliaoftheHauntedForest" + value: { + dps: 92516.93202 + tps: 93566.07493 + hps: 5901.76314 + } +} +dps_results: { + key: "TestBalance-AllItems-RegaliaoftheShatteredVale" + value: { + dps: 92627.43346 + tps: 93966.65705 + hps: 6007.43263 + } +} +dps_results: { + key: "TestBalance-AllItems-Renataki'sSoulCharm-96741" + value: { + dps: 76455.97031 + tps: 77610.52083 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-ReverberatingPrimalDiamond" + value: { + dps: 78668.90109 + tps: 79783.05887 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-RevitalizingPrimalDiamond" + value: { + dps: 78668.90109 + tps: 79782.33978 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-SinisterPrimalDiamond" + value: { + dps: 78770.59583 + tps: 79898.02228 + hps: 4825.08664 + } +} +dps_results: { + key: "TestBalance-AllItems-SynapseSprings(MarkII)-4898" + value: { + dps: 80990.67127 + tps: 81990.27854 + hps: 4825.08664 + } +} +dps_results: { + key: "TestBalance-AllItems-TalismanofBloodlust-96864" + value: { + dps: 79282.98399 + tps: 80591.80814 + hps: 4814.55361 + } +} +dps_results: { + key: "TestBalance-AllItems-TheGloamingBlade-88149" + value: { + dps: 79626.66551 + tps: 80731.62887 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-TyrannicalPrimalDiamond" + value: { + dps: 78174.22584 + tps: 79295.26729 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-UnerringVisionofLeiShen-96930" + value: { + dps: 86027.21497 + tps: 87111.51931 + hps: 4825.08664 + } +} +dps_results: { + key: "TestBalance-AllItems-Wushoolay'sFinalChoice-96785" + value: { + dps: 103926.84559 + tps: 104598.02776 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-YaungolFireCarrier-86518" + value: { + dps: 79626.66551 + tps: 80731.62887 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-AllItems-ZenAlchemistStone-75274" + value: { + dps: 82259.88404 + tps: 83202.72224 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-Average-Default" + value: { + dps: 80391.9934 + tps: 81432.90047 + hps: 4787.23875 + } +} +dps_results: { + key: "TestBalance-Settings-NightElf-preraid-Default-standard-FullBuffs-0.0yards-LongMultiTarget" + value: { + dps: 87855.16898 + tps: 128713.11039 + hps: 4810.72341 + } +} +dps_results: { + key: "TestBalance-Settings-NightElf-preraid-Default-standard-FullBuffs-0.0yards-LongSingleTarget" + value: { + dps: 79626.66551 + tps: 80731.62887 + hps: 4826.04419 + } +} +dps_results: { + key: "TestBalance-Settings-NightElf-preraid-Default-standard-FullBuffs-0.0yards-ShortSingleTarget" + value: { + dps: 118205.04535 + tps: 115686.66219 + hps: 5170.76163 + } +} +dps_results: { + key: "TestBalance-Settings-NightElf-preraid-Default-standard-NoBuffs-0.0yards-LongMultiTarget" + value: { + dps: 55195.47797 + tps: 94313.17142 + hps: 4554.10711 + } +} +dps_results: { + key: "TestBalance-Settings-NightElf-preraid-Default-standard-NoBuffs-0.0yards-LongSingleTarget" + value: { + dps: 50446.56707 + tps: 52420.20675 + hps: 4555.01088 + } +} +dps_results: { + key: "TestBalance-Settings-NightElf-preraid-Default-standard-NoBuffs-0.0yards-ShortSingleTarget" + value: { + dps: 58555.19567 + tps: 60676.34404 + hps: 4880.3688 + } +} +dps_results: { + key: "TestBalance-Settings-NightElf-t14-Default-standard-FullBuffs-0.0yards-LongMultiTarget" + value: { + dps: 131929.13121 + tps: 176937.13237 + hps: 6038.39565 + } +} +dps_results: { + key: "TestBalance-Settings-NightElf-t14-Default-standard-FullBuffs-0.0yards-LongSingleTarget" + value: { + dps: 121432.40315 + tps: 122484.29323 + hps: 6052.83581 + } +} +dps_results: { + key: "TestBalance-Settings-NightElf-t14-Default-standard-FullBuffs-0.0yards-ShortSingleTarget" + value: { + dps: 177184.92109 + tps: 173111.5965 + hps: 6443.92361 + } +} +dps_results: { + key: "TestBalance-Settings-NightElf-t14-Default-standard-NoBuffs-0.0yards-LongMultiTarget" + value: { + dps: 86205.64366 + tps: 130647.61217 + hps: 5674.45216 + } +} +dps_results: { + key: "TestBalance-Settings-NightElf-t14-Default-standard-NoBuffs-0.0yards-LongSingleTarget" + value: { + dps: 77917.3953 + tps: 80191.13873 + hps: 5678.96106 + } +} +dps_results: { + key: "TestBalance-Settings-NightElf-t14-Default-standard-NoBuffs-0.0yards-ShortSingleTarget" + value: { + dps: 88707.4898 + tps: 91015.20693 + hps: 6087.01662 + } +} +dps_results: { + key: "TestBalance-SwitchInFrontOfTarget-Default" + value: { + dps: 79626.66551 + tps: 80731.62887 + hps: 4826.04419 + } +} diff --git a/sim/druid/_apl_values.go b/sim/druid/balance/apl_values.go similarity index 79% rename from sim/druid/_apl_values.go rename to sim/druid/balance/apl_values.go index e0979832f0..94da09fa5f 100644 --- a/sim/druid/_apl_values.go +++ b/sim/druid/balance/apl_values.go @@ -1,11 +1,11 @@ -package druid +package balance import ( "github.com/wowsims/mop/sim/core" "github.com/wowsims/mop/sim/core/proto" ) -func (druid *Druid) NewAPLValue(rot *core.APLRotation, config *proto.APLValue) core.APLValue { +func (druid *BalanceDruid) NewAPLValue(rot *core.APLRotation, config *proto.APLValue) core.APLValue { switch config.Value.(type) { case *proto.APLValue_CurrentSolarEnergy: return druid.newValueCurrentSolarEnergy(config.GetCurrentSolarEnergy(), config.Uuid) @@ -20,10 +20,10 @@ func (druid *Druid) NewAPLValue(rot *core.APLRotation, config *proto.APLValue) c type APLValueCurrentSolarEnergy struct { core.DefaultAPLValueImpl - druid *Druid + druid *BalanceDruid } -func (druid *Druid) newValueCurrentSolarEnergy(_ *proto.APLValueCurrentSolarEnergy, uuid *proto.UUID) core.APLValue { +func (druid *BalanceDruid) newValueCurrentSolarEnergy(_ *proto.APLValueCurrentSolarEnergy, uuid *proto.UUID) core.APLValue { return &APLValueCurrentSolarEnergy{ druid: druid, } @@ -43,10 +43,10 @@ func (value *APLValueCurrentSolarEnergy) String() string { type APLValueCurrentLunarEnergy struct { core.DefaultAPLValueImpl - druid *Druid + druid *BalanceDruid } -func (druid *Druid) newValueCurrentLunarEnergy(_ *proto.APLValueCurrentLunarEnergy, uuid *proto.UUID) core.APLValue { +func (druid *BalanceDruid) newValueCurrentLunarEnergy(_ *proto.APLValueCurrentLunarEnergy, uuid *proto.UUID) core.APLValue { return &APLValueCurrentLunarEnergy{ druid: druid, } @@ -61,13 +61,13 @@ func (value *APLValueCurrentLunarEnergy) GetInt(sim *core.Simulation) int32 { } func (value *APLValueCurrentLunarEnergy) String() string { - return "Current Solar Energy" + return "Current Lunar Energy" } type APLValueCurrentEclipsePhase struct { core.DefaultAPLValueImpl phase proto.APLValueEclipsePhase - druid *Druid + druid *BalanceDruid } func (value *APLValueCurrentEclipsePhase) Type() proto.APLValueType { @@ -90,7 +90,7 @@ func (value *APLValueCurrentEclipsePhase) String() string { return "Current Eclipse Phase" } -func (druid *Druid) newValueCurrentEclipsePhase(config *proto.APLValueCurrentEclipsePhase, uuid *proto.UUID) core.APLValue { +func (druid *BalanceDruid) newValueCurrentEclipsePhase(config *proto.APLValueCurrentEclipsePhase, uuid *proto.UUID) core.APLValue { return &APLValueCurrentEclipsePhase{ druid: druid, phase: config.EclipsePhase, diff --git a/sim/druid/balance/astral_communion.go b/sim/druid/balance/astral_communion.go new file mode 100644 index 0000000000..e541535d93 --- /dev/null +++ b/sim/druid/balance/astral_communion.go @@ -0,0 +1,68 @@ +package balance + +import ( + "time" + + "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/druid" +) + +const ( + EnergyGainPerTick = 25.0 + EnergyGainPerTickDuringSotF = 100.0 +) + +func (moonkin *BalanceDruid) registerAstralCommunionSpell() { + actionID := core.ActionID{SpellID: 127663} + + eclipseEnergyGain := EnergyGainPerTick + + solarMetric := moonkin.NewSolarEnergyMetrics(actionID) + lunarMetric := moonkin.NewLunarEnergyMetrics(actionID) + + moonkin.AstralCommunion = moonkin.RegisterSpell(druid.Humanoid|druid.Moonkin, core.SpellConfig{ + ActionID: actionID, + SpellSchool: core.SpellSchoolArcane, + Flags: core.SpellFlagHelpful | core.SpellFlagChanneled | core.SpellFlagAPL, + ClassSpellMask: druid.DruidSpellAstralCommunion, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{GCD: core.GCDDefault}, + }, + + Hot: core.DotConfig{ + SelfOnly: true, + Aura: core.Aura{Label: "Astral Communion"}, + NumberOfTicks: 4, + TickLength: time.Second * 1, + AffectedByCastSpeed: false, + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + if moonkin.CanGainEnergy(SolarAndLunarEnergy) { + moonkin.AddEclipseEnergy(eclipseEnergyGain, LunarEnergy, sim, lunarMetric, dot.Spell) + } else { + moonkin.AddEclipseEnergy(eclipseEnergyGain, SolarEnergy, sim, solarMetric, dot.Spell) + } + }, + }, + + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + spell.SelfHot().Apply(sim) + + if moonkin.AstralInsight.IsActive() { + eclipseEnergyGain = EnergyGainPerTickDuringSotF + + spell.SelfHot().TickOnce(sim) + spell.SelfHot().Deactivate(sim) + + eclipseEnergyGain = EnergyGainPerTick + moonkin.AstralInsight.Deactivate(sim) + } + }, + }) + + moonkin.AddEclipseCallback(func(_ Eclipse, gained bool, sim *core.Simulation) { + if gained && moonkin.AstralCommunion.SelfHot().IsActive() { + moonkin.AstralCommunion.SelfHot().Deactivate(sim) + } + }) +} diff --git a/sim/druid/balance/astral_storm.go b/sim/druid/balance/astral_storm.go new file mode 100644 index 0000000000..aa08d0e64e --- /dev/null +++ b/sim/druid/balance/astral_storm.go @@ -0,0 +1,69 @@ +package balance + +import ( + "time" + + "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/druid" +) + +const ( + AstralStormBonusCoeff = 0.236 + AstralStormCoeff = 0.199 +) + +func (moonkin *BalanceDruid) registerAstralStormSpell() { + moonkin.AstralStormTickSpell = moonkin.RegisterSpell(druid.Humanoid|druid.Moonkin, core.SpellConfig{ + ActionID: core.ActionID{SpellID: 106998}, + SpellSchool: core.SpellSchoolArcane, + ProcMask: core.ProcMaskSpellProc, + Flags: core.SpellFlagAoE, + ClassSpellMask: druid.DruidSpellAstralStorm, + + CritMultiplier: moonkin.DefaultCritMultiplier(), + DamageMultiplier: 1, + ThreatMultiplier: 1, + BonusCoefficient: AstralStormBonusCoeff, + + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + damage := moonkin.CalcScalingSpellDmg(AstralStormCoeff) + + for _, aoeTarget := range sim.Encounter.TargetUnits { + spell.CalcAndDealDamage(sim, aoeTarget, damage, spell.OutcomeMagicHitAndCrit) + } + }, + }) + + moonkin.AstralStorm = moonkin.RegisterSpell(druid.Humanoid|druid.Moonkin, core.SpellConfig{ + ActionID: core.ActionID{SpellID: 106996}, + SpellSchool: core.SpellSchoolArcane, + ProcMask: core.ProcMaskSpellDamage, + Flags: core.SpellFlagChanneled | core.SpellFlagAPL, + ClassSpellMask: druid.DruidSpellAstralStorm, + + ManaCost: core.ManaCostOptions{ + BaseCostPercent: 50.3, + }, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + }, + Dot: core.DotConfig{ + IsAOE: true, + Aura: core.Aura{ + Label: "Astral Storm (Aura)", + }, + NumberOfTicks: 10, + TickLength: time.Second * 1, + AffectedByCastSpeed: true, + OnTick: func(sim *core.Simulation, target *core.Unit, _ *core.Dot) { + moonkin.AstralStormTickSpell.Cast(sim, target) + }, + }, + + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + spell.AOEDot().Apply(sim) + }, + }) +} diff --git a/sim/druid/balance/balance.go b/sim/druid/balance/balance.go index ded3f8b82c..59c506a3c8 100644 --- a/sim/druid/balance/balance.go +++ b/sim/druid/balance/balance.go @@ -3,10 +3,15 @@ package balance import ( "github.com/wowsims/mop/sim/core" "github.com/wowsims/mop/sim/core/proto" - "github.com/wowsims/mop/sim/core/stats" "github.com/wowsims/mop/sim/druid" ) +const ( + WrathBaseEnergyGain float64 = 15 + StarsurgeBaseEnergyGain float64 = 20 + StarfireBaseEnergyGain float64 = 20 +) + func RegisterBalanceDruid() { core.RegisterAgentFactory( proto.Player_BalanceDruid{}, @@ -29,10 +34,13 @@ func NewBalanceDruid(character *core.Character, options *proto.Player) *BalanceD selfBuffs := druid.SelfBuffs{} moonkin := &BalanceDruid{ - Druid: druid.New(character, druid.Moonkin, selfBuffs, options.TalentsString), - Options: balanceOptions.Options, + Druid: druid.New(character, druid.Moonkin, selfBuffs, options.TalentsString), + Options: balanceOptions.Options, + EclipseEnergyMap: make(EclipseEnergyMap), } + moonkin.registerTreants() + moonkin.SelfBuffs.InnervateTarget = &proto.UnitReference{} if balanceOptions.Options.ClassOptions.InnervateTarget != nil { moonkin.SelfBuffs.InnervateTarget = balanceOptions.Options.ClassOptions.InnervateTarget @@ -41,14 +49,26 @@ func NewBalanceDruid(character *core.Character, options *proto.Player) *BalanceD return moonkin } -type BalanceOnUseTrinket struct { - Cooldown *core.MajorCooldown - Stat stats.Stat -} - type BalanceDruid struct { *druid.Druid + eclipseEnergyBar Options *proto.BalanceDruid_Options + + EclipseEnergyMap EclipseEnergyMap + + AstralCommunion *druid.DruidSpell + AstralStorm *druid.DruidSpell + AstralStormTickSpell *druid.DruidSpell + CelestialAlignment *druid.DruidSpell + ChosenOfElune *druid.DruidSpell + Starfall *druid.DruidSpell + Starfire *druid.DruidSpell + Sunfire *druid.DruidSpell + Starsurge *druid.DruidSpell + + AstralInsight *core.Aura // Soul of the Forest + DreamOfCenarius *core.Aura + NaturesGrace *core.Aura } func (moonkin *BalanceDruid) GetDruid() *druid.Druid { @@ -58,53 +78,34 @@ func (moonkin *BalanceDruid) GetDruid() *druid.Druid { func (moonkin *BalanceDruid) Initialize() { moonkin.Druid.Initialize() + moonkin.EnableEclipseBar() + moonkin.RegisterEclipseAuras() + moonkin.RegisterEclipseEnergyGainAura() + + moonkin.RegisterBalancePassives() moonkin.RegisterBalanceSpells() - // if moonkin.OwlkinFrenzyAura != nil && moonkin.Options.OkfUptime > 0 { - // moonkin.Env.RegisterPreFinalizeEffect(func() { - // core.ApplyFixedUptimeAura(moonkin.OwlkinFrenzyAura, float64(moonkin.Options.OkfUptime), time.Second*5, 0) - // }) - // } + moonkin.ApplyGlyphs() } func (moonkin *BalanceDruid) ApplyTalents() { + moonkin.Druid.ApplyTalents() - // moonkin.EnableEclipseBar() - // moonkin.RegisterEclipseAuras() - // moonkin.RegisterEclipseEnergyGainAura() - - // Moonfury passive - moonkin.RegisterAura( - core.Aura{ - Label: "Moonfury", - Duration: core.NeverExpires, - ActionID: core.ActionID{ - SpellID: 16913, - }, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - }, - ) + moonkin.ApplyBalanceTalents() +} - moonkin.AddStaticMod(core.SpellModConfig{ - ClassMask: druid.DruidSpellWrath | druid.DruidSpellStarfire | druid.DruidSpellStarsurge | druid.DruidSpellStarfall | druid.DruidSpellDoT, - Kind: core.SpellMod_CritMultiplier_Flat, - FloatValue: 1.0, - }) - - moonkin.AddStaticMod(core.SpellModConfig{ - School: core.SpellSchoolArcane | core.SpellSchoolNature, - ClassMask: druid.DruidSpellsAll, - Kind: core.SpellMod_DamageDone_Pct, - FloatValue: 0.1, - }) - - // Apply druid talents - // moonkin.Druid.ApplyTalents() +func (moonkin *BalanceDruid) RegisterBalanceSpells() { + moonkin.registerSunfireSpell() + moonkin.registerStarfireSpell() + moonkin.registerStarsurgeSpell() + moonkin.registerStarfallSpell() + moonkin.registerAstralCommunionSpell() + moonkin.registerCelestialAlignmentSpell() + moonkin.registerAstralStormSpell() + moonkin.registerWildMushrooms() } func (moonkin *BalanceDruid) Reset(sim *core.Simulation) { + moonkin.eclipseEnergyBar.reset() moonkin.Druid.Reset(sim) - //moonkin.RebirthTiming = moonkin.Env.BaseDuration.Seconds() * sim.RandomFloat("Rebirth Timing") } diff --git a/sim/druid/balance/_balance_test.go b/sim/druid/balance/balance_test.go similarity index 52% rename from sim/druid/balance/_balance_test.go rename to sim/druid/balance/balance_test.go index 043f01df78..a91afa13f4 100644 --- a/sim/druid/balance/_balance_test.go +++ b/sim/druid/balance/balance_test.go @@ -17,31 +17,22 @@ func TestBalance(t *testing.T) { Class: proto.Class_ClassDruid, Race: proto.Race_RaceNightElf, - GearSet: core.GetGearSet("../../../ui/druid/balance/gear_sets", "t13"), + GearSet: core.GetGearSet("../../../ui/druid/balance/gear_sets", "preraid"), OtherGearSets: []core.GearSetCombo{ - core.GetGearSet("../../../ui/druid/balance/gear_sets", "t12"), + core.GetGearSet("../../../ui/druid/balance/gear_sets", "t14"), }, - Talents: StandardTalents, - Glyphs: StandardGlyphs, - Consumables: FullConsumesSpec, - SpecOptions: core.SpecOptionsCombo{Label: "Default", SpecOptions: PlayerOptionsBalance}, - Rotation: core.GetAplRotation("../../../ui/druid/balance/apls", "t13"), - OtherRotations: []core.RotationCombo{ - core.GetAplRotation("../../../ui/druid/balance/apls", "t12"), - }, - ItemFilter: ItemFilter, + Talents: StandardTalents, + Glyphs: StandardGlyphs, + Consumables: FullConsumesSpec, + SpecOptions: core.SpecOptionsCombo{Label: "Default", SpecOptions: PlayerOptionsBalance}, + Rotation: core.GetAplRotation("../../../ui/druid/balance/apls", "standard"), + OtherRotations: []core.RotationCombo{}, + ItemFilter: ItemFilter, })) } -var StandardTalents = "33230221123212111001-01-020331" -var StandardGlyphs = &proto.Glyphs{ - Major1: int32(proto.DruidMajorGlyph_GlyphOfStarfall), - Major2: int32(proto.DruidMajorGlyph_GlyphOfRebirth), - Major3: int32(proto.DruidMajorGlyph_GlyphOfMonsoon), - Minor1: int32(proto.DruidMinorGlyph_GlyphOfTyphoon), - Minor2: int32(proto.DruidMinorGlyph_GlyphOfUnburdenedRebirth), - Minor3: int32(proto.DruidMinorGlyph_GlyphOfMarkOfTheWild), -} +var StandardTalents = "113221" +var StandardGlyphs = &proto.Glyphs{} var PlayerOptionsBalance = &proto.Player_BalanceDruid{ BalanceDruid: &proto.BalanceDruid{ @@ -52,11 +43,12 @@ var PlayerOptionsBalance = &proto.Player_BalanceDruid{ } var FullConsumesSpec = &proto.ConsumesSpec{ - FlaskId: 58086, // Flask of the Draconic Mind - FoodId: 62671, // Severed Sagefish Head - PotId: 58091, // Volcanic Potion - PrepotId: 58091, // Volcanic Potion + FlaskId: 76085, // Flask of the Warm Sun + FoodId: 74650, // Mogu Fish Stew + PotId: 76093, // Potion of the Jade Serpent + PrepotId: 76093, // Potion of the Jade Serpent } + var ItemFilter = core.ItemFilter{ WeaponTypes: []proto.WeaponType{ proto.WeaponType_WeaponTypeDagger, diff --git a/sim/druid/balance/celestial_alignment.go b/sim/druid/balance/celestial_alignment.go new file mode 100644 index 0000000000..a884a24ea3 --- /dev/null +++ b/sim/druid/balance/celestial_alignment.go @@ -0,0 +1,66 @@ +package balance + +import ( + "time" + + "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/druid" +) + +func (moonkin *BalanceDruid) registerCelestialAlignmentSpell() { + actionID := core.ActionID{SpellID: 112071} + + celestialAlignmentAura := moonkin.RegisterAura(core.Aura{ + Label: "Celestial Alignment", + ActionID: actionID, + Duration: time.Second * 15, + OnGain: func(_ *core.Aura, sim *core.Simulation) { + moonkin.SuspendEclipseBar() + + // Activate both eclipse damage bonuses + moonkin.ActivateEclipse(LunarEclipse, sim) + moonkin.ActivateEclipse(SolarEclipse, sim) + }, + OnExpire: func(_ *core.Aura, sim *core.Simulation) { + moonkin.DeactivateEclipse(LunarEclipse, sim) + moonkin.DeactivateEclipse(SolarEclipse, sim) + + // Restore previous eclipse gain mask + moonkin.RestoreEclipseBar() + }, + OnCastComplete: func(_ *core.Aura, sim *core.Simulation, spell *core.Spell) { + if spell.ClassSpellMask == druid.DruidSpellMoonfire { + moonkin.Sunfire.Dot(spell.Unit.CurrentTarget).Apply(sim) + } + + if spell.ClassSpellMask == druid.DruidSpellSunfire { + moonkin.Moonfire.Dot(spell.Unit.CurrentTarget).Apply(sim) + } + }, + }) + + moonkin.CelestialAlignment = moonkin.RegisterSpell(druid.Humanoid|druid.Moonkin, core.SpellConfig{ + ActionID: actionID, + SpellSchool: core.SpellSchoolArcane, + Flags: core.SpellFlagAPL, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: 0, + }, + CD: core.Cooldown{ + Timer: moonkin.NewTimer(), + Duration: time.Minute * 3, + }, + }, + + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { + celestialAlignmentAura.Activate(sim) + }, + }) + + moonkin.AddMajorCooldown(core.MajorCooldown{ + Spell: moonkin.CelestialAlignment.Spell, + Type: core.CooldownTypeDPS, + }) +} diff --git a/sim/druid/_eclipse.go b/sim/druid/balance/eclipse.go similarity index 59% rename from sim/druid/_eclipse.go rename to sim/druid/balance/eclipse.go index 713cc23e20..4bb391672f 100644 --- a/sim/druid/_eclipse.go +++ b/sim/druid/balance/eclipse.go @@ -1,11 +1,11 @@ -package druid +package balance import ( - "fmt" "time" "github.com/wowsims/mop/sim/core" "github.com/wowsims/mop/sim/core/proto" + "github.com/wowsims/mop/sim/druid" ) /* @@ -15,6 +15,7 @@ import ( type EclipseEnergy byte const ( + NoEnergy EclipseEnergy = 0 SolarEnergy EclipseEnergy = 1 LunarEnergy EclipseEnergy = 2 SolarAndLunarEnergy = SolarEnergy | LunarEnergy @@ -30,17 +31,18 @@ const ( type EclipseCallback func(eclipse Eclipse, gained bool, sim *core.Simulation) type eclipseEnergyBar struct { - druid *Druid + moonkin *BalanceDruid lunarEnergy float64 solarEnergy float64 currentEclipse Eclipse gainMask EclipseEnergy // which energy the unit is currently allowed to accumulate + previousGainMask EclipseEnergy // used to restore gain mask after CA eclipseCallbacks []EclipseCallback eclipseTrigger func(spell *core.Spell) bool // used to deactivate eclipse spells } func (eb *eclipseEnergyBar) reset() { - if eb.druid == nil { + if eb.moonkin == nil { return } @@ -48,65 +50,122 @@ func (eb *eclipseEnergyBar) reset() { eb.solarEnergy = 0 // in neutral state we can gain both - eb.gainMask = SolarEnergy | LunarEnergy + eb.gainMask = SolarAndLunarEnergy eb.currentEclipse = NoEclipse } -func (druid *Druid) EnableEclipseBar() { - druid.eclipseEnergyBar = eclipseEnergyBar{ - druid: druid, - gainMask: SolarEnergy | LunarEnergy, - eclipseCallbacks: druid.eclipseEnergyBar.eclipseCallbacks, +func (eb *eclipseEnergyBar) resetWithMask(gainMask EclipseEnergy) { + eb.reset() + eb.gainMask = gainMask +} + +func (moonkin *BalanceDruid) EnableEclipseBar() { + moonkin.eclipseEnergyBar = eclipseEnergyBar{ + moonkin: moonkin, + gainMask: SolarAndLunarEnergy, + eclipseCallbacks: moonkin.eclipseEnergyBar.eclipseCallbacks, } } +func (moonkin *BalanceDruid) SuspendEclipseBar() { + moonkin.eclipseEnergyBar.previousGainMask = moonkin.eclipseEnergyBar.gainMask + moonkin.eclipseEnergyBar.resetWithMask(NoEnergy) +} + +func (moonkin *BalanceDruid) RestoreEclipseBar() { + moonkin.eclipseEnergyBar.resetWithMask(moonkin.eclipseEnergyBar.previousGainMask) +} + +func (moonkin *BalanceDruid) ActivateEclipse(eclipse Eclipse, sim *core.Simulation) { + moonkin.eclipseEnergyBar.invokeCallback(eclipse, true, sim) +} + +func (moonkin *BalanceDruid) DeactivateEclipse(eclipse Eclipse, sim *core.Simulation) { + moonkin.eclipseEnergyBar.invokeCallback(eclipse, false, sim) +} + func getEclipseMasteryBonus(masteryPoints float64) float64 { - return (16 + masteryPoints*2) / 100 + return (15 + masteryPoints*2) / 100 } -func (druid *Druid) RegisterEclipseAuras() { - baselineEclipsePct := 0.25 - initialEclipseMasteryBonus := getEclipseMasteryBonus(druid.GetMasteryPoints()) +func (moonkin *BalanceDruid) RegisterEclipseAuras() { + manaMetrics := moonkin.NewManaMetrics(core.ActionID{SpellID: 79577 /* Eclipse */}) + + baselineEclipsePct := 0.15 + initialEclipseMasteryBonus := getEclipseMasteryBonus(moonkin.GetMasteryPoints()) - lunarSpellMod := druid.AddDynamicMod(core.SpellModConfig{ + lunarSpellMod := moonkin.AddDynamicMod(core.SpellModConfig{ School: core.SpellSchoolArcane, + ProcMask: core.ProcMaskSpellDamage, Kind: core.SpellMod_DamageDone_Pct, FloatValue: baselineEclipsePct + initialEclipseMasteryBonus, }) - solarSpellMod := druid.AddDynamicMod(core.SpellModConfig{ + solarSpellMod := moonkin.AddDynamicMod(core.SpellModConfig{ School: core.SpellSchoolNature, + ProcMask: core.ProcMaskSpellDamage, Kind: core.SpellMod_DamageDone_Pct, FloatValue: baselineEclipsePct + initialEclipseMasteryBonus, }) - lunarEclipse := druid.RegisterAura(core.Aura{ + moonkin.AddOnMasteryStatChanged(func(sim *core.Simulation, oldMastery float64, newMastery float64) { + if !moonkin.IsInEclipse() { + return + } + + masteryBonusDiff := getEclipseMasteryBonus(newMastery) - getEclipseMasteryBonus(oldMastery) + + if lunarSpellMod.IsActive { + lunarSpellMod.UpdateFloatValue(lunarSpellMod.GetFloatValue() + core.MasteryRatingToMasteryPoints(masteryBonusDiff)) + } else if solarSpellMod.IsActive { + solarSpellMod.UpdateFloatValue(solarSpellMod.GetFloatValue() + core.MasteryRatingToMasteryPoints(masteryBonusDiff)) + } + }) + + lunarEclipse := moonkin.RegisterAura(core.Aura{ ActionID: core.ActionID{SpellID: 48518}, Label: "Eclipse (Lunar)", Duration: core.NeverExpires, OnGain: func(aura *core.Aura, sim *core.Simulation) { - lunarSpellMod.UpdateFloatValue(baselineEclipsePct + getEclipseMasteryBonus(druid.GetMasteryPoints())) + if moonkin.DreamOfCenarius.IsActive() { + baselineEclipsePct += 0.25 + moonkin.DreamOfCenarius.Deactivate(sim) + } + + lunarSpellMod.UpdateFloatValue(baselineEclipsePct + getEclipseMasteryBonus(moonkin.GetMasteryPoints())) lunarSpellMod.Activate() }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { + baselineEclipsePct = 0.15 lunarSpellMod.Deactivate() }, }) - solarEclipse := druid.RegisterAura(core.Aura{ + solarEclipse := moonkin.RegisterAura(core.Aura{ ActionID: core.ActionID{SpellID: 48517}, Label: "Eclipse (Solar)", Duration: core.NeverExpires, OnGain: func(aura *core.Aura, sim *core.Simulation) { - solarSpellMod.UpdateFloatValue(baselineEclipsePct + getEclipseMasteryBonus(druid.GetMasteryPoints())) + if moonkin.DreamOfCenarius.IsActive() { + baselineEclipsePct += 0.25 + moonkin.DreamOfCenarius.Deactivate(sim) + } + + solarSpellMod.UpdateFloatValue(baselineEclipsePct + getEclipseMasteryBonus(moonkin.GetMasteryPoints())) solarSpellMod.Activate() }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { + baselineEclipsePct = 0.15 solarSpellMod.Deactivate() }, }) - druid.AddEclipseCallback(func(eclipse Eclipse, gained bool, sim *core.Simulation) { + moonkin.AddEclipseCallback(func(eclipse Eclipse, gained bool, sim *core.Simulation) { + if gained { + // Moonkins are energized for 50% maximum mana every time they enter eclipse. + moonkin.AddMana(sim, moonkin.MaxMana()*0.5, manaMetrics) + } + if eclipse == LunarEclipse { if gained { lunarEclipse.Activate(sim) @@ -123,11 +182,11 @@ func (druid *Druid) RegisterEclipseAuras() { }) } -func (druid *Druid) RegisterEclipseEnergyGainAura() { - solarMetric := druid.NewSolarEnergyMetrics(core.ActionID{SpellID: 89265}) - lunarMetric := druid.NewLunarEnergyMetrics(core.ActionID{SpellID: 89265}) +func (moonkin *BalanceDruid) RegisterEclipseEnergyGainAura() { + solarMetric := moonkin.NewSolarEnergyMetrics(core.ActionID{SpellID: 89265}) + lunarMetric := moonkin.NewLunarEnergyMetrics(core.ActionID{SpellID: 89265}) - druid.RegisterAura(core.Aura{ + moonkin.RegisterAura(core.Aura{ ActionID: core.ActionID{SpellID: 89265}, Label: "Eclipse Energy", Duration: core.NeverExpires, @@ -135,76 +194,37 @@ func (druid *Druid) RegisterEclipseEnergyGainAura() { aura.Activate(sim) }, OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - var eclipseEnergyMultiplier float64 = 1.0 - - if druid.canEuphoriaProc(spell) && druid.hasEuphoriaProcced(sim) { - eclipseEnergyMultiplier = 2 - } - if energyGain := druid.GetSpellEclipseEnergy(spell.ClassSpellMask, druid.currentEclipse != NoEclipse); energyGain != 0 { + if energyGain := moonkin.GetSpellEclipseEnergy(spell.ClassSpellMask, moonkin.currentEclipse != NoEclipse); energyGain != 0 { switch spell.ClassSpellMask { - case DruidSpellStarfire: - druid.AddEclipseEnergy(energyGain*eclipseEnergyMultiplier, SolarEnergy, sim, solarMetric, spell) - case DruidSpellWrath: - druid.AddEclipseEnergy(energyGain*eclipseEnergyMultiplier, LunarEnergy, sim, lunarMetric, spell) - case DruidSpellStarsurge: - if druid.CanGainEnergy(SolarEnergy) { - druid.AddEclipseEnergy(energyGain, SolarEnergy, sim, solarMetric, spell) + case druid.DruidSpellStarfire: + moonkin.AddEclipseEnergy(energyGain, SolarEnergy, sim, solarMetric, spell) + case druid.DruidSpellWrath: + moonkin.AddEclipseEnergy(energyGain, LunarEnergy, sim, lunarMetric, spell) + case druid.DruidSpellStarsurge: + if moonkin.CanGainEnergy(SolarAndLunarEnergy) { + moonkin.AddEclipseEnergy(energyGain, LunarEnergy, sim, solarMetric, spell) } else { - druid.AddEclipseEnergy(energyGain, LunarEnergy, sim, lunarMetric, spell) + moonkin.AddEclipseEnergy(energyGain, SolarEnergy, sim, lunarMetric, spell) } - case DruidSpellMoonfire: // Moonfire (under the effect of Lunar Shower) - druid.AddEclipseEnergy(energyGain, SolarEnergy, sim, solarMetric, spell) - case DruidSpellSunfire: // Sunfire (under the effect of Lunar Shower) - druid.AddEclipseEnergy(energyGain, LunarEnergy, sim, lunarMetric, spell) } } }, OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { // chekc if trigger is supposed to handle spell hit, then clear - if druid.eclipseTrigger != nil && druid.eclipseTrigger(spell) { - druid.eclipseTrigger = nil + if moonkin.eclipseTrigger != nil && moonkin.eclipseTrigger(spell) { + moonkin.eclipseTrigger = nil } }, }) } -func (druid *Druid) hasEuphoriaProcced(sim *core.Simulation) bool { - return sim.Proc(0.12*float64(druid.Talents.Euphoria), fmt.Sprintf("Euphoria %d/2", druid.Talents.Euphoria)) +func (moonkin *BalanceDruid) HasEclipseBar() bool { + return moonkin.eclipseEnergyBar.moonkin != nil } -func (druid *Druid) canEuphoriaProc(spell *core.Spell) bool { - if druid.Talents.Euphoria == 0 { - return false - } - - if druid.currentEclipse != NoEclipse { - return false - } - - if spell.ClassSpellMask != DruidSpellStarfire && spell.ClassSpellMask != DruidSpellWrath { - return false - } - - if druid.Talents.Euphoria == 1 { - return true - } - - if druid.Talents.Euphoria == 2 { - if druid.CanGainEnergy(SolarEnergy) && druid.CurrentSolarEnergy() <= 35 && druid.CurrentLunarEnergy() == 0 { - return true - } - - if druid.CanGainEnergy(LunarEnergy) && druid.CurrentLunarEnergy() <= 35 && druid.CurrentSolarEnergy() == 0 { - return true - } - } - - return false -} - -func (druid *Druid) HasEclipseBar() bool { - return druid.eclipseEnergyBar.druid != nil +func (moonkin *BalanceDruid) IsInEclipse() bool { + return moonkin.currentEclipse != NoEclipse } func (eb *eclipseEnergyBar) AddEclipseCallback(callback EclipseCallback) { @@ -212,7 +232,7 @@ func (eb *eclipseEnergyBar) AddEclipseCallback(callback EclipseCallback) { } func (eb *eclipseEnergyBar) AddEclipseEnergy(amount float64, kind EclipseEnergy, sim *core.Simulation, metrics *core.ResourceMetrics, spell *core.Spell) { - if eb.druid == nil { + if eb.moonkin == nil { return } @@ -243,6 +263,11 @@ func (eb *eclipseEnergyBar) CanGainEnergy(kind EclipseEnergy) bool { return eb.gainMask&kind > 0 } +func (eb *eclipseEnergyBar) StoreGainMaskAndSuspend() { + eb.previousGainMask = eb.gainMask + eb.gainMask = NoEnergy +} + // spends the given amount of energy and returns how much energy remains // this might be added to the solar energy func (eb *eclipseEnergyBar) spendLunarEnergy(amount float64, sim *core.Simulation, metrics *core.ResourceMetrics, spell *core.Spell) float64 { @@ -256,7 +281,7 @@ func (eb *eclipseEnergyBar) spendLunarEnergy(amount float64, sim *core.Simulatio eb.lunarEnergy -= spend if sim.Log != nil { - eb.druid.Log(sim, "Spent %0.0f lunar energy from %s (%0.0f --> %0.0f) of %0.0f total.", spend, metrics.ActionID, old, eb.lunarEnergy, 100.0) + eb.moonkin.Log(sim, "Spent %0.0f lunar energy from %s (%0.0f --> %0.0f) of %0.0f total.", spend, metrics.ActionID, old, eb.lunarEnergy, 100.0) } if eb.lunarEnergy == 0 { @@ -281,7 +306,7 @@ func (eb *eclipseEnergyBar) addLunarEnergy(amount float64, sim *core.Simulation, eb.lunarEnergy += gain if sim.Log != nil { - eb.druid.Log(sim, "Gained %0.0f lunar energy from %s (%0.0f --> %0.0f) of %0.0f total.", gain, metrics.ActionID, old, eb.lunarEnergy, 100.0) + eb.moonkin.Log(sim, "Gained %0.0f lunar energy from %s (%0.0f --> %0.0f) of %0.0f total.", gain, metrics.ActionID, old, eb.lunarEnergy, 100.0) } if eb.lunarEnergy == 100 { @@ -305,7 +330,7 @@ func (eb *eclipseEnergyBar) SetEclipse(eclipse Eclipse, sim *core.Simulation, sp eb.invokeCallback(eclipse, true, sim) eb.currentEclipse = eclipse } else { - if spell.ClassSpellMask&DruidSpellWrath > 0 { + if spell.Matches(druid.DruidSpellWrath) { eb.eclipseTrigger = func(triggerSpell *core.Spell) bool { if spell.ClassSpellMask == triggerSpell.ClassSpellMask && !(triggerSpell.ProcMask&core.ProcMaskSpellProc > 0) { eb.invokeCallback(eb.currentEclipse, false, sim) @@ -352,7 +377,7 @@ func (eb *eclipseEnergyBar) spendSolarEnergy(amount float64, sim *core.Simulatio eb.solarEnergy -= spend if sim.Log != nil { - eb.druid.Log(sim, "Spent %0.0f solar energy from %s (%0.0f --> %0.0f) of %0.0f total.", spend, metrics.ActionID, old, eb.solarEnergy, 100.0) + eb.moonkin.Log(sim, "Spent %0.0f solar energy from %s (%0.0f --> %0.0f) of %0.0f total.", spend, metrics.ActionID, old, eb.solarEnergy, 100.0) } if eb.solarEnergy == 0 { @@ -377,7 +402,7 @@ func (eb *eclipseEnergyBar) addSolarEnergy(amount float64, sim *core.Simulation, eb.solarEnergy += gain if sim.Log != nil { - eb.druid.Log(sim, "Gained %0.0f solar energy from %s (%0.0f --> %0.0f) of %0.0f total.", gain, metrics.ActionID, old, eb.solarEnergy, 100.0) + eb.moonkin.Log(sim, "Gained %0.0f solar energy from %s (%0.0f --> %0.0f) of %0.0f total.", gain, metrics.ActionID, old, eb.solarEnergy, 100.0) } if eb.solarEnergy == 100 { @@ -387,10 +412,10 @@ func (eb *eclipseEnergyBar) addSolarEnergy(amount float64, sim *core.Simulation, metrics.AddEvent(amount, gain) } -func (unit *Druid) NewSolarEnergyMetrics(actionID core.ActionID) *core.ResourceMetrics { +func (unit *BalanceDruid) NewSolarEnergyMetrics(actionID core.ActionID) *core.ResourceMetrics { return unit.Metrics.NewResourceMetrics(actionID, proto.ResourceType_ResourceTypeSolarEnergy) } -func (unit *Druid) NewLunarEnergyMetrics(actionID core.ActionID) *core.ResourceMetrics { +func (unit *BalanceDruid) NewLunarEnergyMetrics(actionID core.ActionID) *core.ResourceMetrics { return unit.Metrics.NewResourceMetrics(actionID, proto.ResourceType_ResourceTypeLunarEnergy) } diff --git a/sim/druid/balance/eclipse_energy_map.go b/sim/druid/balance/eclipse_energy_map.go new file mode 100644 index 0000000000..f7f409ea96 --- /dev/null +++ b/sim/druid/balance/eclipse_energy_map.go @@ -0,0 +1,25 @@ +package balance + +type EclipseEnergyValues struct { + InEclipse float64 + NoEclipse float64 +} + +type EclipseEnergyMap = map[int64]EclipseEnergyValues + +func (moonkin *BalanceDruid) SetSpellEclipseEnergy(spellMask int64, inEclipseEnergy float64, noEclipseEnergy float64) { + moonkin.EclipseEnergyMap[spellMask] = EclipseEnergyValues{ + InEclipse: inEclipseEnergy, + NoEclipse: noEclipseEnergy, + } +} + +func (moonkin *BalanceDruid) GetSpellEclipseEnergy(spellMask int64, inEclipse bool) float64 { + energyValue := moonkin.EclipseEnergyMap[spellMask] + + if inEclipse { + return energyValue.InEclipse + } + + return energyValue.NoEclipse +} diff --git a/sim/druid/balance/passives.go b/sim/druid/balance/passives.go new file mode 100644 index 0000000000..2521072a07 --- /dev/null +++ b/sim/druid/balance/passives.go @@ -0,0 +1,199 @@ +package balance + +import ( + "time" + + "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/core/stats" + "github.com/wowsims/mop/sim/druid" +) + +func (moonkin *BalanceDruid) RegisterBalancePassives() { + moonkin.registerMoonkinForm() + moonkin.registerShootingStars() + moonkin.registerBalanceOfPower() + moonkin.registerEuphoria() + moonkin.registerOwlkinFrenzy() + moonkin.registerKillerInstinct() + moonkin.registerLeatherSpecialization() + moonkin.registerNaturalInsight() + moonkin.registerTotalEclipse() + moonkin.registerLunarShower() + moonkin.registerNaturesGrace() +} + +func (moonkin *BalanceDruid) registerMoonkinForm() { + moonkin.AddStaticMod(core.SpellModConfig{ + School: core.SpellSchoolArcane | core.SpellSchoolNature, + FloatValue: 0.2, + Kind: core.SpellMod_DamageDone_Pct, + }) + + moonkin.MultiplyStat(stats.Armor, 0.6) + + core.MakePermanent(moonkin.RegisterAura(core.Aura{ + Label: "Moonkin Form", + ActionID: core.ActionID{ + SpellID: 24858, + }, + })) + + core.MakePermanent(core.MoonkinAura(&moonkin.Unit)) +} + +func (moonkin *BalanceDruid) registerShootingStars() { + castTimeModConfig := core.SpellModConfig{ + ClassMask: druid.DruidSpellStarsurge, + Kind: core.SpellMod_CastTime_Pct, + FloatValue: -1, + } + + ssAura := moonkin.RegisterAura(core.Aura{ + Label: "Shooting Stars" + moonkin.Label, + ActionID: core.ActionID{SpellID: 93400}, + Duration: time.Second * 12, + OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { + if !spell.Matches(druid.DruidSpellStarsurge) { + return + } + + aura.Deactivate(sim) + }, + OnGain: func(_ *core.Aura, _ *core.Simulation) { + moonkin.Starsurge.CD.Reset() + }, + }).AttachSpellMod(castTimeModConfig) + + core.MakeProcTriggerAura(&moonkin.Unit, core.ProcTrigger{ + Name: "Shooting Stars Trigger" + moonkin.Label, + Callback: core.CallbackOnPeriodicDamageDealt, + Outcome: core.OutcomeCrit, + ProcChance: 0.3, + ClassSpellMask: druid.DruidSpellSunfireDoT | druid.DruidSpellMoonfireDoT, + Handler: func(sim *core.Simulation, _ *core.Spell, _ *core.SpellResult) { + ssAura.Activate(sim) + }, + }) + + // Keeping the logic below for when the nerf is reverted in the latest phase of MOP + + // ShootingStarsHandler540 := func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + // activeTargetCount := 0 + // baseProcChance := 0.3 + + // for _, target := range sim.Encounter.TargetUnits { + // dot := spell.Dot(target) + // if dot != nil && dot.IsActive() { + // activeTargetCount++ + // } + // } + + // procChance := baseProcChance * math.Sqrt(float64(activeTargetCount)) / float64(activeTargetCount) + + // if sim.Proc(procChance, "Shooting Stars") { + // ssAura.Activate(sim) + // } + // } +} + +func (moonkin *BalanceDruid) registerBalanceOfPower() { + moonkin.AddStat(stats.HitRating, -moonkin.GetBaseStats()[stats.Spirit]) + moonkin.AddStatDependency(stats.Spirit, stats.HitRating, 1) +} + +func (moonkin *BalanceDruid) registerNaturesGrace() { + moonkin.NaturesGrace = moonkin.RegisterAura(core.Aura{ + Label: "Nature's Grace", + ActionID: core.ActionID{SpellID: 16886}, + Duration: time.Second * 15, + OnGain: func(_ *core.Aura, sim *core.Simulation) { + moonkin.MultiplyCastSpeed(sim, 1.15) + }, + OnExpire: func(_ *core.Aura, sim *core.Simulation) { + moonkin.MultiplyCastSpeed(sim, 1 / 1.15) + }, + }) + + moonkin.AddEclipseCallback(func(_ Eclipse, gained bool, sim *core.Simulation) { + if gained { + moonkin.NaturesGrace.Activate(sim) + } + }) +} + +func (moonkin *BalanceDruid) registerEuphoria() { + moonkin.SetSpellEclipseEnergy(druid.DruidSpellWrath, WrathBaseEnergyGain, WrathBaseEnergyGain*2) + moonkin.SetSpellEclipseEnergy(druid.DruidSpellStarfire, StarfireBaseEnergyGain, StarfireBaseEnergyGain*2) + moonkin.SetSpellEclipseEnergy(druid.DruidSpellStarsurge, StarsurgeBaseEnergyGain, StarsurgeBaseEnergyGain*2) +} + +func (moonkin *BalanceDruid) registerOwlkinFrenzy() {} + +func (moonkin *BalanceDruid) registerKillerInstinct() {} + +func (moonkin *BalanceDruid) registerLeatherSpecialization() {} + +func (moonkin *BalanceDruid) registerNaturalInsight() { + moonkin.MultiplyStat(stats.Mana, 5) +} + +func (moonkin *BalanceDruid) registerTotalEclipse() {} + +func (moonkin *BalanceDruid) registerLunarShower() { + lunarShowerDmgMod := moonkin.AddDynamicMod(core.SpellModConfig{ + ClassMask: druid.DruidSpellMoonfire | druid.DruidSpellSunfire, + Kind: core.SpellMod_DamageDone_Pct, + }) + + lunarShowerResourceMod := moonkin.AddDynamicMod(core.SpellModConfig{ + ClassMask: druid.DruidSpellMoonfire | druid.DruidSpellSunfire, + Kind: core.SpellMod_PowerCost_Pct, + }) + + var lunarShowerAura = moonkin.RegisterAura(core.Aura{ + Label: "Lunar Shower", + Duration: time.Second * 3, + ActionID: core.ActionID{SpellID: 81192}, + MaxStacks: 3, + OnGain: func(_ *core.Aura, Race_RaceNightElf *core.Simulation) { + lunarShowerDmgMod.Activate() + lunarShowerResourceMod.Activate() + }, + OnStacksChange: func(_ *core.Aura, _ *core.Simulation, _, newStacks int32) { + lunarShowerDmgMod.UpdateFloatValue(float64(newStacks) * 0.45) + lunarShowerResourceMod.UpdateFloatValue(float64(newStacks) * -0.3) + }, + OnExpire: func(_ *core.Aura, _ *core.Simulation) { + lunarShowerDmgMod.Deactivate() + lunarShowerResourceMod.Deactivate() + }, + }) + + moonkin.RegisterAura(core.Aura{ + Label: "Lunar Shower Handler", + Duration: core.NeverExpires, + OnReset: func(aura *core.Aura, sim *core.Simulation) { + aura.Activate(sim) + }, + OnSpellHitDealt: func(_ *core.Aura, sim *core.Simulation, spell *core.Spell, _ *core.SpellResult) { + if !spell.Matches(druid.DruidSpellMoonfire | druid.DruidSpellSunfire) { + return + } + + // does not proc off procs + if spell.ProcMask.Matches(core.ProcMaskProc) { + return + } + + if lunarShowerAura.IsActive() { + if lunarShowerAura.GetStacks() < 3 { + lunarShowerAura.AddStack(sim) + lunarShowerAura.Refresh(sim) + } + } else { + lunarShowerAura.Activate(sim) + lunarShowerAura.SetStacks(sim, 1) + } + }, + }) +} diff --git a/sim/druid/balance/starfall.go b/sim/druid/balance/starfall.go new file mode 100644 index 0000000000..2c5f20504f --- /dev/null +++ b/sim/druid/balance/starfall.go @@ -0,0 +1,91 @@ +package balance + +import ( + "time" + + "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/druid" +) + +const ( + StarfallBonusCoeff = 0.364 + StarfallCoeff = 0.58 + StarfallVariance = 0.15 +) + +func (moonkin *BalanceDruid) registerStarfallSpell() { + + numberOfTicks := core.TernaryInt32(moonkin.Env.GetNumTargets() > 1, 20, 10) + tickLength := time.Second + + starfallTickSpell := moonkin.RegisterSpell(druid.Humanoid|druid.Moonkin, core.SpellConfig{ + ActionID: core.ActionID{SpellID: 50286}, + SpellSchool: core.SpellSchoolArcane, + ProcMask: core.ProcMaskSpellDamage, + ClassSpellMask: druid.DruidSpellStarfall, + Flags: core.SpellFlagPassiveSpell, + + DamageMultiplier: 1, + CritMultiplier: moonkin.DefaultCritMultiplier(), + ThreatMultiplier: 1, + BonusCoefficient: StarfallBonusCoeff, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + baseDamage := moonkin.CalcAndRollDamageRange(sim, StarfallCoeff, StarfallVariance) + spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) + }, + }) + + moonkin.Starfall = moonkin.RegisterSpell(druid.Humanoid|druid.Moonkin, core.SpellConfig{ + ActionID: core.ActionID{SpellID: 48505}, + SpellSchool: core.SpellSchoolArcane, + ProcMask: core.ProcMaskSpellProc, + Flags: core.SpellFlagAPL, + + RelatedSelfBuff: moonkin.GetOrRegisterAura(core.Aura{ + Label: "Starfall", + ActionID: core.ActionID{SpellID: 48505}, + Duration: time.Second * 10, + }), + + ManaCost: core.ManaCostOptions{ + BaseCostPercent: 32.6, + }, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + CD: core.Cooldown{ + Timer: moonkin.NewTimer(), + Duration: time.Second * 90, + }, + }, + + Dot: core.DotConfig{ + Aura: core.Aura{ + Label: "Starfall", + }, + NumberOfTicks: numberOfTicks, + TickLength: tickLength, + OnTick: func(sim *core.Simulation, target *core.Unit, _ *core.Dot) { + starfallTickSpell.Cast(sim, target) + }, + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + spell.RelatedSelfBuff.Activate(sim) + + result := spell.CalcAndDealOutcome(sim, target, spell.OutcomeMagicHit) + if result.Landed() { + spell.Dot(target).Apply(sim) + } + }, + }) + + moonkin.AddEclipseCallback(func(eclipse Eclipse, gained bool, _ *core.Simulation) { + if gained && eclipse == LunarEclipse { + moonkin.Starfall.CD.Reset() + } + }) +} diff --git a/sim/druid/balance/starfire.go b/sim/druid/balance/starfire.go new file mode 100644 index 0000000000..e49862cd5c --- /dev/null +++ b/sim/druid/balance/starfire.go @@ -0,0 +1,48 @@ +package balance + +import ( + "time" + + "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/druid" +) + +const ( + StarfireBonusCoeff = 2.166 + StarfireCoeff = 4.456 + StarfireVariance = 0.25 +) + +func (moonkin *BalanceDruid) registerStarfireSpell() { + moonkin.Starfire = moonkin.RegisterSpell(druid.Humanoid|druid.Moonkin, core.SpellConfig{ + ActionID: core.ActionID{SpellID: 2912}, + SpellSchool: core.SpellSchoolArcane, + ProcMask: core.ProcMaskSpellDamage, + ClassSpellMask: druid.DruidSpellStarfire, + Flags: core.SpellFlagAPL, + + ManaCost: core.ManaCostOptions{ + BaseCostPercent: 15.5, + }, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + CastTime: time.Millisecond * 2700, + }, + }, + + BonusCoefficient: StarfireBonusCoeff, + + DamageMultiplier: 1, + + CritMultiplier: moonkin.DefaultCritMultiplier(), + + ThreatMultiplier: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + baseDamage := moonkin.CalcAndRollDamageRange(sim, StarfireCoeff, StarfireVariance) + spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) + }, + }) +} diff --git a/sim/druid/balance/starsurge.go b/sim/druid/balance/starsurge.go new file mode 100644 index 0000000000..ff580cd944 --- /dev/null +++ b/sim/druid/balance/starsurge.go @@ -0,0 +1,58 @@ +package balance + +import ( + "time" + + "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/druid" +) + +const ( + StarsurgeBonusCoeff = 2.388 + StarsurgeCoeff = 4.54 + StarsurgeVariance = 0.319 +) + +func (moonkin *BalanceDruid) registerStarsurgeSpell() { + moonkin.Starsurge = moonkin.RegisterSpell(druid.Humanoid|druid.Moonkin, core.SpellConfig{ + ActionID: core.ActionID{SpellID: 78674}, + SpellSchool: core.SpellSchoolArcane | core.SpellSchoolNature, + ProcMask: core.ProcMaskSpellDamage, + ClassSpellMask: druid.DruidSpellStarsurge, + Flags: core.SpellFlagAPL, + MissileSpeed: 20, + + DamageMultiplier: 1, + DamageMultiplierAdditive: 1, + CritMultiplier: moonkin.DefaultCritMultiplier(), + + ManaCost: core.ManaCostOptions{ + BaseCostPercent: 15.5, + }, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + CastTime: time.Second * 2, + }, + CD: core.Cooldown{ + Timer: moonkin.NewTimer(), + Duration: time.Second * 15, + }, + }, + + ThreatMultiplier: 1, + + BonusCoefficient: StarsurgeBonusCoeff, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + + baseDamage := moonkin.CalcAndRollDamageRange(sim, StarsurgeCoeff, StarsurgeVariance) + result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) + + spell.WaitTravelTime(sim, func(sim *core.Simulation) { + spell.DealDamage(sim, result) + }) + }, + }) +} diff --git a/sim/druid/balance/sunfire.go b/sim/druid/balance/sunfire.go new file mode 100644 index 0000000000..419a7a127a --- /dev/null +++ b/sim/druid/balance/sunfire.go @@ -0,0 +1,107 @@ +package balance + +import ( + "time" + + "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/druid" +) + +const ( + SunfireBonusCoeff = 0.24 + + SunfireDotCoeff = 0.24 + + SunfireImpactCoeff = 0.571 + SunfireImpactVariance = 0.2 +) + +func (moonkin *BalanceDruid) registerSunfireSpell() { + moonkin.registerSunfireImpactSpell() + moonkin.registerSunfireDoTSpell() +} + +func (moonkin *BalanceDruid) registerSunfireDoTSpell() { + moonkin.Sunfire.RelatedDotSpell = moonkin.Unit.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: 93402}.WithTag(1), + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskSpellDamage, + ClassSpellMask: druid.DruidSpellSunfireDoT, + Flags: core.SpellFlagPassiveSpell, + + DamageMultiplier: 1, + CritMultiplier: moonkin.DefaultCritMultiplier(), + ThreatMultiplier: 1, + + Dot: core.DotConfig{ + Aura: core.Aura{ + Label: "Sunfire", + OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if result.Landed() && result.DidCrit() && spell.Matches(druid.DruidSpellWrath|druid.DruidSpellStarsurge) { + oldDuration := moonkin.Sunfire.Dot(aura.Unit).RemainingDuration(sim) + moonkin.Sunfire.Dot(aura.Unit).AddTick() + + if sim.Log != nil { + moonkin.Log(sim, "[DEBUG]: %s extended %s. Old Duration: %0.0f, new duration: %0.0f.", spell.ActionID, moonkin.Sunfire.ActionID, oldDuration.Seconds(), moonkin.Sunfire.Dot(aura.Unit).RemainingDuration(sim).Seconds()) + } + } + }, + }, + NumberOfTicks: 7, + TickLength: time.Second * 2, + AffectedByCastSpeed: true, + BonusCoefficient: SunfireBonusCoeff, + + OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { + dot.Snapshot(target, moonkin.CalcScalingSpellDmg(SunfireDotCoeff)) + }, + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) + }, + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + result := spell.CalcOutcome(sim, target, spell.OutcomeAlwaysHitNoHitCounter) + + spell.Dot(target).Apply(sim) + spell.DealOutcome(sim, result) + }, + }) +} + +func (moonkin *BalanceDruid) registerSunfireImpactSpell() { + + moonkin.Sunfire = moonkin.RegisterSpell(druid.Humanoid|druid.Moonkin, core.SpellConfig{ + ActionID: core.ActionID{SpellID: 93402}, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskSpellDamage, + ClassSpellMask: druid.DruidSpellSunfire, + Flags: core.SpellFlagAPL, + + ManaCost: core.ManaCostOptions{ + BaseCostPercent: 9, + }, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + }, + + DamageMultiplier: 1, + + CritMultiplier: moonkin.DefaultCritMultiplier(), + ThreatMultiplier: 1, + BonusCoefficient: SunfireBonusCoeff, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + baseDamage := moonkin.CalcAndRollDamageRange(sim, SunfireImpactCoeff, SunfireImpactVariance) + result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) + + if result.Landed() { + moonkin.Sunfire.RelatedDotSpell.Cast(sim, target) + } + + spell.DealDamage(sim, result) + }, + }) +} diff --git a/sim/druid/balance/talents.go b/sim/druid/balance/talents.go new file mode 100644 index 0000000000..eb25990fd7 --- /dev/null +++ b/sim/druid/balance/talents.go @@ -0,0 +1,121 @@ +package balance + +import ( + "time" + + "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/druid" +) + +func (moonkin *BalanceDruid) ApplyBalanceTalents() { + moonkin.registerIncarnation() + moonkin.registerDreamOfCenarius() + moonkin.registerSoulOfTheForest() +} + +func (moonkin *BalanceDruid) registerIncarnation() { + if !moonkin.Talents.Incarnation { + return + } + + actionID := core.ActionID{SpellID: 102560} + + incarnationSpellMod := moonkin.AddDynamicMod(core.SpellModConfig{ + School: core.SpellSchoolArcane | core.SpellSchoolNature, + Kind: core.SpellMod_DamageDone_Pct, + FloatValue: 0.25, + }) + + incarnationAura := moonkin.RegisterAura(core.Aura{ + Label: "Incarnation: Chosen of Elune", + ActionID: actionID, + Duration: time.Second * 30, + OnGain: func(_ *core.Aura, _ *core.Simulation) { + // Only apply the damage bonus when in Eclipse + if moonkin.IsInEclipse() { + incarnationSpellMod.Activate() + } + }, + OnExpire: func(_ *core.Aura, _ *core.Simulation) { + incarnationSpellMod.Deactivate() + }, + }) + + // Add Eclipse callback to apply/remove damage bonus when entering/exiting Eclipse + moonkin.AddEclipseCallback(func(_ Eclipse, gained bool, _ *core.Simulation) { + if incarnationAura.IsActive() { + if gained { + incarnationSpellMod.Activate() + } else { + incarnationSpellMod.Deactivate() + } + } + }) + + moonkin.ChosenOfElune = moonkin.RegisterSpell(druid.Humanoid|druid.Moonkin, core.SpellConfig{ + ActionID: actionID, + Flags: core.SpellFlagAPL, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + CD: core.Cooldown{ + Timer: moonkin.NewTimer(), + Duration: time.Minute * 3, + }, + }, + + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { + incarnationAura.Activate(sim) + }, + }) + + moonkin.AddMajorCooldown(core.MajorCooldown{ + Spell: moonkin.ChosenOfElune.Spell, + Type: core.CooldownTypeDPS, + }) +} + +func (moonkin *BalanceDruid) registerDreamOfCenarius() { + if !moonkin.Talents.DreamOfCenarius { + return + } + + moonkin.DreamOfCenarius = moonkin.RegisterAura(core.Aura{ + Label: "Dream of Cenarius", + ActionID: core.ActionID{SpellID: 145151}, + Duration: time.Second * 30, + }) + + core.MakeProcTriggerAura(&moonkin.Unit, core.ProcTrigger{ + Name: "Dream of Cenarius Trigger", + Callback: core.CallbackOnCastComplete, + ClassSpellMask: druid.DruidSpellHealingTouch, + Handler: func(sim *core.Simulation, _ *core.Spell, _ *core.SpellResult) { + moonkin.DreamOfCenarius.Activate(sim) + }, + }) +} + +func (moonkin *BalanceDruid) registerSoulOfTheForest() { + if !moonkin.Talents.SoulOfTheForest { + return + } + + moonkin.AstralInsight = moonkin.RegisterAura(core.Aura{ + Label: "Astral Insight (SotF)", + ActionID: core.ActionID{SpellID: 145138}, + Duration: time.Second * 30, + }) + + core.MakeProcTriggerAura(&moonkin.Unit, core.ProcTrigger{ + Name: "Astral Insight (SotF) Trigger", + Callback: core.CallbackOnCastComplete, + ClassSpellMask: druid.DruidSpellWrath | druid.DruidSpellStarfire | druid.DruidSpellStarsurge, + ProcChance: 0.08, + Handler: func(sim *core.Simulation, _ *core.Spell, _ *core.SpellResult) { + moonkin.AstralInsight.Activate(sim) + }, + }) +} diff --git a/sim/druid/balance/treants.go b/sim/druid/balance/treants.go new file mode 100644 index 0000000000..0d1bacbea2 --- /dev/null +++ b/sim/druid/balance/treants.go @@ -0,0 +1,97 @@ +package balance + +import ( + "time" + + "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/core/stats" + "github.com/wowsims/mop/sim/druid" +) + +type BalanceTreant struct { + *druid.DefaultTreantImpl + + Wrath *core.Spell +} + +const ( + TreantWrathBonusCoeff = 0.375 + TreantWrathCoeff = 1.875 + TreantWrathVariance = 0.12 +) + +func (moonkin *BalanceDruid) newTreant() *BalanceTreant { + treant := &BalanceTreant{ + DefaultTreantImpl: moonkin.NewDefaultTreant(druid.TreantConfig{ + StatInheritance: func(ownerStats stats.Stats) stats.Stats { + combinedHitExp := 0.5 * (ownerStats[stats.HitRating] + ownerStats[stats.ExpertiseRating]) + + return stats.Stats{ + stats.Health: 0.4 * ownerStats[stats.Health], + stats.HitRating: combinedHitExp, + stats.ExpertiseRating: combinedHitExp, + stats.SpellCritPercent: ownerStats[stats.SpellCritPercent], + stats.SpellPower: ownerStats[stats.SpellPower], + stats.HasteRating: ownerStats[stats.HasteRating], + } + }, + + EnableAutos: false, + }), + } + + treant.PseudoStats.DamageDealtMultiplier *= 1.091 + moonkin.AddPet(treant) + + return treant +} + +func (moonkin *BalanceDruid) registerTreants() { + for idx := range moonkin.Treants { + moonkin.Treants[idx] = moonkin.newTreant() + } +} + +func (treant *BalanceTreant) registerWrathSpell() { + treant.Wrath = treant.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: 113769}, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskSpellDamage, + MissileSpeed: 20, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: time.Millisecond * 500, + CastTime: time.Millisecond * 2500, + }, + }, + + BonusCoefficient: TreantWrathBonusCoeff, + + DamageMultiplier: 1, + + CritMultiplier: treant.DefaultCritMultiplier(), + + ThreatMultiplier: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + baseDamage := treant.CalcAndRollDamageRange(sim, TreantWrathCoeff, TreantWrathVariance) + result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) + + spell.WaitTravelTime(sim, func(sim *core.Simulation) { + spell.DealDamage(sim, result) + }) + }, + }) +} + +func (treant *BalanceTreant) Initialize() { + treant.registerWrathSpell() +} + +func (treant *BalanceTreant) ExecuteCustomRotation(sim *core.Simulation) { + if treant.Wrath.CanCast(sim, treant.CurrentTarget) { + treant.Wrath.Cast(sim, treant.CurrentTarget) + return + } +} diff --git a/sim/druid/wild_mushrooms.go b/sim/druid/balance/wild_mushrooms.go similarity index 53% rename from sim/druid/wild_mushrooms.go rename to sim/druid/balance/wild_mushrooms.go index 1e92091267..94f2f0044c 100644 --- a/sim/druid/wild_mushrooms.go +++ b/sim/druid/balance/wild_mushrooms.go @@ -1,22 +1,28 @@ -package druid +package balance import ( "time" "github.com/wowsims/mop/sim/core" - "github.com/wowsims/mop/sim/core/proto" + "github.com/wowsims/mop/sim/druid" ) -func (druid *Druid) registerWildMushrooms() { +const ( + WildMushroomsBonusCoeff = 0.349 + WildMushroomsCoeff = 0.295 + WildMushroomsVariance = 0.19 +) + +func (moonkin *BalanceDruid) registerWildMushrooms() { - wildMushroomsStackAura := druid.GetOrRegisterAura(core.Aura{ - Label: "Wild Mushroom Stacks", + wildMushroomsStackAura := moonkin.GetOrRegisterAura(core.Aura{ + Label: "Wild Mushrooms (Tracker)", ActionID: core.ActionID{SpellID: 88747}, Duration: core.NeverExpires, MaxStacks: 3, }) - druid.WildMushrooms = druid.RegisterSpell(Humanoid|Moonkin, core.SpellConfig{ + moonkin.WildMushrooms = moonkin.RegisterSpell(druid.Humanoid|druid.Moonkin, core.SpellConfig{ ActionID: core.ActionID{SpellID: 88747}, Flags: core.SpellFlagAPL, @@ -35,44 +41,43 @@ func (druid *Druid) registerWildMushrooms() { }, }) - wildMushroomsDamage := druid.RegisterSpell(Humanoid|Moonkin, core.SpellConfig{ + wildMushroomsDamage := moonkin.RegisterSpell(druid.Humanoid|druid.Moonkin, core.SpellConfig{ ActionID: core.ActionID{SpellID: 78777}, SpellSchool: core.SpellSchoolNature, Flags: core.SpellFlagAoE | core.SpellFlagPassiveSpell, ProcMask: core.ProcMaskSpellDamage, DamageMultiplier: 1, ThreatMultiplier: 1, - ClassSpellMask: DruidSpellWildMushroomDetonate, - CritMultiplier: druid.DefaultCritMultiplier(), - BonusCoefficient: 0.6032, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - min, max := core.CalcScalingSpellEffectVarianceMinMax(proto.Class_ClassDruid, 0.9464, 0.19) - baseDamage := sim.Roll(min, max) + ClassSpellMask: druid.DruidSpellWildMushroomDetonate, + CritMultiplier: moonkin.DefaultCritMultiplier(), + BonusCoefficient: WildMushroomsBonusCoeff, + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { for _, aoeTarget := range sim.Encounter.TargetUnits { - spell.CalcAndDealDamage(sim, aoeTarget, baseDamage, spell.OutcomeMagicHitAndCrit) + damage := moonkin.CalcAndRollDamageRange(sim, WildMushroomsCoeff, WildMushroomsVariance) + spell.CalcAndDealDamage(sim, aoeTarget, damage, spell.OutcomeMagicHitAndCrit) } }, }) - druid.WildMushroomsDetonate = druid.RegisterSpell(Humanoid|Moonkin, core.SpellConfig{ + moonkin.WildMushroomsDetonate = moonkin.RegisterSpell(druid.Humanoid|druid.Moonkin, core.SpellConfig{ ActionID: core.ActionID{SpellID: 88751}, SpellSchool: core.SpellSchoolNature, ProcMask: core.ProcMaskSpellDamage, - ClassSpellMask: DruidSpellWildMushroomDetonate, - Flags: core.SpellFlagAPL | SpellFlagOmenTrigger | core.SpellFlagPassiveSpell, + ClassSpellMask: druid.DruidSpellWildMushroomDetonate, + Flags: core.SpellFlagAPL | core.SpellFlagPassiveSpell, Cast: core.CastConfig{ CD: core.Cooldown{ - Timer: druid.NewTimer(), + Timer: moonkin.NewTimer(), Duration: time.Second * 10, }, }, DamageMultiplier: 1, ThreatMultiplier: 1, - CritMultiplier: druid.DefaultCritMultiplier(), - BonusCoefficient: 0.6032, + CritMultiplier: moonkin.DefaultCritMultiplier(), + BonusCoefficient: WildMushroomsBonusCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { diff --git a/sim/druid/burning_treant.go b/sim/druid/burning_treant.go deleted file mode 100644 index a6fa01248c..0000000000 --- a/sim/druid/burning_treant.go +++ /dev/null @@ -1,89 +0,0 @@ -package druid - -import ( - "time" - - "github.com/wowsims/mop/sim/core" - "github.com/wowsims/mop/sim/core/stats" -) - -type BurningTreant struct { - core.Pet - - owner *Druid - - Fireseed *core.Spell -} - -func (druid *Druid) NewBurningTreant() *BurningTreant { - baseStats := stats.Stats{stats.SpellCritPercent: 0} - - statInheritance := func(ownerStats stats.Stats) stats.Stats { - return stats.Stats{ - stats.SpellHitPercent: ownerStats[stats.SpellHitPercent], - } - } - - burningTreant := &BurningTreant{ - Pet: core.NewPet(core.PetConfig{ - Name: "Burning Treant", - Owner: &druid.Character, - BaseStats: baseStats, - StatInheritance: statInheritance, - EnabledOnStart: false, - IsGuardian: true, - }), - owner: druid, - } - - druid.AddPet(burningTreant) - return burningTreant -} - -func (treant *BurningTreant) GetPet() *core.Pet { - return &treant.Pet -} - -func (treant *BurningTreant) Initialize() { - treant.registerFireseedSpell() -} - -func (treant *BurningTreant) Reset(_ *core.Simulation) { -} - -func (treant *BurningTreant) ExecuteCustomRotation(sim *core.Simulation) { - if treant.Fireseed.CanCast(sim, treant.CurrentTarget) { - treant.Fireseed.Cast(sim, treant.CurrentTarget) - delay := time.Duration(sim.RollWithLabel(250.0, 1000.0, "Fireseed cast delay")) * time.Millisecond - treant.WaitUntil(sim, treant.NextGCDAt()+delay) - return - } -} - -func (treant *BurningTreant) registerFireseedSpell() { - treant.Fireseed = treant.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 99026}, - SpellSchool: core.SpellSchoolFire, - ProcMask: core.ProcMaskSpellDamage, - MissileSpeed: 24, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: 0, - CastTime: time.Second * 2, - }, - }, - - DamageMultiplier: 1, - CritMultiplier: treant.DefaultCritMultiplier(), - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := sim.Roll(5192, 6035) - result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - spell.WaitTravelTime(sim, func(sim *core.Simulation) { - spell.DealDamage(sim, result) - }) - }, - }) -} diff --git a/sim/druid/druid.go b/sim/druid/druid.go index 8b856f7034..fa279ed2c8 100644 --- a/sim/druid/druid.go +++ b/sim/druid/druid.go @@ -8,11 +8,6 @@ import ( "github.com/wowsims/mop/sim/core/stats" ) -const ( - SpellFlagNaturesGrace = core.SpellFlagAgentReserved1 - SpellFlagOmenTrigger = core.SpellFlagAgentReserved2 -) - type Druid struct { core.Character SelfBuffs @@ -25,8 +20,6 @@ type Druid struct { Treants TreantAgents - EclipseEnergyMap EclipseEnergyMap - RebirthUsed bool RebirthTiming float64 BleedsActive int @@ -42,15 +35,16 @@ type Druid struct { FerociousBite *DruidSpell ForceOfNature *DruidSpell FrenziedRegeneration *DruidSpell + HealingTouch *DruidSpell Hurricane *DruidSpell HurricaneTickSpell *DruidSpell - InsectSwarm *DruidSpell Lacerate *DruidSpell MangleBear *DruidSpell MangleCat *DruidSpell Maul *DruidSpell MightOfUrsoc *DruidSpell Moonfire *DruidSpell + NaturesSwiftness *DruidSpell Prowl *DruidSpell Rebirth *DruidSpell Rake *DruidSpell @@ -58,10 +52,6 @@ type Druid struct { Rip *DruidSpell SavageRoar *DruidSpell Shred *DruidSpell - Starfire *DruidSpell - Starfall *DruidSpell - Starsurge *DruidSpell - Sunfire *DruidSpell SurvivalInstincts *DruidSpell SwipeBear *DruidSpell SwipeCat *DruidSpell @@ -87,18 +77,14 @@ type Druid struct { FrenziedRegenerationAura *core.Aura LunarEclipseProcAura *core.Aura MightOfUrsocAura *core.Aura - NaturesGraceProcAura *core.Aura OwlkinFrenzyAura *core.Aura ProwlAura *core.Aura - SolarEclipseProcAura *core.Aura SurvivalInstinctsAura *core.Aura SavageRoarDurationTable [6]time.Duration ProcOoc func(sim *core.Simulation) - ExtendingMoonfireStacks int - form DruidForm disabledMCDs []*core.MajorCooldown @@ -110,36 +96,19 @@ type Druid struct { T13Feral4pBonus *core.Aura } -const ( - WrathBaseEnergyGain float64 = 13 + 1.0/3 - StarsurgeBaseEnergyGain float64 = 15 - StarfireBaseEnergyGain float64 = 20 - MoonfireBaseEnergyGain float64 = 0 - SunfireBaseEnergyGain float64 = 0 - - MoonfireLunarShowerEnergyGain float64 = MoonfireBaseEnergyGain + 8 - SunfireLunarShowerEnergyGain float64 = SunfireBaseEnergyGain + 8 - - Wrath4PT12EnergyGain float64 = WrathBaseEnergyGain + 3 - Starfire4PT12EnergyGain float64 = StarfireBaseEnergyGain + 5 -) - const ( DruidSpellFlagNone int64 = 0 DruidSpellBarkskin int64 = 1 << iota - DruidSpellCyclone - DruidSpellEntanglingRoots DruidSpellFearieFire - DruidSpellHibernate DruidSpellHurricane + DruidSpellAstralStorm + DruidSpellAstralCommunion DruidSpellInnervate - DruidSpellInsectSwarm DruidSpellMangleBear DruidSpellMangleCat DruidSpellMaul DruidSpellMoonfire DruidSpellMoonfireDoT - DruidSpellNaturesGrasp DruidSpellRavage DruidSpellShred DruidSpellStarfall @@ -147,8 +116,6 @@ const ( DruidSpellStarsurge DruidSpellSunfire DruidSpellSunfireDoT - DruidSpellThorns - DruidSpellTyphoon DruidSpellWildMushroom DruidSpellWildMushroomDetonate DruidSpellWrath @@ -164,15 +131,16 @@ const ( DruidSpellWildGrowth DruidSpellLast - DruidSpellsAll = DruidSpellLast<<1 - 1 - DruidSpellDoT = DruidSpellInsectSwarm | DruidSpellMoonfireDoT | DruidSpellSunfireDoT - DruidSpellHoT = DruidSpellRejuvenation | DruidSpellLifebloom | DruidSpellRegrowth | DruidSpellWildGrowth - DruidSpellInstant = DruidSpellBarkskin | DruidSpellInsectSwarm | DruidSpellMoonfire | DruidSpellStarfall | DruidSpellSunfire | DruidSpellFearieFire | DruidSpellBarkskin - DruidSpellMangle = DruidSpellMangleBear | DruidSpellMangleCat - DruidArcaneSpells = DruidSpellMoonfire | DruidSpellMoonfireDoT | DruidSpellStarfire | DruidSpellStarsurge | DruidSpellStarfall - DruidNatureSpells = DruidSpellWrath | DruidSpellInsectSwarm | DruidSpellStarsurge | DruidSpellSunfire | DruidSpellSunfireDoT | DruidSpellTyphoon | DruidSpellHurricane - DruidHealingSpells = DruidSpellHealingTouch | DruidSpellRegrowth | DruidSpellRejuvenation | DruidSpellLifebloom | DruidSpellNourish | DruidSpellSwiftmend - DruidDamagingSpells = DruidArcaneSpells | DruidNatureSpells + DruidSpellsAll = DruidSpellLast<<1 - 1 + DruidSpellDoT = DruidSpellMoonfireDoT | DruidSpellSunfireDoT + DruidSpellHoT = DruidSpellRejuvenation | DruidSpellLifebloom | DruidSpellRegrowth | DruidSpellWildGrowth + DruidSpellInstant = DruidSpellBarkskin | DruidSpellMoonfire | DruidSpellStarfall | DruidSpellSunfire | DruidSpellFearieFire | DruidSpellBarkskin + DruidSpellMangle = DruidSpellMangleBear | DruidSpellMangleCat + DruidArcaneSpells = DruidSpellMoonfire | DruidSpellMoonfireDoT | DruidSpellStarfire | DruidSpellStarsurge | DruidSpellStarfall + DruidNatureSpells = DruidSpellWrath | DruidSpellStarsurge | DruidSpellSunfire | DruidSpellSunfireDoT | DruidSpellHurricane + DruidHealingNonInstantSpells = DruidSpellHealingTouch | DruidSpellRegrowth | DruidSpellNourish + DruidHealingSpells = DruidHealingNonInstantSpells | DruidSpellRejuvenation | DruidSpellLifebloom | DruidSpellSwiftmend + DruidDamagingSpells = DruidArcaneSpells | DruidNatureSpells ) type SelfBuffs struct { @@ -195,10 +163,6 @@ func (druid *Druid) GetCharacter() *core.Character { // raidBuffs.MarkOfTheWild = true // } -func (druid *Druid) BalanceCritMultiplier() float64 { - return druid.CritMultiplier(1, 0) -} - func (druid *Druid) HasMajorGlyph(glyph proto.DruidMajorGlyph) bool { return druid.HasGlyph(int32(glyph)) } @@ -257,24 +221,22 @@ func (druid *Druid) Initialize() { return core.WeakenedBlowsAura(target) }) - druid.registerFaerieFireSpell() - // druid.registerRebirthSpell() - // druid.registerInnervateCD() - druid.registerTranquilityCD() + druid.RegisterBaselineSpells() + + druid.ApplyGlyphs() } -func (druid *Druid) RegisterBalanceSpells() { - druid.registerHurricaneSpell() - druid.registerInsectSwarmSpell() +func (druid *Druid) RegisterBaselineSpells() { druid.registerMoonfireSpell() - druid.registerSunfireSpell() - // druid.registerStarfireSpell() druid.registerWrathSpell() - // druid.registerStarfallSpell() - // druid.registerTyphoonSpell() - // druid.registerForceOfNature() - druid.registerStarsurgeSpell() - druid.registerWildMushrooms() + druid.registerHealingTouchSpell() + druid.registerHurricaneSpell() + druid.registerNaturesSwiftness() + druid.registerFaerieFireSpell() + druid.registerTranquilityCD() + + // druid.registerRebirthSpell() + // druid.registerInnervateCD() } func (druid *Druid) RegisterFeralCatSpells() { @@ -315,7 +277,6 @@ func (druid *Druid) RegisterFeralTankSpells() { } func (druid *Druid) Reset(_ *core.Simulation) { - // druid.eclipseEnergyBar.reset() druid.BleedsActive = 0 druid.form = druid.StartingForm druid.disabledMCDs = []*core.MajorCooldown{} @@ -330,7 +291,6 @@ func New(char *core.Character, form DruidForm, selfBuffs SelfBuffs, talents stri StartingForm: form, form: form, ClassSpellScaling: core.GetClassSpellScalingCoefficient(proto.Class_ClassDruid), - EclipseEnergyMap: make(EclipseEnergyMap), } core.FillTalentsProto(druid.Talents.ProtoReflect(), talents) diff --git a/sim/druid/eclipse_energy_map.go b/sim/druid/eclipse_energy_map.go deleted file mode 100644 index 2f0f75fd6e..0000000000 --- a/sim/druid/eclipse_energy_map.go +++ /dev/null @@ -1,25 +0,0 @@ -package druid - -type EclipseEnergyValues struct { - InEclipse float64 - NoEclipse float64 -} - -type EclipseEnergyMap = map[int64]EclipseEnergyValues - -func (druid *Druid) SetSpellEclipseEnergy(spellMask int64, inEclipseEnergy float64, noEclipseEnergy float64) { - druid.EclipseEnergyMap[spellMask] = EclipseEnergyValues{ - InEclipse: inEclipseEnergy, - NoEclipse: noEclipseEnergy, - } -} - -func (druid *Druid) GetSpellEclipseEnergy(spellMask int64, inEclipse bool) float64 { - energyValue := druid.EclipseEnergyMap[spellMask] - - if inEclipse { - return energyValue.InEclipse - } - - return energyValue.NoEclipse -} diff --git a/sim/druid/feral/_omen_of_clarity.go b/sim/druid/feral/_omen_of_clarity.go index 155e250541..09a6c7fd45 100644 --- a/sim/druid/feral/_omen_of_clarity.go +++ b/sim/druid/feral/_omen_of_clarity.go @@ -14,13 +14,6 @@ func (druid *Druid) applyOmenOfClarity() { Duration: time.Second * 15, OnInit: func(aura *core.Aura, sim *core.Simulation) { affectedSpells = core.FilterSlice([]*DruidSpell{ - // Balance - druid.Starfire, - druid.Wrath, - druid.Hurricane, - druid.WildMushrooms, - druid.ForceOfNature, - druid.Starsurge, // Feral druid.DemoralizingRoar, @@ -81,15 +74,7 @@ func (druid *Druid) applyOmenOfClarity() { } // https://github.com/JamminL/wotlk-classic-bugs/issues/66#issuecomment-1182017571 - if druid.HurricaneTickSpell.IsEqual(spell) { - curCastTickSpeed := spell.CurDot().TickPeriod().Seconds() / 10 - hurricaneCoeff := 1.0 - (7.0 / 9.0) - spellCoeff := hurricaneCoeff * curCastTickSpeed - chanceToProc := ((1.5 / 60) * 3.5) * spellCoeff - if sim.Proc(chanceToProc, "Clearcasting") { - druid.ProcOoc(sim) - } - } else if druid.AutoAttacks.PPMProc(sim, 3.5, core.ProcMaskMeleeWhiteHit, "Omen of Clarity", spell) { // Melee + if druid.AutoAttacks.PPMProc(sim, 3.5, core.ProcMaskMeleeWhiteHit, "Omen of Clarity", spell) { // Melee druid.ProcOoc(sim) } else if spell.Flags.Matches(SpellFlagOmenTrigger) { // Spells // Heavily based on comment here @@ -102,17 +87,8 @@ func (druid *Druid) applyOmenOfClarity() { } chanceToProc := (castTime / 60) * 3.5 - if druid.Typhoon.IsEqual(spell) { // Add Typhoon - chanceToProc *= 0.25 - } else if druid.Moonfire.IsEqual(spell) { // Add Moonfire - chanceToProc *= 0.076 - } else if druid.WildMushroomsDetonate.IsEqual(spell) { - // Wild Mushroom: Detonate seems to have an 'almost' guaranteed chance to proc - // setting to 0.5 to be safe - chanceToProc = 0.5 - } else { - chanceToProc *= 0.666 - } + chanceToProc *= 0.666 + if sim.Proc(chanceToProc, "Clearcasting") { druid.ProcOoc(sim) } diff --git a/sim/druid/forms.go b/sim/druid/forms.go index c6b9b9867e..3d20d99d2a 100644 --- a/sim/druid/forms.go +++ b/sim/druid/forms.go @@ -263,30 +263,3 @@ func (druid *Druid) registerBearFormSpell() { }, }) } - -// func (druid *Druid) applyMoonkinForm() { -// if !druid.InForm(Moonkin) || !druid.Talents.MoonkinForm { -// return -// } - -// druid.PseudoStats.DamageDealtMultiplier *= 1 + (float64(druid.Talents.MasterShapeshifter) * 0.02) -// if druid.Talents.ImprovedMoonkinForm > 0 { -// druid.AddStatDependency(stats.Spirit, stats.SpellPower, 0.1*float64(druid.Talents.ImprovedMoonkinForm)) -// } - -// manaMetrics := druid.NewManaMetrics(core.ActionID{SpellID: 24858}) -// druid.RegisterAura(core.Aura{ -// Label: "Moonkin Form", -// 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 result.DidCrit() { -// if druid.Moonfire.IsEqual(spell) || druid.Starfire.IsEqual(spell) || druid.Wrath.IsEqual(spell) { -// druid.AddMana(sim, 0.02*druid.MaxMana(), manaMetrics) -// } -// } -// }, -// }) -// } diff --git a/sim/druid/glyphs.go b/sim/druid/glyphs.go new file mode 100644 index 0000000000..bbadd4e29b --- /dev/null +++ b/sim/druid/glyphs.go @@ -0,0 +1,26 @@ +package druid + +import ( + "time" + + "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/core/proto" +) + +func (druid *Druid) ApplyGlyphs() { + if druid.HasMajorGlyph(proto.DruidMajorGlyph_GlyphOfHealingTouch) { + druid.RegisterAura(core.Aura{ + Label: "Glyph of Healing Touch", + Duration: core.NeverExpires, + OnReset: func(aura *core.Aura, sim *core.Simulation) { + aura.Activate(sim) + }, + OnCastComplete: func(_ *core.Aura, sim *core.Simulation, spell *core.Spell) { + if spell.Matches(DruidSpellHealingTouch) && !druid.NaturesSwiftness.CD.IsReady(sim) { + *druid.NaturesSwiftness.CD.Timer = core.Timer(time.Duration(*druid.NaturesSwiftness.CD.Timer) - time.Second*3) + druid.UpdateMajorCooldowns() + } + }, + }) + } +} diff --git a/sim/druid/guardian/TestGuardian.results b/sim/druid/guardian/TestGuardian.results index 44c31ed26c..1217ccfaa2 100644 --- a/sim/druid/guardian/TestGuardian.results +++ b/sim/druid/guardian/TestGuardian.results @@ -2055,6 +2055,33 @@ dps_results: { hps: 22571.53866 } } +dps_results: { + key: "TestGuardian-AllItems-RegaliaoftheEternalBlossom" + value: { + dps: 78227.40415 + tps: 542055.31124 + dtps: 53195.35956 + hps: 20329.08563 + } +} +dps_results: { + key: "TestGuardian-AllItems-RegaliaoftheHauntedForest" + value: { + dps: 79927.42794 + tps: 553605.73137 + dtps: 51763.54015 + hps: 23205.99778 + } +} +dps_results: { + key: "TestGuardian-AllItems-RegaliaoftheShatteredVale" + value: { + dps: 82160.66459 + tps: 570325.38159 + dtps: 51456.97956 + hps: 26001.45301 + } +} dps_results: { key: "TestGuardian-AllItems-RelicofChi-Ji-79330" value: { @@ -2907,7 +2934,7 @@ dps_results: { dps: 89350.50055 tps: 618225.31812 dtps: 54589.53868 - hps: 24434.36121 + hps: 24434.35949 } } dps_results: { diff --git a/sim/druid/healing_touch.go b/sim/druid/healing_touch.go new file mode 100644 index 0000000000..c802bba836 --- /dev/null +++ b/sim/druid/healing_touch.go @@ -0,0 +1,43 @@ +package druid + +import ( + "time" + + "github.com/wowsims/mop/sim/core" +) + +const ( + HealingTouchBonusCoeff = 1.86 + HealingTouchCoeff = 18.388 + HealingTouchVariance = 0.166 +) + +func (druid *Druid) registerHealingTouchSpell() { + actionID := core.ActionID{SpellID: 5185} + + druid.HealingTouch = druid.RegisterSpell(Humanoid|Moonkin, core.SpellConfig{ + ActionID: actionID, + SpellSchool: core.SpellSchoolNature, + ProcMask: core.ProcMaskSpellHealing, + ClassSpellMask: DruidSpellHealingTouch, + Flags: core.SpellFlagHelpful | core.SpellFlagAPL, + + ManaCost: core.ManaCostOptions{ + BaseCostPercent: 28.9, + }, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + CastTime: time.Millisecond * 2500, + }, + }, + + BonusCoefficient: HealingTouchBonusCoeff, + + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + baseHealing := druid.CalcAndRollDamageRange(sim, HealingTouchCoeff, HealingTouchVariance) + spell.CalcAndDealHealing(sim, spell.Unit, baseHealing, spell.OutcomeHealingCrit) + }, + }) +} diff --git a/sim/druid/hurricane.go b/sim/druid/hurricane.go index a96dadf848..47a2e8380c 100644 --- a/sim/druid/hurricane.go +++ b/sim/druid/hurricane.go @@ -6,21 +6,26 @@ import ( "github.com/wowsims/mop/sim/core" ) +const ( + HurricaneBonusCoeff = 0.31 + HurricaneCoeff = 0.31 +) + func (druid *Druid) registerHurricaneSpell() { druid.HurricaneTickSpell = druid.RegisterSpell(Humanoid|Moonkin, core.SpellConfig{ ActionID: core.ActionID{SpellID: 42231}, SpellSchool: core.SpellSchoolNature, ProcMask: core.ProcMaskSpellProc, - Flags: core.SpellFlagAoE | SpellFlagOmenTrigger, + Flags: core.SpellFlagAoE, ClassSpellMask: DruidSpellHurricane, CritMultiplier: druid.DefaultCritMultiplier(), DamageMultiplier: 1, ThreatMultiplier: 1, - BonusCoefficient: 0.095, + BonusCoefficient: HurricaneBonusCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - damage := 0.327 * druid.ClassSpellScaling + damage := druid.CalcScalingSpellDmg(HurricaneCoeff) for _, aoeTarget := range sim.Encounter.TargetUnits { spell.CalcAndDealDamage(sim, aoeTarget, damage, spell.OutcomeMagicHitAndCrit) @@ -36,8 +41,7 @@ func (druid *Druid) registerHurricaneSpell() { ClassSpellMask: DruidSpellHurricane, ManaCost: core.ManaCostOptions{ - BaseCostPercent: 81, - PercentModifier: 100, + BaseCostPercent: 50.3, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ diff --git a/sim/druid/insect_swarm.go b/sim/druid/insect_swarm.go deleted file mode 100644 index 36ad1a5575..0000000000 --- a/sim/druid/insect_swarm.go +++ /dev/null @@ -1,66 +0,0 @@ -package druid - -import ( - "time" - - "github.com/wowsims/mop/sim/core" - "github.com/wowsims/mop/sim/core/proto" -) - -func (druid *Druid) registerInsectSwarmSpell() { - druid.InsectSwarm = druid.RegisterSpell(Humanoid|Moonkin, core.SpellConfig{ - ActionID: core.ActionID{SpellID: 5570}, - SpellSchool: core.SpellSchoolArcane | core.SpellSchoolNature, - ProcMask: core.ProcMaskSpellDamage, - ClassSpellMask: DruidSpellInsectSwarm, - Flags: core.SpellFlagAPL | SpellFlagOmenTrigger, - - ManaCost: core.ManaCostOptions{ - BaseCostPercent: 8, - PercentModifier: 100, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - CritMultiplier: druid.DefaultCritMultiplier(), - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Insect Swarm", - }, - - NumberOfTicks: 6, - TickLength: time.Second * 2, - AffectedByCastSpeed: true, - BonusCoefficient: 0.13, - - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - baseDamage := core.CalcScalingSpellAverageEffect(proto.Class_ClassDruid, 0.138) - dot.Snapshot(target, baseDamage) - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHit) - - if result.Landed() { - spell.Dot(target).Apply(sim) - } - - spell.DealOutcome(sim, result) - }, - - ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, useSnapshot bool) *core.SpellResult { - baseDamage := core.CalcScalingSpellAverageEffect(proto.Class_ClassDruid, 0.138) - return spell.CalcPeriodicDamage(sim, target, baseDamage, spell.OutcomeExpectedMagicCrit) - }, - }) -} diff --git a/sim/druid/items.go b/sim/druid/items.go new file mode 100644 index 0000000000..467e748615 --- /dev/null +++ b/sim/druid/items.go @@ -0,0 +1,63 @@ +package druid + +import ( + "github.com/wowsims/mop/sim/core" +) + +// T14 Balance +var ItemSetRegaliaOfTheEternalBloosom = core.NewItemSet(core.ItemSet{ + Name: "Regalia of the Eternal Blossom", + Bonuses: map[int32]core.ApplySetBonus{ + 2: func(_ core.Agent, setBonusAura *core.Aura) { + // Your Starfall deals 20% additional damage. + setBonusAura.AttachSpellMod(core.SpellModConfig{ + Kind: core.SpellMod_DamageDone_Pct, + ClassMask: DruidSpellStarfall, + FloatValue: 0.2, + }) + }, + 4: func(_ core.Agent, setBonusAura *core.Aura) { + // Increases the duration of your Moonfire and Sunfire spells by 2 sec. + setBonusAura.AttachSpellMod(core.SpellModConfig{ + Kind: core.SpellMod_DotNumberOfTicks_Flat, + ClassMask: DruidSpellMoonfireDoT | DruidSpellSunfireDoT, + IntValue: 1, + }) + }, + }, +}) + +// T15 Balance +var ItemSetRegaliaOfTheHauntedForest = core.NewItemSet(core.ItemSet{ + Name: "Regalia of the Haunted Forest", + Bonuses: map[int32]core.ApplySetBonus{ + 2: func(_ core.Agent, setBonusAura *core.Aura) { + // Increases the critical strike chance of Starsurge by 10%. + setBonusAura.AttachSpellMod(core.SpellModConfig{ + Kind: core.SpellMod_BonusCrit_Percent, + ClassMask: DruidSpellStarsurge, + FloatValue: 10, + }) + }, + 4: func(agent core.Agent, setBonusAura *core.Aura) { + // Nature's Grace now also grants 1000 critical strike and 1000 mastery for its duration. + }, + }, +}) + +// T16 Balance +var ItemSetRegaliaOfTheShatteredVale = core.NewItemSet(core.ItemSet{ + ID: 1197, + Name: "Regalia of the Shattered Vale", + Bonuses: map[int32]core.ApplySetBonus{ + 2: func(agent core.Agent, setBonusAura *core.Aura) { + // Arcane spells cast while in Lunar Eclipse will shoot a single Lunar Bolt at the target. Nature spells cast while in a Solar Eclipse will shoot a single Solar Bolt at the target. + }, + 4: func(agent core.Agent, setBonusAura *core.Aura) { + // Your chance to get Shooting Stars from a critical strike from Moonfire or Sunfire is increased by 8%. + }, + }, +}) + +func init() { +} diff --git a/sim/druid/moonfire.go b/sim/druid/moonfire.go index 5af48d3d27..ed6e3ad1bf 100644 --- a/sim/druid/moonfire.go +++ b/sim/druid/moonfire.go @@ -4,7 +4,15 @@ import ( "time" "github.com/wowsims/mop/sim/core" - "github.com/wowsims/mop/sim/core/proto" +) + +const ( + MoonfireBonusCoeff = 0.24 + + MoonfireDotCoeff = 0.24 + + MoonfireImpactCoeff = 0.571 + MoonfireImpactVariance = 0.2 ) func (druid *Druid) registerMoonfireSpell() { @@ -18,7 +26,7 @@ func (druid *Druid) registerMoonfireDoTSpell() { SpellSchool: core.SpellSchoolArcane, ProcMask: core.ProcMaskSpellDamage, ClassSpellMask: DruidSpellMoonfireDoT, - Flags: SpellFlagOmenTrigger | core.SpellFlagPassiveSpell, + Flags: core.SpellFlagPassiveSpell, DamageMultiplier: 1, CritMultiplier: druid.DefaultCritMultiplier(), @@ -27,15 +35,24 @@ func (druid *Druid) registerMoonfireDoTSpell() { Dot: core.DotConfig{ Aura: core.Aura{ Label: "Moonfire", + OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if result.Landed() && result.DidCrit() && spell.Matches(DruidSpellStarfire|DruidSpellStarsurge) { + oldDuration := druid.Moonfire.Dot(aura.Unit).RemainingDuration(sim) + druid.Moonfire.Dot(aura.Unit).AddTick() + + if sim.Log != nil { + druid.Log(sim, "[DEBUG]: %s extended %s. Old Duration: %0.0f, new duration: %0.0f.", spell.ActionID, druid.Moonfire.ActionID, oldDuration.Seconds(), druid.Moonfire.Dot(aura.Unit).RemainingDuration(sim).Seconds()) + } + } + }, }, - NumberOfTicks: 6, + NumberOfTicks: 7, TickLength: time.Second * 2, AffectedByCastSpeed: true, - BonusCoefficient: 0.18, + BonusCoefficient: MoonfireBonusCoeff, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - baseDamage := core.CalcScalingSpellAverageEffect(proto.Class_ClassDruid, 0.095) - dot.Snapshot(target, baseDamage) + dot.Snapshot(target, druid.CalcScalingSpellDmg(MoonfireDotCoeff)) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) @@ -52,18 +69,16 @@ func (druid *Druid) registerMoonfireDoTSpell() { } func (druid *Druid) registerMoonfireImpactSpell() { - druid.SetSpellEclipseEnergy(DruidSpellMoonfire, MoonfireBaseEnergyGain, MoonfireBaseEnergyGain) druid.Moonfire = druid.RegisterSpell(Humanoid|Moonkin, core.SpellConfig{ ActionID: core.ActionID{SpellID: 8921}, SpellSchool: core.SpellSchoolArcane, ProcMask: core.ProcMaskSpellDamage, ClassSpellMask: DruidSpellMoonfire, - Flags: core.SpellFlagAPL | SpellFlagOmenTrigger, + Flags: core.SpellFlagAPL, ManaCost: core.ManaCostOptions{ BaseCostPercent: 9, - PercentModifier: 100, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ @@ -75,19 +90,13 @@ func (druid *Druid) registerMoonfireImpactSpell() { CritMultiplier: druid.DefaultCritMultiplier(), ThreatMultiplier: 1, - BonusCoefficient: 0.18, + BonusCoefficient: MoonfireBonusCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - min, max := core.CalcScalingSpellEffectVarianceMinMax(proto.Class_ClassDruid, 0.221, 0.2) - baseDamage := sim.Roll(min, max) + baseDamage := druid.CalcAndRollDamageRange(sim, MoonfireImpactCoeff, MoonfireImpactVariance) result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) if result.Landed() { - if druid.Sunfire.Dot(target).IsActive() { - druid.Sunfire.Dot(target).Deactivate(sim) - } - - druid.ExtendingMoonfireStacks = 3 druid.Moonfire.RelatedDotSpell.Cast(sim, target) } diff --git a/sim/druid/natures_swiftness.go b/sim/druid/natures_swiftness.go new file mode 100644 index 0000000000..a88b415596 --- /dev/null +++ b/sim/druid/natures_swiftness.go @@ -0,0 +1,66 @@ +package druid + +import ( + "time" + + "github.com/wowsims/mop/sim/core" +) + +func (druid *Druid) registerNaturesSwiftness() { + actionID := core.ActionID{SpellID: 132158} + cdTimer := druid.NewTimer() + cd := time.Minute * 1 + + castTimeModConfig := core.SpellModConfig{ + ClassMask: DruidHealingNonInstantSpells, + Kind: core.SpellMod_CastTime_Pct, + FloatValue: -1, + } + + resourceCostModConfig := core.SpellModConfig{ + ClassMask: DruidHealingNonInstantSpells, + Kind: core.SpellMod_PowerCost_Pct, + FloatValue: -1, + } + + healingPowerModConfig := core.SpellModConfig{ + ClassMask: DruidHealingNonInstantSpells, + Kind: core.SpellMod_DamageDone_Pct, + FloatValue: 0.5, + } + + nsAura := druid.RegisterAura(core.Aura{ + Label: "Nature's Swiftness", + ActionID: actionID, + Duration: core.NeverExpires, + OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { + if !spell.Matches(DruidSpellHealingTouch) { + return + } + + aura.Deactivate(sim) + cdTimer.Set(sim.CurrentTime + cd) + druid.UpdateMajorCooldowns() + }, + }).AttachSpellMod(castTimeModConfig).AttachSpellMod(resourceCostModConfig).AttachSpellMod(healingPowerModConfig) + + druid.NaturesSwiftness = druid.RegisterSpell(Any, core.SpellConfig{ + ActionID: actionID, + Flags: core.SpellFlagNoOnCastComplete, + RelatedSelfBuff: nsAura, + Cast: core.CastConfig{ + CD: core.Cooldown{ + Timer: cdTimer, + Duration: cd, + }, + }, + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + spell.RelatedSelfBuff.Activate(sim) + }, + }) + + druid.AddMajorCooldown(core.MajorCooldown{ + Spell: druid.NaturesSwiftness.Spell, + Type: core.CooldownTypeDPS, + }) +} diff --git a/sim/druid/starsurge.go b/sim/druid/starsurge.go deleted file mode 100644 index b592e39774..0000000000 --- a/sim/druid/starsurge.go +++ /dev/null @@ -1,56 +0,0 @@ -package druid - -import ( - "time" - - "github.com/wowsims/mop/sim/core" - "github.com/wowsims/mop/sim/core/proto" -) - -func (druid *Druid) registerStarsurgeSpell() { - druid.SetSpellEclipseEnergy(DruidSpellStarsurge, StarsurgeBaseEnergyGain, StarsurgeBaseEnergyGain) - - druid.Starsurge = druid.RegisterSpell(Humanoid|Moonkin, core.SpellConfig{ - ActionID: core.ActionID{SpellID: 78674}, - SpellSchool: core.SpellSchoolArcane | core.SpellSchoolNature, - ProcMask: core.ProcMaskSpellDamage, - ClassSpellMask: DruidSpellStarsurge, - Flags: core.SpellFlagAPL | SpellFlagOmenTrigger, - MissileSpeed: 20, - - DamageMultiplier: 1, - DamageMultiplierAdditive: 1, - CritMultiplier: druid.DefaultCritMultiplier(), - ManaCost: core.ManaCostOptions{ - BaseCostPercent: 11, - PercentModifier: 100, - }, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - CastTime: time.Second * 2, - }, - CD: core.Cooldown{ - Timer: druid.NewTimer(), - Duration: time.Second * 15, - }, - }, - - ThreatMultiplier: 1, - - BonusCoefficient: 1.228, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - min, max := core.CalcScalingSpellEffectVarianceMinMax(proto.Class_ClassDruid, 1.228, 0.32) - baseDamage := sim.Roll(min, max) - result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - - if result.Landed() { - spell.WaitTravelTime(sim, func(sim *core.Simulation) { - spell.DealDamage(sim, result) - }) - } - }, - }) -} diff --git a/sim/druid/sunfire.go b/sim/druid/sunfire.go deleted file mode 100644 index 7ebcd81785..0000000000 --- a/sim/druid/sunfire.go +++ /dev/null @@ -1,96 +0,0 @@ -package druid - -import ( - "time" - - "github.com/wowsims/mop/sim/core" - "github.com/wowsims/mop/sim/core/proto" -) - -func (druid *Druid) registerSunfireSpell() { - druid.registerSunfireImpactSpell() - druid.registerSunfireDoTSpell() -} - -func (druid *Druid) registerSunfireDoTSpell() { - druid.Sunfire.RelatedDotSpell = druid.Unit.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 93402}.WithTag(1), - SpellSchool: core.SpellSchoolNature, - ProcMask: core.ProcMaskSpellDamage, - ClassSpellMask: DruidSpellSunfireDoT, - Flags: SpellFlagOmenTrigger | core.SpellFlagPassiveSpell, - - DamageMultiplier: 1, - CritMultiplier: druid.DefaultCritMultiplier(), - ThreatMultiplier: 1, - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Sunfire", - }, - NumberOfTicks: 6, - TickLength: time.Second * 2, - AffectedByCastSpeed: true, - BonusCoefficient: 0.18, - - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - baseDamage := core.CalcScalingSpellAverageEffect(proto.Class_ClassDruid, 0.095) - dot.Snapshot(target, baseDamage) - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeSnapshotCrit) - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcOutcome(sim, target, spell.OutcomeAlwaysHitNoHitCounter) - - spell.Dot(target).Apply(sim) - spell.DealOutcome(sim, result) - }, - }) -} - -func (druid *Druid) registerSunfireImpactSpell() { - druid.SetSpellEclipseEnergy(DruidSpellSunfire, SunfireBaseEnergyGain, SunfireBaseEnergyGain) - - druid.Sunfire = druid.RegisterSpell(Humanoid|Moonkin, core.SpellConfig{ - ActionID: core.ActionID{SpellID: 93402}, - SpellSchool: core.SpellSchoolNature, - ProcMask: core.ProcMaskSpellDamage, - ClassSpellMask: DruidSpellSunfire, - Flags: core.SpellFlagAPL | SpellFlagOmenTrigger, - - ManaCost: core.ManaCostOptions{ - BaseCostPercent: 9, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - DamageMultiplier: 1, - - CritMultiplier: druid.DefaultCritMultiplier(), - ThreatMultiplier: 1, - BonusCoefficient: 0.18, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - min, max := core.CalcScalingSpellEffectVarianceMinMax(proto.Class_ClassDruid, 0.221, 0.2) - baseDamage := sim.Roll(min, max) - result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - - if result.Landed() { - if druid.Moonfire.Dot(target).IsActive() { - druid.Moonfire.Dot(target).Deactivate(sim) - } - - druid.ExtendingMoonfireStacks = 3 - druid.Sunfire.RelatedDotSpell.Cast(sim, target) - } - - spell.DealDamage(sim, result) - }, - }) -} diff --git a/sim/druid/talents.go b/sim/druid/talents.go index 29ddc53736..83998c4bac 100644 --- a/sim/druid/talents.go +++ b/sim/druid/talents.go @@ -4,6 +4,7 @@ import ( "time" "github.com/wowsims/mop/sim/core" + "github.com/wowsims/mop/sim/core/stats" ) func (druid *Druid) ApplyTalents() { @@ -12,6 +13,69 @@ func (druid *Druid) ApplyTalents() { druid.registerCenarionWard() druid.registerForceOfNature() + + druid.registerHeartOfTheWild() + druid.registerNaturesVigil() +} + +func (druid *Druid) registerHeartOfTheWild() { + if !druid.Talents.HeartOfTheWild { + return + } + + // Apply 6% increase to Stamina, Agility, and Intellect + statMultiplier := 1.06 + druid.MultiplyStat(stats.Stamina, statMultiplier) + druid.MultiplyStat(stats.Agility, statMultiplier) + druid.MultiplyStat(stats.Intellect, statMultiplier) + + // The activation spec specific effects are not implemented - most likely irrelevant for the sim unless proven otherwise +} + +func (druid *Druid) registerNaturesVigil() { + if !druid.Talents.NaturesVigil { + return + } + + actionID := core.ActionID{SpellID: 124974} + + naturesVigilAura := druid.RegisterAura(core.Aura{ + Label: "Nature's Vigil", + ActionID: actionID, + Duration: time.Second * 30, + OnGain: func(_ *core.Aura, _ *core.Simulation) { + druid.PseudoStats.DamageDealtMultiplier *= 1.12 + }, + OnExpire: func(_ *core.Aura, _ *core.Simulation) { + druid.PseudoStats.DamageDealtMultiplier /= 1.12 + }, + }) + + naturesVigilSpell := druid.RegisterSpell(Any, core.SpellConfig{ + ActionID: actionID, + Flags: core.SpellFlagAPL, + RelatedSelfBuff: naturesVigilAura, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: 0, + }, + IgnoreHaste: true, + CD: core.Cooldown{ + Timer: druid.NewTimer(), + Duration: time.Second * 90, + }, + }, + + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + spell.RelatedSelfBuff.Activate(sim) + }, + }) + + druid.AddMajorCooldown(core.MajorCooldown{ + Spell: naturesVigilSpell.Spell, + Type: core.CooldownTypeDPS, + }) } func (druid *Druid) registerYserasGift() { diff --git a/sim/druid/wrath.go b/sim/druid/wrath.go index a343d78efd..2c6602ef03 100644 --- a/sim/druid/wrath.go +++ b/sim/druid/wrath.go @@ -4,36 +4,35 @@ import ( "time" "github.com/wowsims/mop/sim/core" - "github.com/wowsims/mop/sim/core/proto" ) -func (druid *Druid) registerWrathSpell() { - druid.SetSpellEclipseEnergy(DruidSpellWrath, WrathBaseEnergyGain, WrathBaseEnergyGain) +const ( + WrathBonusCoeff = 1.338 + WrathCoeff = 2.676 + WrathVariance = 0.25 +) +func (druid *Druid) registerWrathSpell() { druid.Wrath = druid.RegisterSpell(Humanoid|Moonkin, core.SpellConfig{ ActionID: core.ActionID{SpellID: 5176}, SpellSchool: core.SpellSchoolNature, ProcMask: core.ProcMaskSpellDamage, ClassSpellMask: DruidSpellWrath, - Flags: core.SpellFlagAPL | SpellFlagOmenTrigger, + Flags: core.SpellFlagAPL, MissileSpeed: 20, ManaCost: core.ManaCostOptions{ - BaseCostPercent: 9, - PercentModifier: 100, + BaseCostPercent: 8.8, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ GCD: core.GCDDefault, - CastTime: time.Millisecond * 2500, + CastTime: time.Millisecond * 2000, }, }, - BonusCoefficient: 0.879, - - // TODO: Was the value of 1 here incorrect to begin with? - BonusCritPercent: 1 / core.CritRatingPerCritPercent, + BonusCoefficient: WrathBonusCoeff, DamageMultiplier: 1, @@ -42,15 +41,12 @@ func (druid *Druid) registerWrathSpell() { ThreatMultiplier: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - min, max := core.CalcScalingSpellEffectVarianceMinMax(proto.Class_ClassDruid, 0.896, 0.12) - baseDamage := sim.Roll(min, max) + baseDamage := druid.CalcAndRollDamageRange(sim, WrathCoeff, WrathVariance) result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) - if result.Landed() { - spell.WaitTravelTime(sim, func(sim *core.Simulation) { - spell.DealDamage(sim, result) - }) - } + spell.WaitTravelTime(sim, func(sim *core.Simulation) { + spell.DealDamage(sim, result) + }) }, }) } diff --git a/ui/core/components/detailed_results/timeline.tsx b/ui/core/components/detailed_results/timeline.tsx index 8f5a2b0c07..cfcc190c2c 100644 --- a/ui/core/components/detailed_results/timeline.tsx +++ b/ui/core/components/detailed_results/timeline.tsx @@ -1299,8 +1299,7 @@ const idToCategoryMap: Record = { [48465]: SPELL_ACTION_CATEGORY + 0.1, // Starfire [48461]: SPELL_ACTION_CATEGORY + 0.2, // Wrath [53201]: SPELL_ACTION_CATEGORY + 0.3, // Starfall - [48468]: SPELL_ACTION_CATEGORY + 0.4, // Insect Swarm - [48463]: SPELL_ACTION_CATEGORY + 0.5, // Moonfire + [48463]: SPELL_ACTION_CATEGORY + 0.4, // Moonfire // Hunter [48996]: 0.1, // Raptor Strike diff --git a/ui/core/components/individual_sim_ui/apl_values.ts b/ui/core/components/individual_sim_ui/apl_values.ts index 0bc7ae93ed..7e307334ab 100644 --- a/ui/core/components/individual_sim_ui/apl_values.ts +++ b/ui/core/components/individual_sim_ui/apl_values.ts @@ -877,7 +877,7 @@ const valueKindFactories: { [f in ValidAPLValueKind]: ValueKindConfig, _isPrepull: boolean) => player.getClass() == Class.ClassDeathKnight, fields: [], }), - currentLunarEnergy: inputBuilder({ + currentSolarEnergy: inputBuilder({ label: 'Solar Energy', submenu: ['Resources', 'Eclipse'], shortDescription: 'Amount of currently available Solar Energy.', @@ -885,7 +885,7 @@ const valueKindFactories: { [f in ValidAPLValueKind]: ValueKindConfig, _isPrepull: boolean) => player.getSpec() == Spec.SpecBalanceDruid, fields: [], }), - currentSolarEnergy: inputBuilder({ + currentLunarEnergy: inputBuilder({ label: 'Lunar Energy', submenu: ['Resources', 'Eclipse'], shortDescription: 'Amount of currently available Lunar Energy', diff --git a/ui/core/launched_sims.ts b/ui/core/launched_sims.ts index e68a47ec5a..c5f00918fb 100644 --- a/ui/core/launched_sims.ts +++ b/ui/core/launched_sims.ts @@ -44,7 +44,7 @@ export const simLaunchStatuses: Record = { // Druid [Spec.SpecBalanceDruid]: { phase: Phase.Phase1, - status: LaunchStatus.Unlaunched, + status: LaunchStatus.Alpha, }, [Spec.SpecFeralDruid]: { phase: Phase.Phase1, diff --git a/ui/druid/balance/apls/standard.apl.json b/ui/druid/balance/apls/standard.apl.json new file mode 100644 index 0000000000..b5c7967773 --- /dev/null +++ b/ui/druid/balance/apls/standard.apl.json @@ -0,0 +1,27 @@ +{ + "type": "TypeAPL", + "prepullActions": [ + {"action":{"channelSpell":{"spellId":{"spellId":127663},"interruptIf":{"cmp":{"op":"OpGe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"50"}}}}}},"doAtValue":{"const":{"val":"-20s"}}}, + {"action":{"castSpell":{"spellId":{"spellId":88747}}},"doAtValue":{"const":{"val":"-17s"}}}, + {"action":{"castSpell":{"spellId":{"spellId":88747}}},"doAtValue":{"const":{"val":"-15s"}}}, + {"action":{"castSpell":{"spellId":{"spellId":88747}}},"doAtValue":{"const":{"val":"-13s"}}}, + {"action":{"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}},"doAtValue":{"const":{"val":"-2.5s"}}}, + {"action":{"castSpell":{"spellId":{"spellId":5176}}},"doAtValue":{"const":{"val":"-2.5s"}}}, + {"action":{"castSpell":{"spellId":{"spellId":48505}}},"doAtValue":{"const":{"val":"-0.5s"}}} + ], + "priorityList": [ + {"action":{"condition":{"or":{"vals":[{"cmp":{"op":"OpEq","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"100"}}}}]}},"autocastOtherCooldowns":{}}}, + {"action":{"condition":{"cmp":{"op":"OpGe","lhs":{"currentTime":{}},"rhs":{"const":{"val":"2s"}}}},"castSpell":{"spellId":{"spellId":2825,"tag":-1}}}}, + {"action":{"condition":{"and":{"vals":[{"spellIsKnown":{"spellId":{"spellId":102560}}},{"and":{"vals":[{"cmp":{"op":"OpGe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"100"}}}},{"auraIsActive":{"auraId":{"spellId":48518}}}]}}]}},"castSpell":{"spellId":{"spellId":102560}}}}, + {"action":{"condition":{"and":{"vals":[{"spellIsKnown":{"spellId":{"spellId":124974}}},{"or":{"vals":[{"auraIsActive":{"auraId":{"spellId":102560}}},{"cmp":{"op":"OpGe","lhs":{"spellTimeToReady":{"spellId":{"spellId":102560}}},"rhs":{"const":{"val":"88"}}}}]}}]}},"castSpell":{"spellId":{"spellId":124974}}}}, + {"action":{"condition":{"and":{"vals":[{"spellIsKnown":{"spellId":{"spellId":106737}}},{"cmp":{"op":"OpGe","lhs":{"auraRemainingTime":{"auraId":{"spellId":16886}}},"rhs":{"const":{"val":"13s"}}}}]}},"castSpell":{"spellId":{"spellId":106737}}}}, + {"action":{"condition":{"or":{"vals":[{"and":{"vals":[{"spellIsKnown":{"spellId":{"spellId":102560}}},{"or":{"vals":[{"not":{"val":{"auraIsActive":{"auraId":{"spellId":48518}}}}},{"cmp":{"op":"OpLt","lhs":{"auraRemainingTime":{"auraId":{"spellId":102560}}},"rhs":{"const":{"val":"15"}}}}]}},{"auraIsActive":{"auraId":{"spellId":102560}}}]}},{"and":{"vals":[{"not":{"val":{"spellIsKnown":{"spellId":{"spellId":102560}}}}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":48518}}}}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":48517}}}}}]}}]}},"castSpell":{"spellId":{"spellId":112071}}}}, + {"action":{"condition":{"and":{"vals":[{"or":{"vals":[{"cmp":{"op":"OpGe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"60"}}}},{"auraIsActive":{"auraId":{"spellId":48518}}}]}},{"not":{"val":{"auraIsActive":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":48505}}}}},{"or":{"vals":[{"and":{"vals":[{"auraIsKnown":{"auraId":{"spellId":102560}}},{"or":{"vals":[{"cmp":{"op":"OpGe","lhs":{"spellTimeToReady":{"spellId":{"spellId":102560}}},"rhs":{"const":{"val":"30"}}}},{"cmp":{"op":"OpLe","lhs":{"spellTimeToReady":{"spellId":{"spellId":102560}}},"rhs":{"const":{"val":"1"}}}}]}}]}},{"not":{"val":{"auraIsKnown":{"auraId":{"spellId":102560}}}}}]}}]}},"castSpell":{"spellId":{"spellId":48505}}}}, + {"action":{"condition":{"or":{"vals":[{"auraIsActive":{"auraId":{"spellId":48517}}},{"cmp":{"op":"OpGe","lhs":{"auraNumStacks":{"auraId":{"spellId":88747}}},"rhs":{"const":{"val":"1"}}}}]}},"castSpell":{"spellId":{"spellId":88751}}}}, + {"action":{"condition":{"or":{"vals":[{"auraIsActive":{"auraId":{"spellId":48518}}},{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48517}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"20"}}}}]}},{"and":{"vals":[{"not":{"val":{"auraIsActive":{"auraId":{"spellId":48518}}}}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":48517}}}}},{"cmp":{"op":"OpLt","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"60"}}}},{"cmp":{"op":"OpLt","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"70"}}}}]}}]}},"castSpell":{"spellId":{"spellId":78674}}}}, + {"action":{"condition":{"or":{"vals":[{"and":{"vals":[{"or":{"vals":[{"not":{"val":{"dotIsActive":{"spellId":{"spellId":8921}}}}},{"and":{"vals":[{"dotIsActive":{"spellId":{"spellId":8921}}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":8921}}},"rhs":{"dotTickFrequency":{"spellId":{"spellId":8921}}}}}]}}]}},{"or":{"vals":[{"auraIsActive":{"auraId":{"spellId":48518}}},{"auraIsActive":{"auraId":{"spellId":16886}}},{"and":{"vals":[{"not":{"val":{"auraIsActive":{"auraId":{"spellId":48518}}}}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":48517}}}}},{"cmp":{"op":"OpLt","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"70"}}}},{"cmp":{"op":"OpLt","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"60"}}}}]}}]}}]}},{"and":{"vals":[{"or":{"vals":[{"cmp":{"op":"OpGe","lhs":{"auraRemainingTime":{"auraId":{"spellId":112071}}},"rhs":{"const":{"val":"14.5s"}}}},{"cmp":{"op":"OpLe","lhs":{"auraRemainingTime":{"auraId":{"spellId":112071}}},"rhs":{"const":{"val":"1s"}}}}]}},{"auraIsActive":{"auraId":{"spellId":112071}}}]}},{"and":{"vals":[{"cmp":{"op":"OpLt","lhs":{"auraRemainingTime":{"auraId":{"spellId":16886}}},"rhs":{"const":{"val":"1.5s"}}}},{"auraIsActive":{"auraId":{"spellId":48518}}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":8921}}},"rhs":{"const":{"val":"6"}}}}]}}]}},"castSpell":{"spellId":{"spellId":8921}}}}, + {"action":{"condition":{"or":{"vals":[{"and":{"vals":[{"or":{"vals":[{"not":{"val":{"dotIsActive":{"spellId":{"spellId":93402}}}}},{"and":{"vals":[{"dotIsActive":{"spellId":{"spellId":93402}}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":93402}}},"rhs":{"dotTickFrequency":{"spellId":{"spellId":93402}}}}}]}}]}},{"or":{"vals":[{"auraIsActive":{"auraId":{"spellId":48517}}},{"auraIsActive":{"auraId":{"spellId":16886}}},{"and":{"vals":[{"not":{"val":{"auraIsActive":{"auraId":{"spellId":48518}}}}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":48517}}}}},{"cmp":{"op":"OpLt","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"70"}}}},{"cmp":{"op":"OpLt","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"60"}}}}]}}]}}]}},{"and":{"vals":[{"cmp":{"op":"OpLt","lhs":{"auraRemainingTime":{"auraId":{"spellId":16886}}},"rhs":{"const":{"val":"1.5s"}}}},{"auraIsActive":{"auraId":{"spellId":48517}}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":93402}}},"rhs":{"const":{"val":"6"}}}}]}}]}},"castSpell":{"spellId":{"spellId":93402}}}}, + {"action":{"condition":{"druidCurrentEclipsePhase":{"eclipsePhase":"LunarPhase"}},"castSpell":{"spellId":{"spellId":2912}}}}, + {"action":{"castSpell":{"spellId":{"spellId":5176}}}} + ] +} diff --git a/ui/druid/balance/apls/t11.apl.json b/ui/druid/balance/apls/t11.apl.json deleted file mode 100644 index 4929f16b9e..0000000000 --- a/ui/druid/balance/apls/t11.apl.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "type": "TypeAPL", - "prepullActions": [ - {"action":{"castSpell":{"spellId":{"spellId":88747}}},"doAtValue":{"const":{"val":"-5s"}}}, - {"action":{"castSpell":{"spellId":{"spellId":88747}}},"doAtValue":{"const":{"val":"-4s"}}}, - {"action":{"castSpell":{"spellId":{"spellId":88747}}},"doAtValue":{"const":{"val":"-3s"}}}, - {"action":{"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}},"doAtValue":{"const":{"val":"-2s"}}}, - {"action":{"castSpell":{"spellId":{"spellId":2912}}},"doAtValue":{"const":{"val":"-2s"}}}, - {"action":{"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}},"doAtValue":{"const":{"val":"-1.5s"}},"hide":true}, - {"action":{"castSpell":{"spellId":{"spellId":5176}}},"doAtValue":{"const":{"val":"-1.5s"}},"hide":true} - ], - "priorityList": [ - {"action":{"condition":{"cmp":{"op":"OpGt","lhs":{"currentTime":{}},"rhs":{"const":{"val":"2s"}}}},"castSpell":{"spellId":{"spellId":2825,"tag":-1}}}}, - {"action":{"condition":{"and":{"vals":[{"druidCurrentEclipsePhase":{"eclipsePhase":"NeutralPhase"}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":61345}}}}},{"cmp":{"op":"OpLt","lhs":{"currentTime":{}},"rhs":{"const":{"val":"2s"}}}}]}},"castSpell":{"spellId":{"spellId":8921}}}}, - {"action":{"castSpell":{"spellId":{"spellId":33831}}}}, - {"action":{"condition":{"or":{"vals":[{"cmp":{"op":"OpGe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"100"}}}}]}},"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}}}, - {"action":{"condition":{"or":{"vals":[{"cmp":{"op":"OpGe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"100"}}}},{"cmp":{"op":"OpGe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"100"}}}}]}},"autocastOtherCooldowns":{}}}, - {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpGe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"100"}}}}]}},"castSpell":{"spellId":{"spellId":48505}}}}, - {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48517}}},{"or":{"vals":[{"and":{"vals":[{"cmp":{"op":"OpLe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"15"}}}},{"cmp":{"op":"OpLe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"0"}}}}]}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":5570}}},"rhs":{"dotTickFrequency":{"spellId":{"spellId":5570}}}}}]}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":5570}}},"rhs":{"math":{"op":"OpMul","lhs":{"dotTickFrequency":{"spellId":{"spellId":5570}}},"rhs":{"const":{"val":"4"}}}}}}]}},"strictSequence":{"actions":[{"castSpell":{"spellId":{"spellId":5570}}},{"castSpell":{"spellId":{"spellId":93402}}}]}}}, - {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48518}}},{"or":{"vals":[{"and":{"vals":[{"cmp":{"op":"OpLe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"20"}}}},{"cmp":{"op":"OpLe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"0"}}}}]}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":5570}}},"rhs":{"dotTickFrequency":{"spellId":{"spellId":5570}}}}}]}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":5570}}},"rhs":{"math":{"op":"OpMul","lhs":{"dotTickFrequency":{"spellId":{"spellId":5570}}},"rhs":{"const":{"val":"8"}}}}}}]}},"strictSequence":{"actions":[{"castSpell":{"spellId":{"spellId":5570}}},{"castSpell":{"spellId":{"spellId":8921}}}]}}}, - {"action":{"condition":{"and":{"vals":[{"not":{"val":{"druidCurrentEclipsePhase":{"eclipsePhase":"SolarPhase"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"100"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"80"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"60"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"40"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"20"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"0"}}}}]}},"castSpell":{"spellId":{"spellId":78674}}}}, - {"action":{"condition":{"and":{"vals":[{"druidCurrentEclipsePhase":{"eclipsePhase":"SolarPhase"}},{"cmp":{"op":"OpLe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"60"}}}}]}},"castSpell":{"spellId":{"spellId":78674}}}}, - {"action":{"condition":{"or":{"vals":[{"auraIsActive":{"auraId":{"spellId":48518}}},{"auraIsActive":{"auraId":{"spellId":48517}}}]}},"castSpell":{"spellId":{"spellId":78674}}}}, - {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48517}}},{"cmp":{"op":"OpEq","lhs":{"auraNumStacks":{"auraId":{"spellId":88747}}},"rhs":{"const":{"val":"3"}}}}]}},"castSpell":{"spellId":{"spellId":88751}}}}, - {"hide":true,"action":{"condition":{"not":{"val":{"druidCurrentEclipsePhase":{"eclipsePhase":"LunarPhase"}}}},"castSpell":{"spellId":{"spellId":5176}}}}, - {"action":{"condition":{"druidCurrentEclipsePhase":{"eclipsePhase":"SolarPhase"}},"castSpell":{"spellId":{"spellId":5176}}}}, - {"action":{"castSpell":{"spellId":{"spellId":2912}}}} - ] - } diff --git a/ui/druid/balance/apls/t12.apl.json b/ui/druid/balance/apls/t12.apl.json deleted file mode 100644 index 02891823ad..0000000000 --- a/ui/druid/balance/apls/t12.apl.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "type": "TypeAPL", - "prepullActions": [ - {"action":{"castSpell":{"spellId":{"spellId":88747}}},"doAtValue":{"const":{"val":"-5s"}}}, - {"action":{"castSpell":{"spellId":{"spellId":88747}}},"doAtValue":{"const":{"val":"-4s"}}}, - {"action":{"castSpell":{"spellId":{"spellId":88747}}},"doAtValue":{"const":{"val":"-3s"}}}, - {"action":{"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}},"doAtValue":{"const":{"val":"-2s"}}}, - {"action":{"castSpell":{"spellId":{"spellId":2912}}},"doAtValue":{"const":{"val":"-2s"}}}, - {"action":{"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}},"doAtValue":{"const":{"val":"-1.5s"}},"hide":true}, - {"action":{"castSpell":{"spellId":{"spellId":5176}}},"doAtValue":{"const":{"val":"-1.5s"}},"hide":true} - ], - "priorityList": [ - {"action":{"condition":{"cmp":{"op":"OpGt","lhs":{"currentTime":{}},"rhs":{"const":{"val":"2s"}}}},"castSpell":{"spellId":{"spellId":2825,"tag":-1}}}}, - {"action":{"condition":{"and":{"vals":[{"druidCurrentEclipsePhase":{"eclipsePhase":"NeutralPhase"}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":61345}}}}},{"cmp":{"op":"OpLt","lhs":{"currentTime":{}},"rhs":{"const":{"val":"2s"}}}}]}},"castSpell":{"spellId":{"spellId":8921}}}}, - {"action":{"castSpell":{"spellId":{"spellId":33831}}}}, - {"action":{"condition":{"or":{"vals":[{"cmp":{"op":"OpGe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"100"}}}}]}},"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}}}, - {"action":{"condition":{"or":{"vals":[{"cmp":{"op":"OpGe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"100"}}}},{"cmp":{"op":"OpGe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"100"}}}}]}},"autocastOtherCooldowns":{}}}, - {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpGe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"100"}}}}]}},"castSpell":{"spellId":{"spellId":48505}}}}, - {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48517}}},{"or":{"vals":[{"and":{"vals":[{"cmp":{"op":"OpLe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"15"}}}},{"cmp":{"op":"OpGe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"1"}}}}]}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":5570}}},"rhs":{"dotTickFrequency":{"spellId":{"spellId":5570}}}}}]}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":5570}}},"rhs":{"math":{"op":"OpMul","lhs":{"dotTickFrequency":{"spellId":{"spellId":5570}}},"rhs":{"const":{"val":"4"}}}}}}]}},"strictSequence":{"actions":[{"castSpell":{"spellId":{"spellId":5570}}},{"castSpell":{"spellId":{"spellId":93402}}}]}}}, - {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48518}}},{"or":{"vals":[{"and":{"vals":[{"cmp":{"op":"OpLe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"20"}}}},{"cmp":{"op":"OpGe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"1"}}}}]}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":5570}}},"rhs":{"dotTickFrequency":{"spellId":{"spellId":5570}}}}}]}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":5570}}},"rhs":{"math":{"op":"OpMul","lhs":{"dotTickFrequency":{"spellId":{"spellId":5570}}},"rhs":{"const":{"val":"8"}}}}}}]}},"strictSequence":{"actions":[{"castSpell":{"spellId":{"spellId":5570}}},{"castSpell":{"spellId":{"spellId":8921}}}]}}}, - {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48518}}},{"auraIsActive":{"auraId":{"spellId":93399}}},{"or":{"vals":[{"cmp":{"op":"OpGt","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"10"}}}},{"cmp":{"op":"OpEq","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"0"}}}}]}}]}},"castSpell":{"spellId":{"spellId":78674}}}}, - {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48517}}},{"auraIsActive":{"auraId":{"spellId":93399}}},{"or":{"vals":[{"cmp":{"op":"OpGt","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"8"}}}},{"cmp":{"op":"OpEq","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"0"}}}}]}}]}},"castSpell":{"spellId":{"spellId":78674}}}}, - {"action":{"condition":{"and":{"vals":[{"not":{"val":{"druidCurrentEclipsePhase":{"eclipsePhase":"SolarPhase"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"100"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"80"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"60"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"40"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"20"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"0"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"25"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"50"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"75"}}}}]}},"castSpell":{"spellId":{"spellId":78674}}}}, - {"action":{"condition":{"and":{"vals":[{"druidCurrentEclipsePhase":{"eclipsePhase":"SolarPhase"}},{"cmp":{"op":"OpLe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"60"}}}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":48517}}}}}]}},"castSpell":{"spellId":{"spellId":78674}}}}, - {"action":{"condition":{"or":{"vals":[{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48518}}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":93399}}}}}]}},{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48517}}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":93399}}}}}]}}]}},"castSpell":{"spellId":{"spellId":78674}}}}, - {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48517}}},{"cmp":{"op":"OpEq","lhs":{"auraNumStacks":{"auraId":{"spellId":88747}}},"rhs":{"const":{"val":"3"}}}}]}},"castSpell":{"spellId":{"spellId":88751}}}}, - {"hide":true,"action":{"condition":{"not":{"val":{"druidCurrentEclipsePhase":{"eclipsePhase":"LunarPhase"}}}},"castSpell":{"spellId":{"spellId":5176}}}}, - {"action":{"condition":{"druidCurrentEclipsePhase":{"eclipsePhase":"SolarPhase"}},"castSpell":{"spellId":{"spellId":5176}}}}, - {"action":{"castSpell":{"spellId":{"spellId":2912}}}} - ] -} diff --git a/ui/druid/balance/apls/t13.apl.json b/ui/druid/balance/apls/t13.apl.json deleted file mode 100644 index 1a9a0f37e6..0000000000 --- a/ui/druid/balance/apls/t13.apl.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "type": "TypeAPL", - "prepullActions": [ - {"action":{"itemSwap":{"swapSet":"Swap1"}},"doAtValue":{"const":{"val":"-180s"}}}, - {"action":{"castSpell":{"spellId":{"spellId":88747}}},"doAtValue":{"const":{"val":"-10s"}}}, - {"action":{"castSpell":{"spellId":{"spellId":88747}}},"doAtValue":{"const":{"val":"-9s"}}}, - {"action":{"castSpell":{"spellId":{"spellId":88747}}},"doAtValue":{"const":{"val":"-8s"}}}, - {"action":{"activateAllStatBuffProcAuras":{"swapSet":"Swap1","statType1":7,"statType2":-1,"statType3":-1}},"doAtValue":{"const":{"val":"-3s"}}}, - {"action":{"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}},"doAtValue":{"const":{"val":"-2s"}},"hide":true}, - {"action":{"castSpell":{"spellId":{"spellId":2912}}},"doAtValue":{"const":{"val":"-2s"}},"hide":true}, - {"action":{"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}},"doAtValue":{"const":{"val":"-1.5s"}}}, - {"action":{"itemSwap":{"swapSet":"Main"}},"doAtValue":{"const":{"val":"-1.5s"}}}, - {"action":{"castSpell":{"spellId":{"spellId":5176}}},"doAtValue":{"const":{"val":"-1.5s"}}} - ], - "priorityList": [ - {"action":{"condition":{"cmp":{"op":"OpGt","lhs":{"currentTime":{}},"rhs":{"const":{"val":"2s"}}}},"castSpell":{"spellId":{"spellId":2825,"tag":-1}}}}, - {"action":{"condition":{"and":{"vals":[{"druidCurrentEclipsePhase":{"eclipsePhase":"NeutralPhase"}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":61345}}}}},{"cmp":{"op":"OpLt","lhs":{"currentTime":{}},"rhs":{"const":{"val":"2s"}}}}]}},"castSpell":{"spellId":{"spellId":8921}}}}, - {"action":{"condition":{"and":{"vals":[{"or":{"vals":[{"auraIsKnown":{"auraId":{"spellId":109795}}},{"auraIsKnown":{"auraId":{"spellId":107970}}},{"auraIsKnown":{"auraId":{"spellId":109793}}}]}},{"cmp":{"op":"OpLt","lhs":{"currentTime":{}},"rhs":{"const":{"val":"12s"}}}},{"cmp":{"op":"OpGt","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"70"}}}}]}},"castSpell":{"spellId":{"spellId":48505}}}}, - {"action":{"castSpell":{"spellId":{"spellId":33831}}}}, - {"action":{"condition":{"or":{"vals":[{"cmp":{"op":"OpGe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"100"}}}}]}},"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}}}, - {"action":{"condition":{"or":{"vals":[{"cmp":{"op":"OpGe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"100"}}}},{"cmp":{"op":"OpGe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"100"}}}}]}},"autocastOtherCooldowns":{}}}, - {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpGe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"100"}}}}]}},"castSpell":{"spellId":{"spellId":48505}}}}, - {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48517}}},{"or":{"vals":[{"and":{"vals":[{"cmp":{"op":"OpLe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"15"}}}},{"cmp":{"op":"OpGe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"5"}}}}]}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":5570}}},"rhs":{"dotTickFrequency":{"spellId":{"spellId":5570}}}}}]}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":5570}}},"rhs":{"math":{"op":"OpMul","lhs":{"dotTickFrequency":{"spellId":{"spellId":5570}}},"rhs":{"const":{"val":"4"}}}}}}]}},"strictSequence":{"actions":[{"castSpell":{"spellId":{"spellId":5570}}},{"castSpell":{"spellId":{"spellId":93402}}}]}}}, - {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48518}}},{"or":{"vals":[{"and":{"vals":[{"cmp":{"op":"OpLe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"20"}}}},{"cmp":{"op":"OpGe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"1"}}}}]}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":5570}}},"rhs":{"dotTickFrequency":{"spellId":{"spellId":5570}}}}}]}},{"cmp":{"op":"OpLt","lhs":{"dotRemainingTime":{"spellId":{"spellId":5570}}},"rhs":{"math":{"op":"OpMul","lhs":{"dotTickFrequency":{"spellId":{"spellId":5570}}},"rhs":{"const":{"val":"6"}}}}}}]}},"strictSequence":{"actions":[{"castSpell":{"spellId":{"spellId":5570}}},{"castSpell":{"spellId":{"spellId":8921}}}]}}}, - {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48518}}},{"auraIsActive":{"auraId":{"spellId":93399}}},{"or":{"vals":[{"cmp":{"op":"OpGt","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"10"}}}},{"cmp":{"op":"OpEq","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"0"}}}}]}}]}},"castSpell":{"spellId":{"spellId":78674}}}}, - {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48517}}},{"auraIsActive":{"auraId":{"spellId":93399}}},{"or":{"vals":[{"cmp":{"op":"OpGt","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"8"}}}},{"cmp":{"op":"OpEq","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"0"}}}}]}}]}},"castSpell":{"spellId":{"spellId":78674}}}}, - {"action":{"condition":{"and":{"vals":[{"not":{"val":{"druidCurrentEclipsePhase":{"eclipsePhase":"SolarPhase"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"100"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"80"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"60"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"40"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"20"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"0"}}}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":99049}}}}}]}},"castSpell":{"spellId":{"spellId":78674}}}}, - {"action":{"condition":{"and":{"vals":[{"not":{"val":{"druidCurrentEclipsePhase":{"eclipsePhase":"SolarPhase"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"100"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"80"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"60"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"40"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"20"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"0"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"25"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"50"}}}},{"cmp":{"op":"OpNe","lhs":{"currentSolarEnergy":{}},"rhs":{"const":{"val":"75"}}}},{"auraIsActive":{"auraId":{"spellId":99049}}}]}},"castSpell":{"spellId":{"spellId":78674}}}}, - {"action":{"condition":{"and":{"vals":[{"druidCurrentEclipsePhase":{"eclipsePhase":"SolarPhase"}},{"cmp":{"op":"OpLe","lhs":{"currentLunarEnergy":{}},"rhs":{"const":{"val":"60"}}}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":48517}}}}}]}},"castSpell":{"spellId":{"spellId":78674}}}}, - {"action":{"condition":{"or":{"vals":[{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48518}}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":93399}}}}}]}},{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48517}}},{"not":{"val":{"auraIsActive":{"auraId":{"spellId":93399}}}}}]}}]}},"castSpell":{"spellId":{"spellId":78674}}}}, - {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"auraId":{"spellId":48517}}},{"cmp":{"op":"OpEq","lhs":{"auraNumStacks":{"auraId":{"spellId":88747}}},"rhs":{"const":{"val":"3"}}}}]}},"castSpell":{"spellId":{"spellId":88751}}}}, - {"action":{"condition":{"druidCurrentEclipsePhase":{"eclipsePhase":"LunarPhase"}},"castSpell":{"spellId":{"spellId":2912}}}}, - {"action":{"castSpell":{"spellId":{"spellId":5176}}}} - ] -} diff --git a/ui/druid/balance/gear_sets/preraid.gear.json b/ui/druid/balance/gear_sets/preraid.gear.json index 79f9282a9c..70b32eb4bb 100644 --- a/ui/druid/balance/gear_sets/preraid.gear.json +++ b/ui/druid/balance/gear_sets/preraid.gear.json @@ -1,21 +1,20 @@ { "items": [ - {"id":78791,"enchant":4207,"gems":[68780,52207],"reforging":119}, - {"id":77088,"reforging":134}, - {"id":71450,"randomSuffix":-285,"enchant":4200,"gems":[52207],"reforging":162}, - {"id":77098,"enchant":4115,"gems":[52207],"reforging":138}, - {"id":78757,"enchant":4102,"gems":[52207,52207,52208],"reforging":167}, - {"id":71995,"enchant":4257,"gems":[52207,52207,0]}, - {"id":78771,"enchant":4068,"gems":[52207,0]}, - {"id":77181,"gems":[52207,52236,52207],"reforging":119}, - {"id":78809,"enchant":4110,"gems":[52207,52207,52207],"reforging":162}, - {"id":77172,"enchant":4104,"gems":[52207,52208]}, - {"id":77108,"gems":[52208],"reforging":134}, - {"id":78496,"gems":[52207],"reforging":119}, - {"id":77975}, - {"id":69110}, - {"id":78484,"enchant":4097}, - {"id":78441,"enchant":4091,"gems":[52207],"reforging":119}, - {"id":77082,"gems":[52207],"reforging":147} + { "id": 77535, "gems": [76885, 77546, 77542] }, + { "id": 90596, "reforging": 145 }, + { "id": 81690, "enchant": 4806, "gems": [76694], "reforging": 145 }, + { "id": 81084, "enchant": 4423, "reforging": 141 }, + { "id": 85850, "enchant": 4419, "gems": [76694, 76686], "reforging": 167 }, + { "id": 81179, "enchant": 4414, "gems": [0], "reforging": 167 }, + { "id": 85849, "enchant": 4430, "gems": [76668, 0] }, + { "id": 81238, "gems": [76694], "reforging": 117 }, + { "id": 81093, "enchant": 4825, "gems": [76694], "reforging": 119 }, + { "id": 81111, "enchant": 4429, "gems": [76668] }, + { "id": 90859, "reforging": 138 }, + { "id": 87550, "reforging": 162 }, + { "id": 79331 }, + { "id": 81192 }, + { "id": 87544, "enchant": 4442, "reforging": 167 }, + { "id": 79334, "enchant": 4434, "reforging": 167 } ] } diff --git a/ui/druid/balance/gear_sets/t11.gear.json b/ui/druid/balance/gear_sets/t11.gear.json deleted file mode 100644 index 3934f7ce80..0000000000 --- a/ui/druid/balance/gear_sets/t11.gear.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "items": [ - {"id":65200,"enchant":4207,"gems":[68780,52236],"reforging":141}, - {"id":65112,"reforging":162}, - {"id":65203,"enchant":4200,"gems":[52207],"reforging":162}, - {"id":60232,"enchant":4115,"gems":[52207],"reforging":162}, - {"id":65045,"enchant":4102,"gems":[52207,52207],"reforging":167}, - {"id":65021,"enchant":4257,"gems":[0],"reforging":167}, - {"id":65199,"enchant":4068,"gems":[52207,0],"reforging":141}, - {"id":65374,"randomSuffix":-231,"gems":[52208,52207]}, - {"id":65201,"enchant":4110,"gems":[52207,52236]}, - {"id":60236,"enchant":4104,"gems":[52236,52207],"reforging":167}, - {"id":65123,"reforging":166}, - {"id":65373,"randomSuffix":-131}, - {"id":65105}, - {"id":62047,"reforging":167}, - {"id":65041,"enchant":4097}, - {"id":65133,"enchant":4091,"reforging":134}, - {"id":64672,"gems":[52207],"reforging":141} - ] -} diff --git a/ui/druid/balance/gear_sets/t12.gear.json b/ui/druid/balance/gear_sets/t12.gear.json deleted file mode 100644 index 0be602a768..0000000000 --- a/ui/druid/balance/gear_sets/t12.gear.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "items": [ - { "id": 71497, "enchant": 4207, "gems": [68780, 52208], "reforging": 162 }, - { "id": 71472, "gems": [52207], "reforging": 162 }, - { "id": 71450, "randomSuffix": -285, "enchant": 4200, "gems": [52208], "reforging": 162 }, - { "id": 71434, "enchant": 4115, "reforging": 145 }, - { "id": 71499, "enchant": 4102, "gems": [52207, 52207], "reforging": 162 }, - { "id": 71463, "enchant": 4257, "gems": [0] }, - { "id": 71496, "enchant": 4068, "gems": [52208, 0] }, - { "id": 71249, "gems": [52207, 52207], "reforging": 141 }, - { "id": 71498, "enchant": 4110, "gems": [52207, 52207], "reforging": 145 }, - { "id": 71436, "enchant": 4104, "gems": [52208], "reforging": 117 }, - { "id": 71217, "gems": [52207], "reforging": 140 }, - { "id": 71449, "reforging": 145 }, - { "id": 69110 }, - { "id": 62047, "reforging": 167 }, - { "id": 71086, "enchant": 4097, "gems": [52207, 52207, 52207], "reforging": 134 }, - {}, - { "id": 71580, "gems": [52208] } - ] -} diff --git a/ui/druid/balance/gear_sets/t13_item_swap.gear.json b/ui/druid/balance/gear_sets/t13_item_swap.gear.json deleted file mode 100644 index 8a9e7abc79..0000000000 --- a/ui/druid/balance/gear_sets/t13_item_swap.gear.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "items": [ - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {}, - { - "id": 77989 - }, - {}, - { - "id": 78477, - "enchant": 4083 - }, - {}, - {} - ] -} diff --git a/ui/druid/balance/gear_sets/t14.gear.json b/ui/druid/balance/gear_sets/t14.gear.json new file mode 100644 index 0000000000..e2faa4b295 --- /dev/null +++ b/ui/druid/balance/gear_sets/t14.gear.json @@ -0,0 +1,20 @@ +{ + "items": [ + { "id": 86934, "gems": [76885, 76686], "reforging": 145 }, + { "id": 86976, "reforging": 145 }, + { "id": 86937, "enchant": 4806, "gems": [76686], "reforging": 117 }, + { "id": 90512, "enchant": 4423 }, + { "id": 86936, "enchant": 4419, "gems": [76660, 76660], "reforging": 147 }, + { "id": 87054, "enchant": 4414, "gems": [0], "reforging": 117 }, + { "id": 86933, "enchant": 4430, "gems": [0], "reforging": 119 }, + { "id": 87019, "gems": [76660, 76651, 76694], "reforging": 119 }, + { "id": 89948, "enchant": 4825, "gems": [76694, 76651], "reforging": 117 }, + { "id": 90514, "enchant": 4429, "gems": [76668], "reforging": 119 }, + { "id": 90511 }, + { "id": 86949, "reforging": 145 }, + { "id": 79331 }, + { "id": 87065 }, + { "id": 90513, "enchant": 4442, "gems": [76651], "reforging": 145 }, + { "id": 86960, "enchant": 4434, "reforging": 145 } + ] +} diff --git a/ui/druid/balance/gear_sets/t15.gear.json b/ui/druid/balance/gear_sets/t15.gear.json new file mode 100644 index 0000000000..058618dd9c --- /dev/null +++ b/ui/druid/balance/gear_sets/t15.gear.json @@ -0,0 +1,21 @@ +{ + "items": [ + { "id": 95246, "gems": [76885, 76699], "reforging": 116 }, + { "id": 96909, "gems": [76697], "reforging": 117 }, + { "id": 95249, "enchant": 4806, "gems": [76668, 76697] }, + { "id": 98150, "enchant": 4423, "gems": [76668] }, + { "id": 95032, "enchant": 4419, "gems": [76660, 76660, 76660], "reforging": 166 }, + { "id": 96758, "enchant": 4414, "gems": [0], "reforging": 117 }, + { "id": 95245, "enchant": 4430, "gems": [76660, 0], "reforging": 167 }, + { "id": 94997, "gems": [76641, 76641, 76694], "reforging": 117 }, + { "id": 95247, "enchant": 4825, "gems": [76697, 76641] }, + { "id": 95006, "enchant": 4429, "gems": [76697, 76641], "reforging": 167 }, + { "id": 95018, "gems": [76641], "reforging": 116 }, + { "id": 96901, "gems": [76641], "reforging": 167 }, + { "id": 96785, "reforging": 138 }, + { "id": 96888 }, + { "id": 96931, "enchant": 4442, "gems": [76660], "reforging": 116 }, + { "id": 96934, "enchant": 4434, "gems": [76660] }, + {} + ] +} diff --git a/ui/druid/balance/gear_sets/t13.gear.json b/ui/druid/balance/gear_sets/t16.gear.json similarity index 100% rename from ui/druid/balance/gear_sets/t13.gear.json rename to ui/druid/balance/gear_sets/t16.gear.json diff --git a/ui/druid/balance/presets.ts b/ui/druid/balance/presets.ts index c36879eea5..ae22b0597f 100644 --- a/ui/druid/balance/presets.ts +++ b/ui/druid/balance/presets.ts @@ -1,27 +1,20 @@ import * as PresetUtils from '../../core/preset_utils.js'; -import { ConsumesSpec, Debuffs, Glyphs, IndividualBuffs, PartyBuffs, Profession, RaidBuffs, Stat, UnitReference } from '../../core/proto/common.js'; +import { ConsumesSpec, Debuffs, Glyphs, IndividualBuffs, PartyBuffs, Profession, PseudoStat, RaidBuffs, Stat, UnitReference } from '../../core/proto/common.js'; import { BalanceDruid_Options as BalanceDruidOptions, DruidMajorGlyph } from '../../core/proto/druid.js'; import { SavedTalents } from '../../core/proto/ui.js'; -import { Stats } from '../../core/proto_utils/stats'; -import T11Apl from './apls/t11.apl.json'; -import T12Apl from './apls/t12.apl.json'; -import T13Apl from './apls/t13.apl.json'; +import { Stats, UnitStat, UnitStatPresets } from '../../core/proto_utils/stats'; +import StandardApl from './apls/standard.apl.json'; import PreraidGear from './gear_sets/preraid.gear.json'; -import T11Gear from './gear_sets/t11.gear.json'; -import T12Gear from './gear_sets/t12.gear.json'; -import T13Gear from './gear_sets/t13.gear.json'; -import T13ItemSwapGear from './gear_sets/t13_item_swap.gear.json'; +import T14Gear from './gear_sets/t14.gear.json'; +import T15Gear from './gear_sets/t15.gear.json'; +import T16Gear from './gear_sets/t16.gear.json'; export const PreraidPresetGear = PresetUtils.makePresetGear('Pre-raid', PreraidGear); -export const T11PresetGear = PresetUtils.makePresetGear('T11', T11Gear); -export const T12PresetGear = PresetUtils.makePresetGear('T12', T12Gear); -export const T13PresetGear = PresetUtils.makePresetGear('T13', T13Gear); +export const T14PresetGear = PresetUtils.makePresetGear('T14', T14Gear); +export const T15PresetGear = PresetUtils.makePresetGear('T15', T15Gear); +export const T16PresetGear = PresetUtils.makePresetGear('T16', T16Gear); -export const T13PresetItemSwapGear = PresetUtils.makePresetItemSwapGear('T13 - Item Swap', T13ItemSwapGear); - -export const T11PresetRotation = PresetUtils.makePresetAPLRotation('T11 4P', T11Apl); -export const T12PresetRotation = PresetUtils.makePresetAPLRotation('T12', T12Apl); -export const T13PresetRotation = PresetUtils.makePresetAPLRotation('T13', T13Apl); +export const StandardRotation = PresetUtils.makePresetAPLRotation('Standard', StandardApl); export const StandardEPWeights = PresetUtils.makePresetEpWeights( 'Standard', @@ -30,9 +23,9 @@ export const StandardEPWeights = PresetUtils.makePresetEpWeights( [Stat.StatSpirit]: 1.27, [Stat.StatSpellPower]: 1, [Stat.StatHitRating]: 1.27, - [Stat.StatCritRating]: 0.41, + [Stat.StatCritRating]: 0.56, [Stat.StatHasteRating]: 0.8, - [Stat.StatMasteryRating]: 0.56, + [Stat.StatMasteryRating]: 0.41, }), ); @@ -41,9 +34,11 @@ export const StandardEPWeights = PresetUtils.makePresetEpWeights( export const StandardTalents = { name: 'Standard', data: SavedTalents.create({ - talentsString: '', + talentsString: '113221', glyphs: Glyphs.create({ - major2: DruidMajorGlyph.GlyphOfRebirth, + major1: DruidMajorGlyph.GlyphOfStampedingRoar, + major2: DruidMajorGlyph.GlyphOfStampede, + major3: DruidMajorGlyph.GlyphOfRebirth, }), }), }; @@ -55,25 +50,27 @@ export const DefaultOptions = BalanceDruidOptions.create({ }); export const DefaultConsumables = ConsumesSpec.create({ - flaskId: 58086, // Flask of the Draconic Mind - foodId: 62290, // Seafood Magnifique Feast - potId: 58091, // Volcanic Potion - prepotId: 58091, // Volcanic Potion + flaskId: 76085, // Flask of the Warm Sun + foodId: 74650, // Mogu Fish Stew + potId: 76093, // Potion of the Jade Serpent + prepotId: 76093, // Potion of the Jade Serpent +}); + +export const DefaultRaidBuffs = RaidBuffs.create({ + markOfTheWild: true, // stats + darkIntent: true, // spell power + moonkinAura: true, // spell haste + leaderOfThePack: true, // crit % + blessingOfMight: true, // mastery + bloodlust: true, // major haste }); -export const DefaultRaidBuffs = RaidBuffs.create({}); export const DefaultIndividualBuffs = IndividualBuffs.create({}); export const DefaultPartyBuffs = PartyBuffs.create({}); export const DefaultDebuffs = Debuffs.create({ - // bloodFrenzy: true, - // sunderArmor: true, - // ebonPlaguebringer: true, - // mangle: true, - // criticalMass: true, - // demoralizingShout: true, - // frostFever: true, + curseOfElements: true, // spell dmg taken }); export const OtherDefaults = { @@ -82,30 +79,59 @@ export const OtherDefaults = { profession2: Profession.Tailoring, }; -export const PresetBuildPreraid = PresetUtils.makePresetBuild('Balance Pre-raid', { +export const PresetPreraidBuild = PresetUtils.makePresetBuild('Balance Pre-raid', { gear: PreraidPresetGear, talents: StandardTalents, - rotation: T13PresetRotation, + rotation: StandardRotation, epWeights: StandardEPWeights, }); -export const PresetBuildT11 = PresetUtils.makePresetBuild('Balance T11', { - gear: T11PresetGear, +export const T14PresetBuild = PresetUtils.makePresetBuild('Balance T14', { + gear: T14PresetGear, talents: StandardTalents, - rotation: T11PresetRotation, + rotation: StandardRotation, epWeights: StandardEPWeights, }); -export const PresetBuildT12 = PresetUtils.makePresetBuild('Balance T12', { - gear: T12PresetGear, +export const T15PresetBuild = PresetUtils.makePresetBuild('Balance T15', { + gear: T15PresetGear, talents: StandardTalents, - rotation: T12PresetRotation, + rotation: StandardRotation, epWeights: StandardEPWeights, }); -export const PresetBuildT13 = PresetUtils.makePresetBuild('Balance T13', { - gear: T13PresetGear, +export const T16PresetBuild = PresetUtils.makePresetBuild('Balance T16', { + gear: T16PresetGear, talents: StandardTalents, - rotation: T13PresetRotation, + rotation: StandardRotation, epWeights: StandardEPWeights, }); + +export const BALANCE_BREAKPOINTS: UnitStatPresets[] = [ + { + unitStat: UnitStat.fromPseudoStat(PseudoStat.PseudoStatSpellHastePercent), + presets: new Map([ + ['9-tick MF/SF', 5.5618], + ['10-tick MF/SF', 18.0272], + ['11-tick MF/SF', 30.4347], + ['12-tick MF/SF', 42.8444], + ['13-tick MF/SF', 55.3489], + ['14-tick MF/SF', 67.627], + ]), + }, +]; + +export const BALANCE_T14_4P_BREAKPOINTS: UnitStatPresets[] = [ + { + unitStat: UnitStat.fromPseudoStat(PseudoStat.PseudoStatSpellHastePercent), + presets: new Map([ + ['10-tick MF/SF', 3.2431], + ['11-tick MF/SF', 14.1536], + ['12-tick MF/SF', 24.9824], + ['13-tick MF/SF', 35.9227], + ['14-tick MF/SF', 46.7002], + ['15-tick MF/SF', 57.6013], + ['16-tick MF/SF', 68.4388], + ]), + }, +]; diff --git a/ui/druid/balance/sim.ts b/ui/druid/balance/sim.ts index 537903d269..4217b51e74 100644 --- a/ui/druid/balance/sim.ts +++ b/ui/druid/balance/sim.ts @@ -7,7 +7,8 @@ import { Player } from '../../core/player'; import { PlayerClasses } from '../../core/player_classes'; import { APLRotation, APLRotation_Type } from '../../core/proto/apl'; import { Faction, ItemSlot, PseudoStat, Race, Spec, Stat } from '../../core/proto/common'; -import { Stats, UnitStat } from '../../core/proto_utils/stats'; +import { StatCapType } from '../../core/proto/ui'; +import { StatCap, Stats, UnitStat } from '../../core/proto_utils/stats'; import * as DruidInputs from '../inputs'; import * as BalanceInputs from './inputs'; import * as Presets from './presets'; @@ -45,12 +46,23 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecBalanceDruid, { defaults: { // Default equipped gear. - gear: Presets.T13PresetGear.gear, + gear: Presets.PreraidPresetGear.gear, // Default EP weights for sorting gear in the gear picker. epWeights: Presets.StandardEPWeights.epWeights, // Default stat caps for the Reforge optimizer statCaps: (() => { - return new Stats().withPseudoStat(PseudoStat.PseudoStatSpellHitPercent, 17); + return new Stats().withPseudoStat(PseudoStat.PseudoStatSpellHitPercent, 15); + })(), + softCapBreakpoints: (() => { + const hasteSoftCapConfig = StatCap.fromPseudoStat(PseudoStat.PseudoStatSpellHastePercent, { + breakpoints: [...Presets.BALANCE_BREAKPOINTS.find(sc => sc.unitStat.equalsPseudoStat(PseudoStat.PseudoStatSpellHastePercent))!.presets].map( + ([_, value]) => value, + ), + capType: StatCapType.TypeThreshold, + postCapEPs: [0.53 * Mechanics.HASTE_RATING_PER_HASTE_PERCENT], + }); + + return [hasteSoftCapConfig]; })(), // Default consumes settings. consumables: Presets.DefaultConsumables, @@ -86,15 +98,14 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecBalanceDruid, { epWeights: [Presets.StandardEPWeights], // Preset talents that the user can quickly select. talents: [Presets.StandardTalents], - rotations: [Presets.T11PresetRotation, Presets.T12PresetRotation, Presets.T13PresetRotation], + rotations: [Presets.StandardRotation], // Preset gear configurations that the user can quickly select. - gear: [Presets.PreraidPresetGear, Presets.T11PresetGear, Presets.T12PresetGear, Presets.T13PresetGear], - itemSwaps: [Presets.T13PresetItemSwapGear], - builds: [Presets.PresetBuildPreraid, Presets.PresetBuildT11, Presets.PresetBuildT12, Presets.PresetBuildT13], + gear: [Presets.PreraidPresetGear, Presets.T14PresetGear /*Presets.T15PresetGear, Presets.T16PresetGear*/], + builds: [Presets.PresetPreraidBuild, Presets.T14PresetBuild /*Presets.T15PresetBuild, Presets.T16PresetBuild*/], }, autoRotation: (_player: Player): APLRotation => { - return Presets.T13PresetRotation.rotation.rotation!; + return Presets.StandardRotation.rotation.rotation!; }, raidSimPresets: [ @@ -112,10 +123,10 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecBalanceDruid, { defaultGear: { [Faction.Unknown]: {}, [Faction.Alliance]: { - 1: Presets.T13PresetGear.gear, + 1: Presets.PreraidPresetGear.gear, }, [Faction.Horde]: { - 1: Presets.T13PresetGear.gear, + 1: Presets.PreraidPresetGear.gear, }, }, }, @@ -126,7 +137,23 @@ export class BalanceDruidSimUI extends IndividualSimUI { constructor(parentElem: HTMLElement, player: Player) { super(parentElem, player, SPEC_CONFIG); player.sim.waitForInit().then(() => { - new ReforgeOptimizer(this); + new ReforgeOptimizer(this, { + updateSoftCaps: softCaps => { + const gear = player.getGear(); + const hasT144P = gear.getItemSetCount('Regalia of the Eternal Blossom') >= 4; + + if (hasT144P) { + const softCapToModify = softCaps.find(sc => sc.unitStat.equalsPseudoStat(PseudoStat.PseudoStatSpellHastePercent)); + if (softCapToModify) { + softCapToModify.breakpoints = [ + ...Presets.BALANCE_T14_4P_BREAKPOINTS.find(sc => sc.unitStat.equalsPseudoStat(PseudoStat.PseudoStatSpellHastePercent))!.presets, + ].map(([_, value]) => value); + } + } + + return softCaps; + }, + }); }); } } diff --git a/ui/index.html b/ui/index.html index 3a6efef063..c63219a235 100644 --- a/ui/index.html +++ b/ui/index.html @@ -187,7 +187,7 @@

Mists of Pandaria

Druid Balance - Not Yet Supported + Alpha