From a9d5fc53fd189e932047698b9a112f03dddc8371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Hillerstr=C3=B6m?= Date: Mon, 8 Jul 2024 22:26:19 +0200 Subject: [PATCH] [Prot] Add remaining Protection Paladin talents and defensive abilities --- sim/paladin/_divine_protection.go | 72 ----------- sim/paladin/_hand_of_reckoning.go | 42 ------ sim/paladin/_holy_shield.go | 79 ------------ sim/paladin/_spiritual_attunement.go | 36 ------ sim/paladin/divine_protection.go | 76 +++++++++++ sim/paladin/paladin.go | 21 +-- sim/paladin/protection/protection.go | 4 +- sim/paladin/talents_protection.go | 185 +++++++++++++++++++++++---- 8 files changed, 245 insertions(+), 270 deletions(-) delete mode 100644 sim/paladin/_divine_protection.go delete mode 100644 sim/paladin/_hand_of_reckoning.go delete mode 100644 sim/paladin/_holy_shield.go delete mode 100644 sim/paladin/_spiritual_attunement.go create mode 100644 sim/paladin/divine_protection.go diff --git a/sim/paladin/_divine_protection.go b/sim/paladin/_divine_protection.go deleted file mode 100644 index 5de953f6ac..0000000000 --- a/sim/paladin/_divine_protection.go +++ /dev/null @@ -1,72 +0,0 @@ -package paladin - -import ( - "time" - - "github.com/wowsims/cata/sim/core" -) - -func (paladin *Paladin) registerDivineProtectionSpell() { - duration := time.Second*12 + core.TernaryDuration(paladin.HasSetBonus(ItemSetRedemptionPlate, 4), time.Second*3, 0) - - actionID := core.ActionID{SpellID: 498} - paladin.DivineProtectionAura = paladin.RegisterAura(core.Aura{ - Label: "Divine Protection", - ActionID: actionID, - Duration: duration, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - paladin.PseudoStats.DamageTakenMultiplier *= 0.5 - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - paladin.PseudoStats.DamageTakenMultiplier /= 0.5 - }, - }) - - cooldownDur := time.Minute*3 - - 30*time.Second*time.Duration(paladin.Talents.SacredDuty) - - core.TernaryDuration(paladin.HasSetBonus(ItemSetTuralyonsPlate, 4), 30*time.Second, 0) - - paladin.DivineProtection = paladin.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - Flags: core.SpellFlagAPL, - - Cast: core.CastConfig{ - CD: core.Cooldown{ - Timer: paladin.NewTimer(), - Duration: cooldownDur, - }, - SharedCD: core.Cooldown{ - Timer: paladin.GetMutualLockoutDPAW(), - Duration: 30 * time.Second, - }, - }, - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - usable := !paladin.ForbearanceAura.IsActive() - // Prevent Ret from screwing up their rotation by using this. TODO better logic - if usable && paladin.Talents.TheArtOfWar > 0 { - usable = false - } - return usable - }, - - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { - paladin.DivineProtectionAura.Activate(sim) - paladin.ForbearanceAura.Activate(sim) - }, - }) - - paladin.AddMajorCooldown(core.MajorCooldown{ - Spell: paladin.DivineProtection, - Type: core.CooldownTypeSurvival, - }) -} - -func (paladin *Paladin) registerForbearanceDebuff() { - actionID := core.ActionID{SpellID: 25771} - duration := core.TernaryDuration(paladin.HasSetBonus(ItemSetTuralyonsPlate, 4), 90*time.Second, 120*time.Second) - paladin.ForbearanceAura = paladin.RegisterAura(core.Aura{ - Label: "Forbearance", - ActionID: actionID, - Duration: duration, - }) -} diff --git a/sim/paladin/_hand_of_reckoning.go b/sim/paladin/_hand_of_reckoning.go deleted file mode 100644 index f2976182aa..0000000000 --- a/sim/paladin/_hand_of_reckoning.go +++ /dev/null @@ -1,42 +0,0 @@ -package paladin - -import ( - "time" - - "github.com/wowsims/cata/sim/core" - "github.com/wowsims/cata/sim/core/proto" -) - -func (paladin *Paladin) registerHandOfReckoningSpell() { - if !paladin.HasMajorGlyph(proto.PaladinMajorGlyph_GlyphOfReckoning) { - return - } - - paladin.HandOfReckoning = paladin.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 67485}, // 62124 is the "taunt" part - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagMeleeMetrics | core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.03, - Multiplier: 1 - 0.02*float64(paladin.Talents.Benediction), - }, - Cast: core.CastConfig{ - CD: core.Cooldown{ - Timer: paladin.NewTimer(), - Duration: time.Second * time.Duration(core.TernaryInt(paladin.HasSetBonus(ItemSetTuralyonsPlate, 2), 6, 8)), - }, - }, - - DamageMultiplierAdditive: 1, - DamageMultiplier: 1, - ThreatMultiplier: 1, - CritMultiplier: paladin.SpellCritMultiplier(), - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := 1 + .5*spell.MeleeAttackPower() - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicCrit) // cannot miss - }, - }) -} diff --git a/sim/paladin/_holy_shield.go b/sim/paladin/_holy_shield.go deleted file mode 100644 index 054b8ed2b4..0000000000 --- a/sim/paladin/_holy_shield.go +++ /dev/null @@ -1,79 +0,0 @@ -package paladin - -import ( - "time" - - "github.com/wowsims/cata/sim/core" - "github.com/wowsims/cata/sim/core/stats" -) - -func (paladin *Paladin) registerHolyShieldSpell() { - actionID := core.ActionID{SpellID: 48952} - numCharges := int32(8) - - procSpell := paladin.RegisterSpell(core.SpellConfig{ - ActionID: actionID.WithTag(1), - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskEmpty, - - DamageMultiplier: 1, - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - // Beta testing shows wowhead coeffs are probably correct - baseDamage := 274 + - 0.0732*spell.MeleeAttackPower() + - 0.117*spell.SpellPower() - - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHit) - }, - }) - - blockBonus := 30*core.BlockRatingPerBlockChance + core.TernaryFloat64(paladin.Ranged().ID == 29388, 42, 0) - - paladin.HolyShieldAura = paladin.RegisterAura(core.Aura{ - Label: "Holy Shield", - ActionID: actionID, - Duration: time.Second * 10, - MaxStacks: numCharges, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - paladin.AddStatDynamic(sim, stats.Block, blockBonus) - aura.SetStacks(sim, numCharges) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - paladin.AddStatDynamic(sim, stats.Block, -blockBonus) - }, - OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if result.Outcome.Matches(core.OutcomeBlock) { - procSpell.Cast(sim, spell.Unit) - aura.RemoveStack(sim) - } - }, - }) - - paladin.HolyShield = paladin.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - SpellSchool: core.SpellSchoolHoly, - Flags: core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.10, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - CD: core.Cooldown{ - Timer: paladin.NewTimer(), - Duration: time.Second * 8, - }, - }, - - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { - if paladin.HolyShieldAura.IsActive() { - paladin.HolyShieldAura.SetStacks(sim, numCharges) - } - paladin.HolyShieldAura.Activate(sim) - }, - }) -} diff --git a/sim/paladin/_spiritual_attunement.go b/sim/paladin/_spiritual_attunement.go deleted file mode 100644 index b6e65292ec..0000000000 --- a/sim/paladin/_spiritual_attunement.go +++ /dev/null @@ -1,36 +0,0 @@ -package paladin - -import ( - "github.com/wowsims/cata/sim/core" -) - -func (paladin *Paladin) registerSpiritualAttunement() { - if paladin.Talents.SpiritualAttunement == 0 { - return - } - - // No longer baseline in WotLK, affected by talent points and glyphs. Ignoring the old set bonus here. - SpiritualAttunementScalar := (0.05*float64(paladin.Talents.SpiritualAttunement) + core.TernaryFloat64(paladin.HasMajorGlyph(41096), 0.02, 0)) - - paladin.SpiritualAttunementMetrics = paladin.NewManaMetrics(core.ActionID{SpellID: 33776}) - - paladin.RegisterAura(core.Aura{ - Label: "Spiritual Attunement", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - // We assume we were instantly healed for the damage. - if result.Damage > 0 { - paladin.AddMana(sim, result.Damage*SpiritualAttunementScalar, paladin.SpiritualAttunementMetrics) - } - }, - OnPeriodicDamageTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - // We assume we were instantly healed for the damage. - if result.Damage > 0 { - paladin.AddMana(sim, result.Damage*SpiritualAttunementScalar, paladin.SpiritualAttunementMetrics) - } - }, - }) -} diff --git a/sim/paladin/divine_protection.go b/sim/paladin/divine_protection.go new file mode 100644 index 0000000000..fa9fd667ba --- /dev/null +++ b/sim/paladin/divine_protection.go @@ -0,0 +1,76 @@ +package paladin + +import ( + "time" + + "github.com/wowsims/cata/sim/core" + "github.com/wowsims/cata/sim/core/proto" + "github.com/wowsims/cata/sim/core/stats" +) + +func (paladin *Paladin) registerDivineProtectionSpell() { + glyphOfDivineProtection := paladin.HasMajorGlyph(proto.PaladinMajorGlyph_GlyphOfDivineProtection) + + actionID := core.ActionID{SpellID: 498} + paladin.DivineProtectionAura = paladin.RegisterAura(core.Aura{ + Label: "Divine Protection", + ActionID: actionID, + Duration: time.Second * 10, + + OnGain: func(aura *core.Aura, sim *core.Simulation) { + if glyphOfDivineProtection { + paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexArcane] *= 0.6 + paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFire] *= 0.6 + paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFrost] *= 0.6 + paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexHoly] *= 0.6 + paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexNature] *= 0.6 + paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexShadow] *= 0.6 + } else { + paladin.PseudoStats.DamageTakenMultiplier *= 0.8 + } + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + if glyphOfDivineProtection { + paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexArcane] /= 0.6 + paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFire] /= 0.6 + paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFrost] /= 0.6 + paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexHoly] /= 0.6 + paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexNature] /= 0.6 + paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexShadow] /= 0.6 + } else { + paladin.PseudoStats.DamageTakenMultiplier /= 0.8 + } + }, + }) + + paladin.DivineProtection = paladin.RegisterSpell(core.SpellConfig{ + ActionID: actionID, + Flags: core.SpellFlagAPL, + ClassSpellMask: SpellMaskDivineProtection, + + ManaCost: core.ManaCostOptions{ + BaseCost: 0.03, + }, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + NonEmpty: true, + }, + CD: core.Cooldown{ + Timer: paladin.NewTimer(), + Duration: time.Minute * 1, + }, + }, + + ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { + paladin.DivineProtectionAura.Activate(sim) + }, + }) + + paladin.AddMajorCooldown(core.MajorCooldown{ + Spell: paladin.DivineProtection, + Type: core.CooldownTypeSurvival, + ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { + return character.Spec == proto.Spec_SpecProtectionPaladin + }, + }) +} diff --git a/sim/paladin/paladin.go b/sim/paladin/paladin.go index fb502ed045..97221e78d3 100644 --- a/sim/paladin/paladin.go +++ b/sim/paladin/paladin.go @@ -42,6 +42,8 @@ const ( SpellMaskAncientFury SpellMaskSealsOfCommand SpellMaskShieldOfTheRighteous + SpellMaskHolyShield + SpellMaskArdentDefender SpellMaskHolyShock SpellMaskWordOfGlory @@ -155,15 +157,12 @@ type Paladin struct { SealOfJusticeAura *core.Aura AvengingWrathAura *core.Aura DivineProtectionAura *core.Aura - ForbearanceAura *core.Aura ZealotryAura *core.Aura InquisitionAura *core.Aura DivinePurposeAura *core.Aura JudgementsOfThePureAura *core.Aura - GrandCrusaderAura *core.Aura - SacredDutyAura *core.Aura - - SpiritualAttunementMetrics *core.ResourceMetrics + GrandCrusaderAura *core.Aura + SacredDutyAura *core.Aura } // Implemented by each Paladin spec. @@ -231,6 +230,7 @@ func (paladin *Paladin) registerSpells() { paladin.registerConsecrationSpell() paladin.registerHolyWrath() paladin.registerGuardianOfAncientKings() + paladin.registerDivineProtectionSpell() } func (paladin *Paladin) Reset(sim *core.Simulation) { @@ -287,16 +287,9 @@ func NewPaladin(character *core.Character, talentsStr string, options *proto.Pal paladin.AddStatDependency(stats.Agility, stats.MeleeCrit, core.CritPerAgiMaxLevel[character.Class]*core.CritRatingPerCritChance) paladin.AddStat(stats.Parry, -paladin.GetBaseStats()[stats.Strength]*0.27) // Does not apply to base Strength paladin.AddStatDependency(stats.Strength, stats.Parry, 0.27) - - paladin.PseudoStats.BaseDodge += 0.034943 - paladin.PseudoStats.BaseParry += 0.05 - // TODO: figure out the exact tanking stat dependencies for prot pala - // // Paladins get 0.0167 dodge per agi. ~1% per 59.88 - // paladin.AddStatDependency(stats.Agility, stats.Dodge, (1.0/59.88)*core.DodgeRatingPerDodgeChance) - // // Paladins get more melee haste from haste than other classes - // paladin.PseudoStats.MeleeHasteRatingPerHastePercent /= 1.3 - // // Base dodge is unaffected by Diminishing Returns + paladin.PseudoStats.BaseDodge += 0.05 + paladin.PseudoStats.BaseParry += 0.05 // Bonus Armor and Armor are treated identically for Paladins paladin.AddStatDependency(stats.BonusArmor, stats.Armor, 1) diff --git a/sim/paladin/protection/protection.go b/sim/paladin/protection/protection.go index 1a149a1d66..ed498541dd 100644 --- a/sim/paladin/protection/protection.go +++ b/sim/paladin/protection/protection.go @@ -31,8 +31,8 @@ func NewProtectionPaladin(character *core.Character, options *proto.Player) *Pro protOptions := options.GetProtectionPaladin() prot := &ProtectionPaladin{ - Paladin: paladin.NewPaladin(character, options.TalentsString, protOptions.Options.ClassOptions), - Options: protOptions.Options, + Paladin: paladin.NewPaladin(character, options.TalentsString, protOptions.Options.ClassOptions), + Options: protOptions.Options, vengeance: &core.VengeanceTracker{}, } diff --git a/sim/paladin/talents_protection.go b/sim/paladin/talents_protection.go index f37de778ba..26d5721c08 100644 --- a/sim/paladin/talents_protection.go +++ b/sim/paladin/talents_protection.go @@ -17,9 +17,11 @@ func (paladin *Paladin) applyProtectionTalents() { paladin.applyHammerOfTheRighteous() paladin.applyReckoning() paladin.applyShieldOfTheRighteous() - paladin.applyShieldOfTheTemplar() paladin.applyGrandCrusader() + paladin.applyHolyShield() paladin.applySacredDuty() + paladin.applyShieldOfTheTemplar() + paladin.applyArdentDefender() } func (paladin *Paladin) applySealsOfThePure() { @@ -307,18 +309,18 @@ func (paladin *Paladin) applyShieldOfTheTemplar() { Kind: core.SpellMod_Cooldown_Flat, TimeValue: -(time.Second * time.Duration(20*paladin.Talents.ShieldOfTheTemplar)), }) - + core.MakeProcTriggerAura(&paladin.Unit, core.ProcTrigger{ - Name: "Divine Plea Templar Effect", - ActionID: actionId, - Callback: core.CallbackOnCastComplete, - ClassSpellMask: SpellMaskDivinePlea, - ProcChance: 1, + Name: "Divine Plea Templar Effect", + ActionID: actionId, + Callback: core.CallbackOnCastComplete, + ClassSpellMask: SpellMaskDivinePlea, + ProcChance: 1, Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { paladin.GainHolyPower(sim, 3, hpMetrics) }, }) - + } func (paladin *Paladin) applyGrandCrusader() { @@ -330,7 +332,7 @@ func (paladin *Paladin) applyGrandCrusader() { Label: "Grand Crusader (Proc)", ActionID: core.ActionID{SpellID: 85043}, Duration: time.Second * 6, - + // Dummy effect. Implemented in avengers_shield.go OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { @@ -341,12 +343,12 @@ func (paladin *Paladin) applyGrandCrusader() { }) core.MakeProcTriggerAura(&paladin.Unit, core.ProcTrigger{ - Name: "Grand Crusader", - ActionID: core.ActionID{SpellID: 85416}, - Callback: core.CallbackOnSpellHitDealt, - Outcome: core.OutcomeLanded, - ClassSpellMask: SpellMaskBuilder, - ProcChance: []float64{0, 0.05, 0.10}[paladin.Talents.GrandCrusader], + Name: "Grand Crusader", + ActionID: core.ActionID{SpellID: 85416}, + Callback: core.CallbackOnSpellHitDealt, + Outcome: core.OutcomeLanded, + ClassSpellMask: SpellMaskBuilder, + ProcChance: []float64{0, 0.05, 0.10}[paladin.Talents.GrandCrusader], Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { paladin.AvengersShield.CD.Reset() paladin.GrandCrusaderAura.Activate(sim) @@ -354,6 +356,48 @@ func (paladin *Paladin) applyGrandCrusader() { }) } +func (paladin *Paladin) applyHolyShield() { + if !paladin.Talents.HolyShield { + return + } + + holyShieldAura := paladin.RegisterAura(core.Aura{ + Label: "Holy Shield", + ActionID: core.ActionID{SpellID: 20925}, + Duration: time.Second * 10, + + OnGain: func(aura *core.Aura, sim *core.Simulation) { + paladin.PseudoStats.BlockDamageReduction += 0.2 + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + paladin.PseudoStats.BlockDamageReduction -= 0.2 + }, + }) + + paladin.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: 20925}, + Flags: core.SpellFlagAPL, + ClassSpellMask: SpellMaskHolyShield, + + ManaCost: core.ManaCostOptions{ + BaseCost: 0.03, + }, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + NonEmpty: true, + }, + CD: core.Cooldown{ + Timer: paladin.NewTimer(), + Duration: time.Second * 30, + }, + }, + + ApplyEffects: func(sim *core.Simulation, unit *core.Unit, spell *core.Spell) { + holyShieldAura.Activate(sim) + }, + }) +} + // 25/50% chance on Judgement/AS to apply 100% crit to next SotR func (paladin *Paladin) applySacredDuty() { if paladin.Talents.SacredDuty == 0 { @@ -361,24 +405,24 @@ func (paladin *Paladin) applySacredDuty() { } critMod := paladin.AddDynamicMod(core.SpellModConfig{ - ClassMask: SpellMaskShieldOfTheRighteous, - Kind: core.SpellMod_BonusCrit_Rating, + ClassMask: SpellMaskShieldOfTheRighteous, + Kind: core.SpellMod_BonusCrit_Rating, FloatValue: 100 * core.CritRatingPerCritChance, }) paladin.SacredDutyAura = paladin.RegisterAura(core.Aura{ - Label: "Sacred Duty (Proc)", + Label: "Sacred Duty (Proc)", ActionID: core.ActionID{SpellID: 85433}, Duration: time.Second * 10, OnGain: func(aura *core.Aura, sim *core.Simulation) { critMod.Activate() }, - + OnExpire: func(aura *core.Aura, sim *core.Simulation) { critMod.Deactivate() }, - + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { if spell.ClassSpellMask&SpellMaskShieldOfTheRighteous != 0 && result.DidCrit() { paladin.SacredDutyAura.Deactivate(sim) @@ -387,14 +431,105 @@ func (paladin *Paladin) applySacredDuty() { }) core.MakeProcTriggerAura(&paladin.Unit, core.ProcTrigger{ - Name: "Sacred Duty", - ActionID: core.ActionID{SpellID: 53710}, - Callback: core.CallbackOnSpellHitDealt, - Outcome: core.OutcomeLanded, + Name: "Sacred Duty", + ActionID: core.ActionID{SpellID: 53710}, + Callback: core.CallbackOnSpellHitDealt, + Outcome: core.OutcomeLanded, ClassSpellMask: SpellMaskAvengersShield | SpellMaskJudgement, - ProcChance: []float64{0, 0.25, 0.50}[paladin.Talents.SacredDuty], + ProcChance: []float64{0, 0.25, 0.50}[paladin.Talents.SacredDuty], Handler: func(sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { paladin.SacredDutyAura.Activate(sim) }, }) } + +func (paladin *Paladin) applyArdentDefender() { + if !paladin.Talents.ArdentDefender { + return + } + + actionID := core.ActionID{SpellID: 31850} + + adAura := paladin.RegisterAura(core.Aura{ + Label: "Ardent Defender", + ActionID: actionID, + Duration: time.Second * 10, + + OnGain: func(aura *core.Aura, sim *core.Simulation) { + paladin.PseudoStats.DamageTakenMultiplier *= 0.8 + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + paladin.PseudoStats.DamageTakenMultiplier /= 0.8 + }, + }) + + paladin.RegisterSpell(core.SpellConfig{ + ActionID: actionID, + Flags: core.SpellFlagAPL, + SpellSchool: core.SpellSchoolHoly, + ClassSpellMask: SpellMaskArdentDefender, + + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + NonEmpty: true, + }, + CD: core.Cooldown{ + Timer: paladin.NewTimer(), + Duration: time.Minute * 3, + }, + }, + + ApplyEffects: func(sim *core.Simulation, unit *core.Unit, spell *core.Spell) { + adAura.Activate(sim) + }, + }) + + adHealAmount := 0.0 + + // Spell to heal you when AD has procced; fire this before fatal damage so that a Death is not detected + adHeal := paladin.RegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: 66235}, + SpellSchool: core.SpellSchoolHoly, + ProcMask: core.ProcMaskSpellHealing, + Flags: core.SpellFlagHelpful, + + CritMultiplier: 1, + ThreatMultiplier: 0, + DamageMultiplier: 1, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + spell.CalcAndDealHealing(sim, &paladin.Unit, adHealAmount, spell.OutcomeHealing) + }, + }) + + // >= 15% hp, hit gets reduced so we end up at 15% without heal + // < 15% hp, hit gets reduced to 0 and we heal the remaining health up to 15% + paladin.AddDynamicDamageTakenModifier(func(sim *core.Simulation, _ *core.Spell, result *core.SpellResult) { + if adAura.IsActive() && result.Damage >= paladin.CurrentHealth() { + maxHealth := paladin.MaxHealth() + currentHealth := paladin.CurrentHealth() + incomingDamage := result.Damage + + if currentHealth/maxHealth >= 0.15 { + // Incoming attack gets reduced so we end up at 15% hp + // TODO: Overkill counted as absorb but not as healing in logs + result.Damage = currentHealth - maxHealth*0.15 + if sim.Log != nil { + paladin.Log(sim, "Ardent Defender absorbed %.1f damage", incomingDamage-result.Damage) + } + } else { + // Incoming attack gets reduced to 0 + // Heal up to 15% hp + // TODO: Overkill counted as absorb but not as healing in logs + result.Damage = 0 + adHealAmount = maxHealth*0.15 - currentHealth + adHeal.Cast(sim, &paladin.Unit) + if sim.Log != nil { + paladin.Log(sim, "Ardent Defender absorbed %.1f damage and healed for %.1f", incomingDamage, adHealAmount) + } + } + + adAura.Deactivate(sim) + } + }) +}