diff --git a/sim/core/energy.go b/sim/core/energy.go index 225a067ce0..f05df7cfba 100644 --- a/sim/core/energy.go +++ b/sim/core/energy.go @@ -194,6 +194,19 @@ func (eb *energyBar) ResetEnergyTick(sim *Simulation) { sim.RescheduleTask(eb.nextEnergyTick) } +// Used for dynamic updates to maximum Energy, such as from the Druid Primal Madness talent +func (eb *energyBar) UpdateMaxEnergy(sim *Simulation, newMaxEnergy float64, metrics *ResourceMetrics) { + oldMaxEnergy := eb.maxEnergy + eb.maxEnergy = newMaxEnergy + energyDelta := newMaxEnergy - oldMaxEnergy + + if energyDelta >= 0 { + eb.AddEnergy(sim, energyDelta, metrics) + } else { + eb.SpendEnergy(sim, min(-energyDelta, eb.currentEnergy), metrics) + } +} + func (eb *energyBar) AddComboPoints(sim *Simulation, pointsToAdd int32, metrics *ResourceMetrics) { newComboPoints := min(eb.comboPoints+pointsToAdd, 5) metrics.AddEvent(float64(pointsToAdd), float64(newComboPoints-eb.comboPoints)) diff --git a/sim/core/health.go b/sim/core/health.go index 16e375fb32..71011411de 100644 --- a/sim/core/health.go +++ b/sim/core/health.go @@ -87,6 +87,17 @@ func (hb *healthBar) RemoveHealth(sim *Simulation, amount float64) { hb.currentHealth = newHealth } +// Used for dynamic updates to maximum health from "Last Stand" effects +func (hb *healthBar) UpdateMaxHealth(sim *Simulation, bonusHealth float64, metrics *ResourceMetrics) { + hb.unit.AddStatsDynamic(sim, stats.Stats{stats.Health: bonusHealth}) + + if bonusHealth >= 0 { + hb.GainHealth(sim, bonusHealth, metrics) + } else { + hb.RemoveHealth(sim, min(-bonusHealth, hb.currentHealth - 1)) // Last Stand effects always leave the player with at least 1 HP when they expire + } +} + var ChanceOfDeathAuraLabel = "Chance of Death" func (character *Character) trackChanceOfDeath(healingModel *proto.HealingModel) { diff --git a/sim/death_knight/_vampiric_blood.go b/sim/death_knight/_vampiric_blood.go index 1177377f8d..8b19773383 100644 --- a/sim/death_knight/_vampiric_blood.go +++ b/sim/death_knight/_vampiric_blood.go @@ -26,13 +26,12 @@ func (dk *DeathKnight) registerVampiricBloodSpell() { Duration: time.Second*10 + core.TernaryDuration(dk.HasMajorGlyph(proto.DeathKnightMajorGlyph_GlyphOfVampiricBlood), 5*time.Second, 0), OnGain: func(aura *core.Aura, sim *core.Simulation) { bonusHealth = dk.MaxHealth() * 0.15 - dk.AddStatsDynamic(sim, stats.Stats{stats.Health: bonusHealth}) - dk.GainHealth(sim, bonusHealth, healthMetrics) + dk.UpdateMaxHealth(sim, bonusHealth, healthMetrics) dk.PseudoStats.HealingTakenMultiplier *= 1.35 }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { - dk.AddStatsDynamic(sim, stats.Stats{stats.Health: -bonusHealth}) + dk.UpdateMaxHealth(sim, -bonusHealth, healthMetrics) dk.PseudoStats.HealingTakenMultiplier /= 1.35 }, }) diff --git a/sim/druid/_berserk.go b/sim/druid/berserk.go similarity index 73% rename from sim/druid/_berserk.go rename to sim/druid/berserk.go index c71ff2777d..18db717bed 100644 --- a/sim/druid/_berserk.go +++ b/sim/druid/berserk.go @@ -13,7 +13,8 @@ func (druid *Druid) registerBerserkCD() { } actionId := core.ActionID{SpellID: 50334} - glyphBonus := core.TernaryDuration(druid.HasMajorGlyph(proto.DruidMajorGlyph_GlyphOfBerserk), time.Second*5.0, 0.0) + glyphBonus := core.TernaryDuration(druid.HasPrimeGlyph(proto.DruidPrimeGlyph_GlyphOfBerserk), time.Second*10.0, 0.0) + primalMadnessRage := 6.0 * float64(druid.Talents.PrimalMadness) var affectedSpells []*DruidSpell druid.BerserkAura = druid.RegisterAura(core.Aura{ @@ -35,11 +36,19 @@ func (druid *Druid) registerBerserkCD() { for _, spell := range affectedSpells { spell.CostMultiplier -= 0.5 } + + if druid.PrimalMadnessAura != nil { + druid.PrimalMadnessAura.Activate(sim) + } }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { for _, spell := range affectedSpells { spell.CostMultiplier += 0.5 } + + if druid.PrimalMadnessAura.IsActive() && !druid.TigersFuryAura.IsActive() { + druid.PrimalMadnessAura.Deactivate(sim) + } }, }) @@ -48,9 +57,6 @@ func (druid *Druid) registerBerserkCD() { Flags: core.SpellFlagAPL, Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: time.Second, - }, CD: core.Cooldown{ Timer: druid.NewTimer(), Duration: time.Minute * 3, @@ -59,6 +65,10 @@ func (druid *Druid) registerBerserkCD() { }, ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { druid.BerserkAura.Activate(sim) + + if (primalMadnessRage > 0) && druid.InForm(Bear) { + druid.AddRage(sim, primalMadnessRage, druid.PrimalMadnessRageMetrics) + } }, }) diff --git a/sim/druid/druid.go b/sim/druid/druid.go index 1800866239..175b3e20e2 100644 --- a/sim/druid/druid.go +++ b/sim/druid/druid.go @@ -48,6 +48,7 @@ type Druid struct { Maul *DruidSpell MaulQueueSpell *DruidSpell Moonfire *DruidSpell + Pulverize *DruidSpell Rebirth *DruidSpell Rake *DruidSpell Rip *DruidSpell @@ -79,6 +80,8 @@ type Druid struct { MoonkinT84PCAura *core.Aura NaturesGraceProcAura *core.Aura PredatoryInstinctsAura *core.Aura + PrimalMadnessAura *core.Aura + PulverizeAura *core.Aura SavageDefenseAura *core.Aura SurvivalInstinctsAura *core.Aura TigersFuryAura *core.Aura @@ -89,6 +92,7 @@ type Druid struct { BleedCategories core.ExclusiveCategoryArray + PrimalMadnessRageMetrics *core.ResourceMetrics PrimalPrecisionRecoveryMetrics *core.ResourceMetrics SavageRoarDurationTable [6]time.Duration @@ -131,12 +135,9 @@ func (druid *Druid) AddRaidBuffs(raidBuffs *proto.RaidBuffs) { // raidBuffs.MoonkinAura = proto.TristateEffect_TristateEffectImproved // } // } - // if druid.InForm(Cat|Bear) && druid.Talents.LeaderOfThePack { - // raidBuffs.LeaderOfThePack = max(raidBuffs.LeaderOfThePack, proto.TristateEffect_TristateEffectRegular) - // if druid.Talents.ImprovedLeaderOfThePack > 0 { - // raidBuffs.LeaderOfThePack = proto.TristateEffect_TristateEffectImproved - // } - // } + if druid.InForm(Cat|Bear) && druid.Talents.LeaderOfThePack { + raidBuffs.LeaderOfThePack = true + } } // func (druid *Druid) BalanceCritMultiplier() float64 { diff --git a/sim/druid/_enrage.go b/sim/druid/enrage.go similarity index 71% rename from sim/druid/_enrage.go rename to sim/druid/enrage.go index f6545c143f..48d9256eab 100644 --- a/sim/druid/_enrage.go +++ b/sim/druid/enrage.go @@ -11,31 +11,20 @@ func (druid *Druid) registerEnrageSpell() { actionID := core.ActionID{SpellID: 5229} rageMetrics := druid.NewRageMetrics(actionID) - instantRage := []float64{20, 24, 27, 30}[druid.Talents.Intensity] + instantRage := 20.0 + primalMadnessRage := 6.0 * float64(druid.Talents.PrimalMadness) dmgBonus := 0.05 * float64(druid.Talents.KingOfTheJungle) - t10_4p := druid.HasSetBonus(ItemSetLasherweaveBattlegear, 4) - druid.EnrageAura = druid.RegisterAura(core.Aura{ Label: "Enrage Aura", ActionID: actionID, Duration: 10 * time.Second, OnGain: func(aura *core.Aura, sim *core.Simulation) { - druid.PseudoStats.DamageDealtMultiplier *= 1.0 + dmgBonus - if !t10_4p { - druid.ApplyDynamicEquipScaling(sim, stats.Armor, 0.84) - } else { - druid.PseudoStats.DamageTakenMultiplier *= 0.88 - } + druid.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] *= 1.0 + dmgBonus }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { - druid.PseudoStats.DamageDealtMultiplier /= 1.0 + dmgBonus - if !t10_4p { - druid.RemoveDynamicEquipScaling(sim, stats.Armor, 0.84) - } else { - druid.PseudoStats.DamageTakenMultiplier /= 0.88 - } + druid.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] /= 1.0 + dmgBonus }, }) @@ -65,6 +54,10 @@ func (druid *Druid) registerEnrageSpell() { }) druid.EnrageAura.Activate(sim) + + if primalMadnessRage > 0 { + druid.AddRage(sim, primalMadnessRage, druid.PrimalMadnessRageMetrics) + } }, }) diff --git a/sim/druid/feral/feral.go b/sim/druid/feral/feral.go index b8820a3100..5bafacffa9 100644 --- a/sim/druid/feral/feral.go +++ b/sim/druid/feral/feral.go @@ -44,8 +44,7 @@ func NewFeralDruid(character *core.Character, options *proto.Player) *FeralDruid // //cat.maxRipTicks = cat.MaxRipTicks() cat.EnableEnergyBar(100.0) - - // cat.EnableRageBar(core.RageBarOptions{RageMultiplier: 1, MHSwingSpeed: 2.5}) + cat.EnableRageBar(core.RageBarOptions{RageMultiplier: 1, MHSwingSpeed: 2.5}) cat.EnableAutoAttacks(cat, core.AutoAttackOptions{ // Base paw weapon. diff --git a/sim/druid/ferocious_bite.go b/sim/druid/ferocious_bite.go index 0749018cb0..3313832300 100644 --- a/sim/druid/ferocious_bite.go +++ b/sim/druid/ferocious_bite.go @@ -8,6 +8,7 @@ import ( func (druid *Druid) registerFerociousBiteSpell() { dmgPerComboPoint := 290.0 + core.TernaryFloat64(druid.Ranged().ID == 25667, 14, 0) + ripRefreshChance := 0.5 * float64(druid.Talents.BloodInTheWater) druid.FerociousBite = druid.RegisterSpell(Cat, core.SpellConfig{ ActionID: core.ActionID{SpellID: 48577}, @@ -31,7 +32,7 @@ func (druid *Druid) registerFerociousBiteSpell() { }, BonusCritRating: 0 + - core.TernaryFloat64(druid.AssumeBleedActive, 25.0/3.0*float64(druid.Talents.RendAndTear)*core.CritRatingPerCritChance, 0), + core.TernaryFloat64(druid.AssumeBleedActive, []float64{0.0, 8.0, 17.0, 25.0}[druid.Talents.RendAndTear] * core.CritRatingPerCritChance, 0), DamageMultiplier: 1 + 0.05*float64(druid.Talents.FeralAggression), CritMultiplier: druid.DefaultMeleeCritMultiplier(), ThreatMultiplier: 1, @@ -52,6 +53,13 @@ func (druid *Druid) registerFerociousBiteSpell() { if result.Landed() { druid.SpendEnergy(sim, excessEnergy, spell.Cost.(*core.EnergyCost).ResourceMetrics) druid.SpendComboPoints(sim, spell.ComboPointMetrics()) + + // Blood in the Water + ripDot := druid.Rip.Dot(target) + + if sim.IsExecutePhase25() && ripDot.IsActive() && sim.Proc(ripRefreshChance, "Blood in the Water") { + ripDot.Apply(sim) + } } else { spell.IssueRefund(sim) } diff --git a/sim/druid/forms.go b/sim/druid/forms.go index 159a818038..a756688a88 100644 --- a/sim/druid/forms.go +++ b/sim/druid/forms.go @@ -169,8 +169,8 @@ func (druid *Druid) registerCatFormSpell() { Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagAPL, ManaCost: core.ManaCostOptions{ - BaseCost: 0.35, - Multiplier: (1 - 0.2*float64(druid.Talents.KingOfTheJungle)) * (1 - 0.1*float64(druid.Talents.NaturalShapeshifter)), + BaseCost: 0.05, + Multiplier: 1 - 0.1*float64(druid.Talents.NaturalShapeshifter), }, Cast: core.CastConfig{ DefaultCast: core.Cast{ @@ -279,6 +279,7 @@ func (druid *Druid) registerBearFormSpell() { druid.UpdateManaRegenRates() druid.EnrageAura.Deactivate(sim) druid.MaulQueueAura.Deactivate(sim) + druid.PulverizeAura.Deactivate(sim) } }, }) @@ -292,8 +293,8 @@ func (druid *Druid) registerBearFormSpell() { Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagAPL, ManaCost: core.ManaCostOptions{ - BaseCost: 0.35, - Multiplier: (1 - 0.2*float64(druid.Talents.KingOfTheJungle)) * (1 - 0.1*float64(druid.Talents.NaturalShapeshifter)), + BaseCost: 0.05, + Multiplier: 1 - 0.1*float64(druid.Talents.NaturalShapeshifter), }, Cast: core.CastConfig{ DefaultCast: core.Cast{ diff --git a/sim/druid/_frenzied_regeneration.go b/sim/druid/frenzied_regeneration.go similarity index 78% rename from sim/druid/_frenzied_regeneration.go rename to sim/druid/frenzied_regeneration.go index 993fb9c8f1..d647e8bba6 100644 --- a/sim/druid/_frenzied_regeneration.go +++ b/sim/druid/frenzied_regeneration.go @@ -14,37 +14,45 @@ func (druid *Druid) registerFrenziedRegenerationCD() { cdTimer := druid.NewTimer() cd := time.Minute * 3 - healingMulti := core.TernaryFloat64(druid.HasMajorGlyph(proto.DruidMajorGlyph_GlyphOfFrenziedRegeneration), 1.2, 1.0) + isGlyphed := druid.HasMajorGlyph(proto.DruidMajorGlyph_GlyphOfFrenziedRegeneration) + healingMulti := core.TernaryFloat64(isGlyphed, 1.3, 1.0) + var bonusHealth float64 druid.FrenziedRegenerationAura = druid.RegisterAura(core.Aura{ Label: "Frenzied Regeneration", ActionID: actionID, - Duration: time.Second * 10, + Duration: time.Second * 20, OnGain: func(aura *core.Aura, sim *core.Simulation) { druid.PseudoStats.HealingTakenMultiplier *= healingMulti + bonusHealth = druid.MaxHealth() * 0.3 + druid.UpdateMaxHealth(sim, bonusHealth, healthMetrics) }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { druid.PseudoStats.HealingTakenMultiplier /= healingMulti + druid.UpdateMaxHealth(sim, -bonusHealth, healthMetrics) }, }) druid.FrenziedRegeneration = druid.RegisterSpell(Bear, core.SpellConfig{ ActionID: actionID, Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, CD: core.Cooldown{ Timer: cdTimer, Duration: cd, }, - IgnoreHaste: true, }, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + druid.FrenziedRegenerationAura.Activate(sim) + + if isGlyphed { + return + } + core.StartPeriodicAction(sim, core.PeriodicActionOptions{ - NumTicks: 10, + NumTicks: 20, Period: time.Second * 1, + Priority: core.ActionPriorityDOT, OnAction: func(sim *core.Simulation) { rageDumped := min(druid.CurrentRage(), 10.0) healthGained := rageDumped * 0.3 / 100 * druid.MaxHealth() * druid.PseudoStats.HealingTakenMultiplier @@ -55,8 +63,6 @@ func (druid *Druid) registerFrenziedRegenerationCD() { } }, }) - - druid.FrenziedRegenerationAura.Activate(sim) }, }) diff --git a/sim/druid/_maul.go b/sim/druid/maul.go similarity index 90% rename from sim/druid/_maul.go rename to sim/druid/maul.go index 540d080114..cee9499f25 100644 --- a/sim/druid/_maul.go +++ b/sim/druid/maul.go @@ -14,6 +14,7 @@ func (druid *Druid) registerMaulSpell() { } numHits := core.TernaryInt32(druid.HasMajorGlyph(proto.DruidMajorGlyph_GlyphOfMaul) && druid.Env.GetNumTargets() > 1, 2, 1) + rendAndTearMod := []float64{1.0, 1.07, 1.13, 1.2}[druid.Talents.RendAndTear] druid.Maul = druid.RegisterSpell(Bear, core.SpellConfig{ ActionID: core.ActionID{SpellID: 48480}, @@ -22,14 +23,15 @@ func (druid *Druid) registerMaulSpell() { Flags: core.SpellFlagMeleeMetrics | core.SpellFlagIncludeTargetBonusDamage | core.SpellFlagNoOnCastComplete, RageCost: core.RageCostOptions{ - Cost: 15 - float64(druid.Talents.Ferocity), + Cost: 30, Refund: 0.8, }, - DamageMultiplier: 1 + 0.1*float64(druid.Talents.SavageFury), - CritMultiplier: druid.MeleeCritMultiplier(Bear), + DamageMultiplier: 1, + CritMultiplier: druid.DefaultMeleeCritMultiplier(), ThreatMultiplier: 1, FlatThreatBonus: 424, + BonusCoefficient: 1, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { // Need to specially deactivate CC here in case maul is cast simultaneously with another spell. @@ -42,14 +44,13 @@ func (druid *Druid) registerMaulSpell() { modifier += .3 } if druid.AssumeBleedActive || druid.Rip.Dot(target).IsActive() || druid.Rake.Dot(target).IsActive() || druid.Lacerate.Dot(target).IsActive() { - modifier *= 1.0 + (0.04 * float64(druid.Talents.RendAndTear)) + modifier *= rendAndTearMod } curTarget := target for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { baseDamage := flatBaseDamage + - spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) + - spell.BonusWeaponDamage() + spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) baseDamage *= modifier result := spell.CalcAndDealDamage(sim, curTarget, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) diff --git a/sim/druid/pulverize.go b/sim/druid/pulverize.go new file mode 100644 index 0000000000..d8564fc5b0 --- /dev/null +++ b/sim/druid/pulverize.go @@ -0,0 +1,64 @@ +package druid + +import ( + "github.com/wowsims/cata/sim/core" + "github.com/wowsims/cata/sim/core/stats" +) + +func (druid *Druid) registerPulverizeSpell() { + if !druid.Talents.Pulverize { + return + } + + statBonusPerStack := stats.Stats{stats.MeleeCrit: 3.0 * core.CritRatingPerCritChance} + + druid.PulverizeAura = druid.RegisterAura(core.Aura{ + Label: "Pulverize", + ActionID: core.ActionID{SpellID: 80951}, + MaxStacks: 3, + Duration: core.DurationFromSeconds(10.0 + 4.0 * float64(druid.Talents.EndlessCarnage)), + + OnStacksChange: func(aura *core.Aura, sim *core.Simulation, oldStacks int32, newStacks int32) { + druid.AddStatsDynamic(sim, statBonusPerStack.Multiply(float64(newStacks-oldStacks))) + }, + }) + + druid.Pulverize = druid.RegisterSpell(Bear, core.SpellConfig{ + ActionID: core.ActionID{SpellID: 80313}, + SpellSchool: core.SpellSchoolPhysical, + ProcMask: core.ProcMaskMeleeMHSpecial, + Flags: core.SpellFlagMeleeMetrics | core.SpellFlagIncludeTargetBonusDamage | core.SpellFlagAPL, + + RageCost: core.RageCostOptions{ + Cost: 15, + Refund: 0.8, + }, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + IgnoreHaste: true, + }, + + DamageMultiplier: 0.6, + CritMultiplier: druid.DefaultMeleeCritMultiplier(), + ThreatMultiplier: 1, + BonusCoefficient: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + lacerateDot := druid.Lacerate.Dot(target) + lacerateStacksConsumed := core.TernaryInt32(lacerateDot.IsActive(), lacerateDot.GetStacks(), 0) + flatDamage := 1623.6 * float64(lacerateStacksConsumed) + baseDamage := flatDamage/0.6 + spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) + result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) + + if result.Landed() { + lacerateDot.Cancel(sim) + druid.PulverizeAura.Activate(sim) + druid.PulverizeAura.SetStacks(sim, lacerateStacksConsumed) + } else { + spell.IssueRefund(sim) + } + }, + }) +} diff --git a/sim/druid/shred.go b/sim/druid/shred.go index 201bc4afdc..4708f603f9 100644 --- a/sim/druid/shred.go +++ b/sim/druid/shred.go @@ -14,6 +14,7 @@ func (druid *Druid) registerShredSpell() { hasGlyphofShred := druid.HasPrimeGlyph(proto.DruidPrimeGlyph_GlyphOfBloodletting) maxRipTicks := druid.MaxRipTicks() + rendAndTearMod := []float64{1.0, 1.07, 1.13, 1.2}[druid.Talents.RendAndTear] druid.Shred = druid.RegisterSpell(Cat, core.SpellConfig{ ActionID: core.ActionID{SpellID: 48572}, @@ -51,7 +52,7 @@ func (druid *Druid) registerShredSpell() { ripDot := druid.Rip.Dot(target) if druid.AssumeBleedActive || ripDot.IsActive() || druid.Rake.Dot(target).IsActive() || druid.Lacerate.Dot(target).IsActive() { - modifier *= 1.0 + (0.04 * float64(druid.Talents.RendAndTear)) + modifier *= rendAndTearMod } baseDamage *= modifier @@ -79,7 +80,7 @@ func (druid *Druid) registerShredSpell() { modifier += .3 } if druid.AssumeBleedActive || druid.Rip.Dot(target).IsActive() || druid.Rake.Dot(target).IsActive() || druid.Lacerate.Dot(target).IsActive() { - modifier *= 1.0 + (0.04 * float64(druid.Talents.RendAndTear)) + modifier *= rendAndTearMod } baseDamage *= modifier baseres := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeExpectedMagicAlwaysHit) diff --git a/sim/druid/_survival_instincts.go b/sim/druid/survival_instincts.go similarity index 59% rename from sim/druid/_survival_instincts.go rename to sim/druid/survival_instincts.go index 5587aeedbe..9c0470afb3 100644 --- a/sim/druid/_survival_instincts.go +++ b/sim/druid/survival_instincts.go @@ -4,8 +4,6 @@ import ( "time" "github.com/wowsims/cata/sim/core" - "github.com/wowsims/cata/sim/core/proto" - "github.com/wowsims/cata/sim/core/stats" ) func (druid *Druid) registerSurvivalInstinctsCD() { @@ -14,31 +12,25 @@ func (druid *Druid) registerSurvivalInstinctsCD() { } actionID := core.ActionID{SpellID: 61336} - healthMetrics := druid.NewHealthMetrics(actionID) cdTimer := druid.NewTimer() cd := time.Minute * 3 - healthFac := core.TernaryFloat64(druid.HasMajorGlyph(proto.DruidMajorGlyph_GlyphOfSurvivalInstincts), 0.45, 0.3) - var bonusHealth float64 druid.SurvivalInstinctsAura = druid.RegisterAura(core.Aura{ Label: "Survival Instincts", ActionID: actionID, - Duration: time.Second*20 + core.TernaryDuration(druid.HasSetBonus(ItemSetNightsongBattlegear, 4), 8*time.Second, 0), + Duration: time.Second * 12, OnGain: func(aura *core.Aura, sim *core.Simulation) { - bonusHealth = druid.MaxHealth() * healthFac - druid.AddStatsDynamic(sim, stats.Stats{stats.Health: bonusHealth}) - druid.GainHealth(sim, bonusHealth, healthMetrics) + druid.PseudoStats.DamageTakenMultiplier *= 0.5 }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { - druid.AddStatsDynamic(sim, stats.Stats{stats.Health: -bonusHealth}) + druid.PseudoStats.DamageTakenMultiplier /= 0.5 }, }) druid.SurvivalInstincts = druid.RegisterSpell(Cat|Bear, core.SpellConfig{ ActionID: actionID, - Flags: SpellFlagOmenTrigger, Cast: core.CastConfig{ CD: core.Cooldown{ Timer: cdTimer, diff --git a/sim/druid/talents.go b/sim/druid/talents.go index 2cfffe7025..60d3003a0c 100644 --- a/sim/druid/talents.go +++ b/sim/druid/talents.go @@ -41,7 +41,8 @@ func (druid *Druid) ApplyTalents() { // druid.PseudoStats.DamageDealtMultiplier *= 1 + (float64(druid.Talents.EarthAndMoon) * 0.02) // druid.PseudoStats.SpiritRegenRateCasting = float64(druid.Talents.Intensity) * (0.5 / 3) // druid.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] *= 1 + 0.02*float64(druid.Talents.Naturalist) - // druid.ApplyEquipScaling(stats.Armor, druid.ThickHideMultiplier()) + druid.ApplyEquipScaling(stats.Armor, druid.ThickHideMultiplier()) + druid.PseudoStats.ReducedCritTakenChance += 0.02 * float64(druid.Talents.ThickHide) // if druid.Talents.LunarGuidance > 0 { // bonus := 0.04 * float64(druid.Talents.LunarGuidance) @@ -62,16 +63,6 @@ func (druid *Druid) ApplyTalents() { // druid.AddStat(stats.SpellCrit, float64(druid.Talents.ImprovedFaerieFire)*1*core.CritRatingPerCritChance) // } - // if druid.Talents.SurvivalOfTheFittest > 0 { - // bonus := 0.02 * float64(druid.Talents.SurvivalOfTheFittest) - // druid.MultiplyStat(stats.Stamina, 1.0+bonus) - // druid.MultiplyStat(stats.Strength, 1.0+bonus) - // druid.MultiplyStat(stats.Agility, 1.0+bonus) - // druid.MultiplyStat(stats.Intellect, 1.0+bonus) - // druid.MultiplyStat(stats.Spirit, 1.0+bonus) - // druid.PseudoStats.ReducedCritTakenChance += 0.02 * float64(druid.Talents.SurvivalOfTheFittest) - // } - // if druid.Talents.ImprovedMarkOfTheWild > 0 { // bonus := 0.01 * float64(druid.Talents.ImprovedMarkOfTheWild) // druid.MultiplyStat(stats.Stamina, 1.0+bonus) @@ -97,12 +88,13 @@ func (druid *Druid) ApplyTalents() { druid.applyPrimalFury() // druid.applyOmenOfClarity() // druid.applyEclipse() - // druid.applyImprovedLotp() + druid.applyLotp() // druid.applyPredatoryInstincts() - // druid.applyNaturalReaction() + druid.applyNaturalReaction() // druid.applyOwlkinFrenzy() // druid.applyInfectedWounds() druid.applyFurySwipes() + druid.applyPrimalMadness() } // func (druid *Druid) setupNaturesGrace() { @@ -316,13 +308,41 @@ func (druid *Druid) applyPrimalFury() { }) } +func (druid *Druid) applyPrimalMadness() { + if (druid.Talents.PrimalMadness == 0) || !druid.InForm(Cat | Bear) { + return + } + + actionID := core.ActionID{SpellID: 80315 + druid.Talents.PrimalMadness} + druid.PrimalMadnessRageMetrics = druid.NewRageMetrics(actionID) + + if !druid.InForm(Cat) { + return + } + + energyMetrics := druid.NewEnergyMetrics(actionID) + energyGain := 10.0 * float64(druid.Talents.PrimalMadness) + + druid.PrimalMadnessAura = druid.RegisterAura(core.Aura{ + Label: "Primal Madness", + ActionID: actionID, + Duration: core.NeverExpires, // duration is tied to Tiger's Fury / Berserk durations + OnGain: func(aura *core.Aura, sim *core.Simulation) { + druid.UpdateMaxEnergy(sim, 100.0 + energyGain, energyMetrics) + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + druid.UpdateMaxEnergy(sim, 100.0, energyMetrics) + }, + }) +} + // Modifies the Bleed aura to apply the bonus. func (druid *Druid) applyRendAndTear(aura core.Aura) core.Aura { if druid.FerociousBite == nil || druid.Talents.RendAndTear == 0 || druid.AssumeBleedActive { return aura } - bonusCrit := 5.0 * float64(druid.Talents.RendAndTear) * core.CritRatingPerCritChance + bonusCrit := []float64{0.0, 8.0, 17.0, 25.0}[druid.Talents.RendAndTear] * core.CritRatingPerCritChance aura.ApplyOnGain(func(aura *core.Aura, sim *core.Simulation) { if druid.BleedsActive == 0 { @@ -606,45 +626,48 @@ func (druid *Druid) applyRendAndTear(aura core.Aura) core.Aura { // }) // } -// func (druid *Druid) applyImprovedLotp() { -// if druid.Talents.ImprovedLeaderOfThePack == 0 { -// return -// } +func (druid *Druid) applyLotp() { + if !druid.Talents.LeaderOfThePack { + return + } -// actionID := core.ActionID{SpellID: 34300} -// manaMetrics := druid.NewManaMetrics(actionID) -// healthMetrics := druid.NewHealthMetrics(actionID) -// manaRestore := float64(druid.Talents.ImprovedLeaderOfThePack) * 0.04 -// healthRestore := 0.5 * manaRestore + actionID := core.ActionID{SpellID: 17007} + manaMetrics := druid.NewManaMetrics(actionID) + healthMetrics := druid.NewHealthMetrics(actionID) + manaRestore := 0.08 + healthRestore := 0.05 -// icd := core.Cooldown{ -// Timer: druid.NewTimer(), -// Duration: time.Second * 6, -// } + icd := core.Cooldown{ + Timer: druid.NewTimer(), + Duration: time.Second * 6, + } -// druid.RegisterAura(core.Aura{ -// Icd: &icd, -// Label: "Improved Leader of the Pack", -// 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.Landed() { -// return -// } -// if !spell.ProcMask.Matches(core.ProcMaskMeleeOrRanged) || !result.Outcome.Matches(core.OutcomeCrit) { -// return -// } -// if !icd.IsReady(sim) { -// return -// } -// icd.Use(sim) -// druid.AddMana(sim, druid.MaxMana()*manaRestore, manaMetrics) -// druid.GainHealth(sim, druid.MaxHealth()*healthRestore, healthMetrics) -// }, -// }) -// } + druid.RegisterAura(core.Aura{ + Icd: &icd, + Label: "Improved Leader of the Pack", + 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.Landed() { + return + } + if !spell.ProcMask.Matches(core.ProcMaskMeleeOrRanged) || !result.Outcome.Matches(core.OutcomeCrit) { + return + } + if !icd.IsReady(sim) { + return + } + if !druid.InForm(Cat | Bear) { + return + } + icd.Use(sim) + druid.AddMana(sim, druid.MaxMana()*manaRestore, manaMetrics) + druid.GainHealth(sim, druid.MaxHealth()*healthRestore, healthMetrics) + }, + }) +} // func (druid *Druid) applyPredatoryInstincts() { // if druid.Talents.PredatoryInstincts == 0 { @@ -670,26 +693,26 @@ func (druid *Druid) applyRendAndTear(aura core.Aura) core.Aura { // }) // } -// func (druid *Druid) applyNaturalReaction() { -// if druid.Talents.NaturalReaction == 0 { -// return -// } +func (druid *Druid) applyNaturalReaction() { + if druid.Talents.NaturalReaction == 0 { + return + } -// actionID := core.ActionID{SpellID: 59072} -// rageMetrics := druid.NewRageMetrics(actionID) -// rageAdded := float64(druid.Talents.NaturalReaction) - -// core.MakeProcTriggerAura(&druid.Unit, core.ProcTrigger{ -// Name: "Natural Reaction Trigger", -// Callback: core.CallbackOnSpellHitTaken, -// ProcMask: core.ProcMaskMelee, -// Handler: func(sim *core.Simulation, _ *core.Spell, result *core.SpellResult) { -// if druid.InForm(Bear) && result.Outcome.Matches(core.OutcomeDodge) { -// druid.AddRage(sim, rageAdded, rageMetrics) -// } -// }, -// }) -// } + actionID := core.ActionID{SpellID: 59071} + rageMetrics := druid.NewRageMetrics(actionID) + rageAdded := 1.0 + 2.0 * float64(druid.Talents.NaturalReaction - 1) + + core.MakeProcTriggerAura(&druid.Unit, core.ProcTrigger{ + Name: "Natural Reaction Trigger", + Callback: core.CallbackOnSpellHitTaken, + ProcMask: core.ProcMaskMelee, + Handler: func(sim *core.Simulation, _ *core.Spell, result *core.SpellResult) { + if druid.InForm(Bear) && result.Outcome.Matches(core.OutcomeDodge) { + druid.AddRage(sim, rageAdded, rageMetrics) + } + }, + }) +} // func (druid *Druid) applyInfectedWounds() { // if druid.Talents.InfectedWounds == 0 { diff --git a/sim/druid/tigers_fury.go b/sim/druid/tigers_fury.go index 397426d53e..cd3570153a 100644 --- a/sim/druid/tigers_fury.go +++ b/sim/druid/tigers_fury.go @@ -21,9 +21,17 @@ func (druid *Druid) registerTigersFurySpell() { Duration: 6 * time.Second, OnGain: func(aura *core.Aura, sim *core.Simulation) { druid.PseudoStats.BonusDamage += dmgBonus + + if druid.PrimalMadnessAura != nil { + druid.PrimalMadnessAura.Activate(sim) + } }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { druid.PseudoStats.BonusDamage -= dmgBonus + + if druid.PrimalMadnessAura.IsActive() && !druid.BerserkAura.IsActive() { + druid.PrimalMadnessAura.Deactivate(sim) + } }, }) diff --git a/sim/warrior/talents.go b/sim/warrior/talents.go index fdf59eafe9..5ae0d507e2 100644 --- a/sim/warrior/talents.go +++ b/sim/warrior/talents.go @@ -739,11 +739,10 @@ func (warrior *Warrior) ApplyTalents() { // Duration: time.Second * 20, // OnGain: func(aura *core.Aura, sim *core.Simulation) { // bonusHealth = warrior.MaxHealth() * 0.3 -// warrior.AddStatsDynamic(sim, stats.Stats{stats.Health: bonusHealth}) -// warrior.GainHealth(sim, bonusHealth, healthMetrics) +// warrior.UpdateMaxHealth(sim, bonusHealth, healthMetrics) // }, // OnExpire: func(aura *core.Aura, sim *core.Simulation) { -// warrior.AddStatsDynamic(sim, stats.Stats{stats.Health: -bonusHealth}) +// warrior.UpdateMaxHealth(sim, -bonusHealth, healthMetrics) // }, // }) diff --git a/ui/druid/feral/sim.ts b/ui/druid/feral/sim.ts index a7d0f03028..f5a6517981 100644 --- a/ui/druid/feral/sim.ts +++ b/ui/druid/feral/sim.ts @@ -81,7 +81,6 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecFeralDruid, { windfuryTotem: true, bloodlust: true, communion: true, - leaderOfThePack: true, arcaneBrilliance: true, manaSpringTotem: true, }), @@ -107,7 +106,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecFeralDruid, { }, encounterPicker: { // Whether to include 'Execute Duration (%)' in the 'Encounter' section of the settings tab. - showExecuteProportion: false, + showExecuteProportion: true, }, presets: {