diff --git a/sim/_hunter/black_arrow.go b/sim/_hunter/black_arrow.go deleted file mode 100644 index 76f8e55e3..000000000 --- a/sim/_hunter/black_arrow.go +++ /dev/null @@ -1,79 +0,0 @@ -package hunter - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" - "github.com/wowsims/classic/sim/core/stats" -) - -func (hunter *Hunter) registerBlackArrowSpell(timer *core.Timer) { - if !hunter.Talents.BlackArrow { - return - } - - actionID := core.ActionID{SpellID: 63672} - - hunter.BlackArrow = hunter.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskRangedSpecial, - Flags: core.SpellFlagAPL, - CastType: proto.CastType_CastTypeRanged, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.06, - Multiplier: 1 - - 0.03*float64(hunter.Talents.Efficiency) - - 0.2*float64(hunter.Talents.Resourcefulness), - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - IgnoreHaste: true, // Hunter GCD is locked at 1.5s - CD: core.Cooldown{ - Timer: timer, - Duration: time.Second*30 - time.Second*2*time.Duration(hunter.Talents.Resourcefulness), - }, - }, - - DamageMultiplierAdditive: 1 + - .10*float64(hunter.Talents.TrapMastery) + - .02*float64(hunter.Talents.TNT), - DamageMultiplier: 1 * - (1.0 / 1.06), // Black Arrow is not affected by its own 1.06 aura. - ThreatMultiplier: 1, - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "BlackArrow", - OnGain: func(aura *core.Aura, sim *core.Simulation) { - hunter.AttackTables[aura.Unit.UnitIndex][proto.CastType_CastTypeRanged].DamageTakenMultiplier *= 1.06 - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - hunter.AttackTables[aura.Unit.UnitIndex][proto.CastType_CastTypeRanged].DamageTakenMultiplier /= 1.06 - }, - }, - NumberOfTicks: 5, - TickLength: time.Second * 3, - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - // scales slightly better (11.5%) than the tooltip implies (10%), but isn't affected by Hunter's Mark - dot.SnapshotBaseDamage = 553 + 0.023*(dot.Spell.Unit.GetStat(stats.RangedAttackPower)+dot.Spell.Unit.PseudoStats.MobTypeAttackPower) - dot.SnapshotAttackerMultiplier = dot.Spell.AttackerDamageMultiplier(dot.Spell.Unit.AttackTables[target.UnitIndex][dot.Spell.CastType]) - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcOutcome(sim, target, spell.OutcomeRangedHit) - if result.Landed() { - spell.Dot(target).Apply(sim) - } - spell.DealOutcome(sim, result) - }, - }) -} diff --git a/sim/_hunter/kill_shot.go b/sim/_hunter/kill_shot.go deleted file mode 100644 index 9d1d2ca46..000000000 --- a/sim/_hunter/kill_shot.go +++ /dev/null @@ -1,52 +0,0 @@ -package hunter - -import ( - "time" - - "github.com/wowsims/classic/sim/core" -) - -func (hunter *Hunter) registerKillShotSpell() { - hunter.KillShot = hunter.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 61006}, - SpellSchool: core.SpellSchoolPhysical, - ProcMask: core.ProcMaskRangedSpecial, - Flags: core.SpellFlagMeleeMetrics | core.SpellFlagIncludeTargetBonusDamage | core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.07, - Multiplier: 1 - 0.03*float64(hunter.Talents.Efficiency), - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - IgnoreHaste: true, - CD: core.Cooldown{ - Timer: hunter.NewTimer(), - Duration: time.Second * 15, - }, - }, - ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { - return sim.IsExecutePhase20() - }, - - BonusCritRating: 0 + - 5*core.CritRatingPerCritChance*float64(hunter.Talents.SniperTraining), - DamageMultiplier: 1 * - hunter.markedForDeathMultiplier(), - CritMultiplier: hunter.critMultiplier(true, true, false), - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - // 0.2 rap from normalized weapon (2.8/14) and 0.2 from bonus ratio - baseDamage := 0.4*spell.RangedAttackPower(target, false) + - hunter.AutoAttacks.Ranged().BaseDamage(sim) + - hunter.AmmoDamageBonus + - spell.BonusWeaponDamage() + - 325 - baseDamage *= 2 - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeRangedHitAndCrit) - }, - }) -} diff --git a/sim/_hunter/scorpid_sting.go b/sim/_hunter/scorpid_sting.go deleted file mode 100644 index 794883fd1..000000000 --- a/sim/_hunter/scorpid_sting.go +++ /dev/null @@ -1,39 +0,0 @@ -package hunter - -import ( - "github.com/wowsims/classic/sim/core" -) - -func (hunter *Hunter) registerScorpidStingSpell() { - hunter.ScorpidStingAuras = hunter.NewEnemyAuraArray(core.ScorpidStingAura) - - hunter.ScorpidSting = hunter.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 3043}, - SpellSchool: core.SpellSchoolNature, - ProcMask: core.ProcMaskRangedSpecial, - Flags: core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.09, - Multiplier: 1 - 0.03*float64(hunter.Talents.Efficiency), - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - IgnoreHaste: true, // Hunter GCD is locked at 1.5s - }, - - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcOutcome(sim, target, spell.OutcomeRangedHit) - if result.Landed() { - hunter.ScorpidStingAuras.Get(target).Activate(sim) - } - spell.DealOutcome(sim, result) - }, - - RelatedAuras: []core.AuraArray{hunter.ScorpidStingAuras}, - }) -} diff --git a/sim/_hunter/silencing_shot.go b/sim/_hunter/silencing_shot.go deleted file mode 100644 index f0d78fe77..000000000 --- a/sim/_hunter/silencing_shot.go +++ /dev/null @@ -1,55 +0,0 @@ -package hunter - -import ( - "time" - - "github.com/wowsims/classic/sim/core" -) - -func (hunter *Hunter) registerSilencingShotSpell() { - if !hunter.Talents.SilencingShot { - return - } - - hunter.SilencingShot = hunter.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 34490}, - SpellSchool: core.SpellSchoolPhysical, - ProcMask: core.ProcMaskRangedSpecial, - Flags: core.SpellFlagMeleeMetrics | core.SpellFlagIncludeTargetBonusDamage | core.SpellFlagAPL | SpellFlagShot, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.06, - Multiplier: 1 - 0.03*float64(hunter.Talents.Efficiency), - }, - Cast: core.CastConfig{ - CD: core.Cooldown{ - Timer: hunter.NewTimer(), - Duration: time.Second * 20, - }, - }, - - DamageMultiplier: 0.5 * - hunter.markedForDeathMultiplier(), - CritMultiplier: hunter.critMultiplier(true, false, false), - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := hunter.RangedWeaponDamage(sim, spell.RangedAttackPower(target, false)) + - hunter.AmmoDamageBonus + - spell.BonusWeaponDamage() - - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeRangedHitAndCrit) - - // Add a check for later so we use ASAP when it comes off CD. - core.StartDelayedAction(sim, core.DelayedActionOptions{ - DoAt: sim.CurrentTime + hunter.SilencingShot.CD.Duration, - OnAction: func(sim *core.Simulation) { - // Need to check in case Readiness caused a shift in timing. - if hunter.SilencingShot.IsReady(sim) && hunter.Hardcast.Expires <= sim.CurrentTime { - hunter.SilencingShot.Cast(sim, target) - } - }, - }) - }, - }) -} diff --git a/sim/_hunter/volley.go b/sim/_hunter/volley.go deleted file mode 100644 index cc7b197ee..000000000 --- a/sim/_hunter/volley.go +++ /dev/null @@ -1,61 +0,0 @@ -package hunter - -import ( - "time" - - "github.com/wowsims/classic/sim/core" -) - -func (hunter *Hunter) registerVolleySpell() { - hunter.Volley = hunter.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 58434}, - SpellSchool: core.SpellSchoolArcane, - ProcMask: core.ProcMaskRangedSpecial, - Flags: core.SpellFlagChanneled | core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.17, - Multiplier: 1, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - DamageMultiplier: 1 * - (1 + 0.04*float64(hunter.Talents.Barrage)), - CritMultiplier: hunter.critMultiplier(true, false, false), - ThreatMultiplier: 1, - - Dot: core.DotConfig{ - IsAOE: true, - Aura: core.Aura{ - Label: "Volley", - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - hunter.AutoAttacks.DelayRangedUntil(sim, sim.CurrentTime+time.Millisecond*500) - }, - }, - NumberOfTicks: 6, - TickLength: time.Second * 1, - AffectedByCastSpeed: true, - - OnSnapshot: func(sim *core.Simulation, _ *core.Unit, dot *core.Dot, isRollover bool) { - target := hunter.CurrentTarget - baseDamage = 353 + 0.0837*dot.Spell.RangedAttackPower(target, false) - baseDamage *= sim.Encounter.AOECapMultiplier() - dot.Snapshot(target, baseDamage, isRollover) - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - for _, aoeTarget := range sim.Encounter.TargetUnits { - dot.CalcAndDealPeriodicSnapshotDamage(sim, aoeTarget, dot.OutcomeRangedHitAndCritSnapshot) - } - }, - }, - - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { - spell.AOEDot().Apply(sim) - hunter.AutoAttacks.CancelAutoSwing(sim) - }, - }) -} diff --git a/sim/_paladin/avengers_shield.go b/sim/_paladin/avengers_shield.go deleted file mode 100644 index 42f3ce796..000000000 --- a/sim/_paladin/avengers_shield.go +++ /dev/null @@ -1,57 +0,0 @@ -package paladin - -import ( - "time" - - "github.com/wowsims/classic/sim/core" -) - -func (paladin *Paladin) registerAvengersShieldSpell() { - // apply to up to 3 targets - numHits := min(3, paladin.Env.GetNumTargets()) - results := make([]*core.SpellResult, numHits) - - paladin.AvengersShield = paladin.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 48827}, - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskMeleeMHSpecial, - Flags: core.SpellFlagMeleeMetrics | core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.26, - Multiplier: 1 - 0.02*float64(paladin.Talents.Benediction), - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - IgnoreHaste: true, - CD: core.Cooldown{ - Timer: paladin.NewTimer(), - Duration: time.Second * 30, - }, - }, - - DamageMultiplier: 1, - CritMultiplier: paladin.MeleeCritMultiplier(), - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - constBaseDamage := .07*spell.SpellDamage() + .07*spell.MeleeAttackPower() - - curTarget := target - for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { - baseDamage := constBaseDamage + sim.Roll(1100, 1344) - - results[hitIndex] = spell.CalcDamage(sim, curTarget, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) - curTarget = sim.Environment.NextTargetUnit(curTarget) - } - - curTarget = target - for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { - spell.DealDamage(sim, results[hitIndex]) - curTarget = sim.Environment.NextTargetUnit(curTarget) - } - }, - }) -} diff --git a/sim/_paladin/avenging_wrath.go b/sim/_paladin/avenging_wrath.go deleted file mode 100644 index bcb399a16..000000000 --- a/sim/_paladin/avenging_wrath.go +++ /dev/null @@ -1,61 +0,0 @@ -package paladin - -import ( - "time" - - "github.com/wowsims/classic/sim/core" -) - -func (paladin *Paladin) RegisterAvengingWrathCD() { - actionID := core.ActionID{SpellID: 31884} - - paladin.AvengingWrathAura = paladin.RegisterAura(core.Aura{ - Label: "Avenging Wrath", - ActionID: actionID, - Duration: time.Second * 20, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.PseudoStats.DamageDealtMultiplier *= 1.2 - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - aura.Unit.PseudoStats.DamageDealtMultiplier /= 1.2 - }, - }) - core.RegisterPercentDamageModifierEffect(paladin.AvengingWrathAura, 1.2) - - paladin.AvengingWrath = paladin.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.08, - }, - Cast: core.CastConfig{ - CD: core.Cooldown{ - Timer: paladin.NewTimer(), - Duration: time.Minute*3 - (time.Second * time.Duration(30*paladin.Talents.SanctifiedWrath)), - }, - SharedCD: core.Cooldown{ - Timer: paladin.GetMutualLockoutDPAW(), - Duration: 30 * time.Second, - }, - }, - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, _ *core.Spell) { - paladin.AvengingWrathAura.Activate(sim) - }, - }) - - paladin.AddMajorCooldown(core.MajorCooldown{ - Spell: paladin.AvengingWrath, - Type: core.CooldownTypeDPS, - // modify this logic if it should ever not be spammed on CD / maybe should synced with other CDs - ShouldActivate: func(sim *core.Simulation, character *core.Character) bool { - if paladin.CurrentSeal == paladin.SealOfVengeanceAura { - if paladin.SovDotSpell.Dot(paladin.CurrentTarget).GetStacks() < 5 { - return false - } - } - - return true - }, - }) -} diff --git a/sim/_paladin/divine_plea.go b/sim/_paladin/divine_plea.go deleted file mode 100644 index 51d004de8..000000000 --- a/sim/_paladin/divine_plea.go +++ /dev/null @@ -1,50 +0,0 @@ -package paladin - -import ( - "time" - - "github.com/wowsims/classic/sim/core" -) - -func (paladin *Paladin) registerDivinePleaSpell() { - actionID := core.ActionID{SpellID: 54428} - manaMetrics := paladin.NewManaMetrics(actionID) - var manaPA *core.PendingAction - - paladin.DivinePleaAura = paladin.RegisterAura(core.Aura{ - Label: "Divine Plea", - ActionID: actionID, - Duration: time.Second*15 + 1, // Add 1 to make sure the last tick takes effect - OnGain: func(aura *core.Aura, sim *core.Simulation) { - manaPA = core.StartPeriodicAction(sim, core.PeriodicActionOptions{ - Period: time.Second * 3, - OnAction: func(sim *core.Simulation) { - paladin.AddMana(sim, 0.05*paladin.MaxMana(), manaMetrics) - }, - }) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - manaPA.Cancel(sim) - }, - }) - - paladin.DivinePlea = paladin.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - SpellSchool: core.SpellSchoolHoly, - Flags: core.SpellFlagAPL, - - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - CD: core.Cooldown{ - Timer: paladin.NewTimer(), - Duration: time.Minute * 1, - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - paladin.DivinePleaAura.Activate(sim) - }, - }) -} diff --git a/sim/_paladin/divine_protection.go b/sim/_paladin/divine_protection.go deleted file mode 100644 index e45a12b9a..000000000 --- a/sim/_paladin/divine_protection.go +++ /dev/null @@ -1,72 +0,0 @@ -package paladin - -import ( - "time" - - "github.com/wowsims/classic/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 848347cef..000000000 --- a/sim/_paladin/hand_of_reckoning.go +++ /dev/null @@ -1,37 +0,0 @@ -package paladin - -import ( - "time" - - "github.com/wowsims/classic/sim/core" -) - -func (paladin *Paladin) registerHandOfReckoningSpell() { - 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/holy.go b/sim/_paladin/holy/holy.go deleted file mode 100644 index 8db836f9a..000000000 --- a/sim/_paladin/holy/holy.go +++ /dev/null @@ -1,55 +0,0 @@ -package holy - -import ( - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" - "github.com/wowsims/classic/sim/paladin" -) - -func RegisterHolyPaladin() { - core.RegisterAgentFactory( - proto.Player_HolyPaladin{}, - proto.Spec_SpecHolyPaladin, - func(character *core.Character, options *proto.Player) core.Agent { - return NewHolyPaladin(character, options) - }, - func(player *proto.Player, spec interface{}) { - playerSpec, ok := spec.(*proto.Player_HolyPaladin) // I don't really understand this line - if !ok { - panic("Invalid spec value for Holy Paladin!") - } - player.Spec = playerSpec - }, - ) -} - -func NewHolyPaladin(character *core.Character, options *proto.Player) *HolyPaladin { - holyOptions := options.GetHolyPaladin() - - holy := &HolyPaladin{ - Paladin: paladin.NewPaladin(character, options, holyOptions.Options.Aura), - Options: holyOptions.Options, - } - - holy.PaladinAura = holyOptions.Options.Aura - - return holy -} - -type HolyPaladin struct { - *paladin.Paladin - - Options *proto.HolyPaladin_Options -} - -func (holy *HolyPaladin) GetPaladin() *paladin.Paladin { - return holy.Paladin -} - -func (holy *HolyPaladin) Initialize() { - holy.Paladin.Initialize() -} - -func (holy *HolyPaladin) Reset(sim *core.Simulation) { - holy.Paladin.Reset(sim) -} diff --git a/sim/_paladin/holy/holy_test.go b/sim/_paladin/holy/holy_test.go deleted file mode 100644 index 9c8ff3b7c..000000000 --- a/sim/_paladin/holy/holy_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package holy - -import ( - "testing" - - _ "github.com/wowsims/classic/sim/common" // imported to get item effects included. - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -func init() { - RegisterHolyPaladin() -} - -func TestHoly(t *testing.T) { - core.RunTestSuite(t, t.Name(), core.FullCharacterTestSuiteGenerator(core.CharacterSuiteConfig{ - Class: proto.Class_ClassPaladin, - Race: proto.Race_RaceHuman, - OtherRaces: []proto.Race{proto.Race_RaceHuman}, - - GearSet: core.GetGearSet("../../../ui/holy_paladin/gear_sets", "p1"), - Talents: StandardTalents, - Consumes: FullConsumes, - SpecOptions: core.SpecOptionsCombo{Label: "Basic", SpecOptions: BasicOptions}, - Rotation: core.RotationCombo{Label: "Default", Rotation: DefaultRotation}, - - IsHealer: true, - InFrontOfTarget: true, - - ItemFilter: core.ItemFilter{ - WeaponTypes: []proto.WeaponType{ - proto.WeaponType_WeaponTypeSword, - proto.WeaponType_WeaponTypePolearm, - proto.WeaponType_WeaponTypeMace, - proto.WeaponType_WeaponTypeShield, - }, - ArmorType: proto.ArmorType_ArmorTypePlate, - RangedWeaponTypes: []proto.RangedWeaponType{ - proto.RangedWeaponType_RangedWeaponTypeLibram, - }, - }, - })) -} - -var StandardTalents = "50350151020013053100515221-50023131203" - -var defaultProtOptions = &proto.HolyPaladin_Options{ - Judgement: proto.PaladinJudgement_JudgementOfWisdom, - Aura: proto.PaladinAura_DevotionAura, -} - -var BasicOptions = &proto.Player_HolyPaladin{ - HolyPaladin: &proto.HolyPaladin{ - Options: defaultProtOptions, - }, -} - -var FullConsumes = &proto.Consumes{ - Flask: proto.Flask_FlaskOfStoneblood, - Food: proto.Food_FoodDragonfinFilet, - DefaultPotion: proto.Potions_IndestructiblePotion, - PrepopPotion: proto.Potions_IndestructiblePotion, - DefaultConjured: proto.Conjured_ConjuredDarkRune, -} - -var DefaultRotation = core.APLRotationFromJsonString(`{ - "type": "TypeAPL", - "priorityList": [ - {"action":{"autocastOtherCooldowns":{}}} - ] -}`) diff --git a/sim/_paladin/holy_shield.go b/sim/_paladin/holy_shield.go deleted file mode 100644 index fe5182e47..000000000 --- a/sim/_paladin/holy_shield.go +++ /dev/null @@ -1,79 +0,0 @@ -package paladin - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/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.SpellDamage() - - 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.DidBlock() { - 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/items.go b/sim/_paladin/items.go deleted file mode 100644 index 7f18a48a2..000000000 --- a/sim/_paladin/items.go +++ /dev/null @@ -1,533 +0,0 @@ -package paladin - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/stats" -) - -// Tier 6 ret -var ItemSetLightbringerBattlegear = core.NewItemSet(core.ItemSet{ - Name: "Lightbringer Battlegear", - Bonuses: map[int32]core.ApplyEffect{ - 2: func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - manaMetrics := paladin.NewManaMetrics(core.ActionID{SpellID: 38428}) - - paladin.RegisterAura(core.Aura{ - Label: "Lightbringer Battlegear 2pc", - 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.ProcMask.Matches(core.ProcMaskMelee) { - return - } - if sim.RandomFloat("lightbringer 2pc") > 0.2 { - return - } - paladin.AddMana(sim, 50, manaMetrics) - }, - }) - }, - 4: func(agent core.Agent) { - // Implemented in hammer_of_wrath.go - }, - }, -}) - -func (paladin *Paladin) getItemSetLightbringerBattlegearBonus4() float64 { - return core.TernaryFloat64(paladin.HasSetBonus(ItemSetLightbringerBattlegear, 4), .1, 0) -} - -// Tier 7 ret -var ItemSetRedemptionBattlegear = core.NewItemSet(core.ItemSet{ - Name: "Redemption Battlegear", - Bonuses: map[int32]core.ApplyEffect{ - 2: func(agent core.Agent) { - // Implemented in divine_storm.go - }, - 4: func(agent core.Agent) { - // Implemented in judgement.go - }, - }, -}) - -func (paladin *Paladin) getItemSetRedemptionBattlegearBonus2() float64 { - return core.TernaryFloat64(paladin.HasSetBonus(ItemSetRedemptionBattlegear, 2), .1, 0) -} - -// Tier 8 ret -var ItemSetAegisBattlegear = core.NewItemSet(core.ItemSet{ - Name: "Aegis Battlegear", - Bonuses: map[int32]core.ApplyEffect{ - 2: func(agent core.Agent) { - // Implemented in exorcism.go & hammer_of_wrath.go - }, - 4: func(agent core.Agent) { - // Implemented in divine_storm.go & crusader_strike.go - }, - }, -}) - -func (paladin *Paladin) getItemSetAegisBattlegearBonus2() float64 { - return core.TernaryFloat64(paladin.HasSetBonus(ItemSetAegisBattlegear, 2), .1, 0) -} - -// Tier 9 ret (Alliance/Horde) -var ItemSetTuralyonsBattlegear = core.NewItemSet(core.ItemSet{ - Name: "Turalyon's Battlegear", - AlternativeName: "Liadrin's Battlegear", - Bonuses: map[int32]core.ApplyEffect{ - 2: func(agent core.Agent) { - // Implemented in talents.go (Righteous Vengeance) - }, - 4: func(agent core.Agent) { - // Implemented in soc.go, sor.go, sov.go - }, - }, -}) - -// Tier 10 ret -var ItemSetLightswornBattlegear = core.NewItemSet(core.ItemSet{ - Name: "Lightsworn Battlegear", - Bonuses: map[int32]core.ApplyEffect{ - 2: func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - - procSpell := paladin.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 70765}, - ApplyEffects: func(_ *core.Simulation, _ *core.Unit, _ *core.Spell) { - paladin.DivineStorm.CD.Reset() - }, - }) - - paladin.RegisterAura(core.Aura{ - Label: "Lightsworn Battlegear 2pc", - 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.ProcMask.Matches(core.ProcMaskMeleeWhiteHit) { - return - } - if sim.RandomFloat("lightsworn 2pc") > 0.4 { - return - } - procSpell.Cast(sim, &paladin.Unit) - }, - }) - }, - 4: func(agent core.Agent) { - // Implemented in soc.go, sor.go, sov.go - }, - }, -}) - -func (paladin *Paladin) getItemSetLightswornBattlegearBonus4() float64 { - return core.TernaryFloat64(paladin.HasSetBonus(ItemSetLightswornBattlegear, 4), .1, 0) -} - -// PvP ret -var ItemSetGladiatorsVindication = core.NewItemSet(core.ItemSet{ - Name: "Gladiator's Vindication", - Bonuses: map[int32]core.ApplyEffect{ - 2: func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - paladin.AddStat(stats.AttackPower, 50) - paladin.AddStat(stats.Resilience, 100) - }, - 4: func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - paladin.AddStat(stats.AttackPower, 150) - // Rest implemented in judgement.go - }, - }, -}) - -// Tier 7 prot -var ItemSetRedemptionPlate = core.NewItemSet(core.ItemSet{ - Name: "Redemption Plate", - Bonuses: map[int32]core.ApplyEffect{ - 2: func(agent core.Agent) { - // Implemented in hammer_of_the_righteous.go - }, - 4: func(agent core.Agent) { - // TODO: increase duration of divine shield by 3sec - // Implemented in divine_protection.go - }, - }, -}) - -func (paladin *Paladin) getItemSetRedemptionPlateBonus2() float64 { - return core.TernaryFloat64(paladin.HasSetBonus(ItemSetRedemptionPlate, 2), .1, 0) -} - -// Tier 8 prot -var ItemSetAegisPlate = core.NewItemSet(core.ItemSet{ - Name: "Aegis Plate", - Bonuses: map[int32]core.ApplyEffect{ - 2: func(agent core.Agent) { - // Implemented in sov.go - }, - 4: func(agent core.Agent) { - // Implemented in shield_of_righteousness.go - }, - }, -}) - -func (paladin *Paladin) getItemSetAegisPlateBonus2() float64 { - return core.TernaryFloat64(paladin.HasSetBonus(ItemSetAegisPlate, 2), .1, 0) -} - -// Tier 9 prot (Alliance/Horde) -var ItemSetTuralyonsPlate = core.NewItemSet(core.ItemSet{ - Name: "Turalyon's Plate", - AlternativeName: "Liadrin's Plate", - Bonuses: map[int32]core.ApplyEffect{ - 2: func(agent core.Agent) { - // Implemented in hammer_of_the_righteous.go - // TODO: Implement Hand of Reckoning bonus, if it ever becomes relevant - }, - 4: func(agent core.Agent) { - // Implemented in divine_protection.go - }, - }, -}) - -func (paladin *Paladin) getItemSetT9PlateBonus2() float64 { - return core.TernaryFloat64(paladin.HasSetBonus(ItemSetTuralyonsPlate, 2), .05, 0) -} - -// Tier 10 prot -var ItemSetLightswornPlate = core.NewItemSet(core.ItemSet{ - Name: "Lightsworn Plate", - Bonuses: map[int32]core.ApplyEffect{ - 2: func(agent core.Agent) { - // Implemented in hammer_of_the_righteous.go - }, - 4: func(agent core.Agent) { - // TODO: When you activate Divine Plea, you gain 12% dodge for 10 sec - }, - }, -}) - -func (paladin *Paladin) getItemSetLightswornPlateBonus2() float64 { - return core.TernaryFloat64(paladin.HasSetBonus(ItemSetLightswornPlate, 2), .2, 0) -} - -func (paladin *Paladin) getItemSetGladiatorsVindicationBonusGloves() float64 { - switch paladin.Hands().ID { - case 40798, 40802, 40805, 40808, 40812, 51475: // S5a Hateful, S5b Hateful, S5c Deadly, S6 Furious, S7 Relentless, S8 Wrathful - return 0.05 - default: - return 0 - } -} - -func init() { - // Librams implemented in seals.go and judgement.go - - core.NewItemEffect(37574, func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - procAura := paladin.NewTemporaryStatsAura("Libram of Furious Blows Proc", core.ActionID{SpellID: 48835}, stats.Stats{stats.MeleeCrit: 61, stats.SpellCrit: 61}, time.Second*5) - - paladin.RegisterAura(core.Aura{ - Label: "Libram of Furious Blows", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.Flags.Matches(SpellFlagSecondaryJudgement) { - procAura.Activate(sim) - } - }, - }) - }) - - core.NewItemEffect(40706, func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - procAura := paladin.NewTemporaryStatsAura("Libram of Reciprocation Proc", core.ActionID{SpellID: 60819}, stats.Stats{stats.MeleeCrit: 173, stats.SpellCrit: 173}, time.Second*10) - - paladin.RegisterAura(core.Aura{ - Label: "Libram of Reciprocation", - 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 paladin.CurrentSeal == paladin.SealOfCommandAura && spell.Flags.Matches(SpellFlagSecondaryJudgement) { - if sim.RandomFloat("Libram of Reciprocation") > 0.15 { - return - } - procAura.Activate(sim) - } - }, - }) - }) - - core.NewItemEffect(42611, func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - procAura := paladin.NewTemporaryStatsAura("Savage Gladiator's Libram of Fortitude Proc", core.ActionID{SpellID: 60577}, stats.Stats{stats.AttackPower: 94}, time.Second*6) - - paladin.RegisterAura(core.Aura{ - Label: "Savage Gladiator's Libram of Fortitude", - 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.SpellID == paladin.CrusaderStrike.SpellID { - procAura.Activate(sim) - } - }, - }) - }) - - core.NewItemEffect(42851, func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - procAura := paladin.NewTemporaryStatsAura("Hateful Gladiator's Libram of Fortitude Proc", core.ActionID{SpellID: 60632}, stats.Stats{stats.AttackPower: 106}, time.Second*6) - - paladin.RegisterAura(core.Aura{ - Label: "Savage Gladiator's Libram of Fortitude", - 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.SpellID == paladin.CrusaderStrike.SpellID { - procAura.Activate(sim) - } - }, - }) - }) - - core.NewItemEffect(42852, func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - procAura := paladin.NewTemporaryStatsAura("Deadly Gladiator's Libram of Fortitude Proc", core.ActionID{SpellID: 60633}, stats.Stats{stats.AttackPower: 120}, time.Second*10) - - paladin.RegisterAura(core.Aura{ - Label: "Deadly Gladiator's Libram of Fortitude", - 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.SpellID == paladin.CrusaderStrike.SpellID { - procAura.Activate(sim) - } - }, - }) - }) - - core.NewItemEffect(42853, func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - procAura := paladin.NewTemporaryStatsAura("Furious Gladiator's Libram of Fortitude Proc", core.ActionID{SpellID: 60634}, stats.Stats{stats.AttackPower: 144}, time.Second*10) - - paladin.RegisterAura(core.Aura{ - Label: "Furious Gladiator's Libram of Fortitude", - 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.SpellID == paladin.CrusaderStrike.SpellID { - procAura.Activate(sim) - } - }, - }) - }) - - core.NewItemEffect(42854, func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - procAura := paladin.NewTemporaryStatsAura("Relentless Gladiator's Libram of Fortitude Proc", core.ActionID{SpellID: 60635}, stats.Stats{stats.AttackPower: 172}, time.Second*10) - - paladin.RegisterAura(core.Aura{ - Label: "Relentless Gladiator's Libram of Fortitude", - 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.SpellID == paladin.CrusaderStrike.SpellID { - procAura.Activate(sim) - } - }, - }) - }) - - core.NewItemEffect(51478, func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - procAura := paladin.NewTemporaryStatsAura("Wrathful Gladiator's Libram of Fortitude Proc", core.ActionID{SpellID: 60636}, stats.Stats{stats.AttackPower: 204}, time.Second*10) - - paladin.RegisterAura(core.Aura{ - Label: "Wrathful Gladiator's Libram of Fortitude", - 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.SpellID == paladin.CrusaderStrike.SpellID { - procAura.Activate(sim) - } - }, - }) - }) - - core.NewItemEffect(50455, func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - - procAura := core.MakeStackingAura(paladin.GetCharacter(), core.StackingStatAura{ - Aura: core.Aura{ - Label: "Formidable", - ActionID: core.ActionID{SpellID: 71187}, - Duration: time.Second * 15, - MaxStacks: 5, - }, - BonusPerStack: stats.Stats{stats.Strength: 44}, - }) - - paladin.RegisterAura(core.Aura{ - Label: "Libram Of Three Truths", - 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.SpellID == paladin.CrusaderStrike.SpellID { - procAura.Activate(sim) - procAura.AddStack(sim) - } - }, - }) - }) - - core.NewItemEffect(47661, func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - procAura := paladin.NewTemporaryStatsAura("Libram Of Valiance Proc", core.ActionID{SpellID: 67365}, stats.Stats{stats.Strength: 200}, time.Second*15) - - icd := core.Cooldown{ - Timer: paladin.NewTimer(), - Duration: time.Second * 8, - } - procAura.Icd = &icd - - paladin.RegisterAura(core.Aura{ - Label: "Libram Of Valiance", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnPeriodicDamageDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell == paladin.SovDotSpell { - if !icd.IsReady(sim) || sim.RandomFloat("Libram of Valiance") > 0.70 { - return - } - icd.Use(sim) - - procAura.Activate(sim) - } - }, - }) - }) - - core.NewItemEffect(32368, func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - procAura := paladin.NewTemporaryStatsAura("Tome of the Lightbringer Proc", core.ActionID{SpellID: 41042}, stats.Stats{stats.BlockValue: 186}, time.Second*10) - - paladin.RegisterAura(core.Aura{ - Label: "Tome of the Lightbringer", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.Flags.Matches(SpellFlagPrimaryJudgement) { - procAura.Activate(sim) - } - }, - }) - }) - - core.NewItemEffect(40707, func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - procAura := paladin.NewTemporaryStatsAura("Libram of Obstruction Proc", core.ActionID{SpellID: 60794}, stats.Stats{stats.BlockValue: 352}, time.Second*10) - - paladin.RegisterAura(core.Aura{ - Label: "Libram of Obstruction", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.Flags.Matches(SpellFlagPrimaryJudgement) { - procAura.Activate(sim) - } - }, - }) - }) - - core.NewItemEffect(45145, func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - procAura := paladin.NewTemporaryStatsAura("Libram of the Sacred Shield Proc", core.ActionID{SpellID: 65182}, stats.Stats{stats.BlockValue: 450}, time.Second*20) - - paladin.RegisterAura(core.Aura{ - Label: "Libram of the Sacred Shield", - 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.SpellID == paladin.HolyShield.SpellID { - procAura.Activate(sim) - } - }, - }) - }) - - core.NewItemEffect(32489, func(agent core.Agent) { - paladin := agent.(PaladinAgent).GetPaladin() - - // The spell effect is https://www.wowhead.com/wotlk/spell=40472/enduring-judgement, most likely - dotSpell := paladin.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{ItemID: 32489}, - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskEmpty, - DamageMultiplier: 1, - ThreatMultiplier: 1, - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "AshtongueTalismanOfZeal", - }, - NumberOfTicks: 4, - TickLength: time.Second * 2, - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.SnapshotBaseDamage = 480 / 4 - dot.SnapshotAttackerMultiplier = dot.Spell.AttackerDamageMultiplier(dot.Spell.Unit.AttackTables[target.UnitIndex][dot.Spell.CastType]) - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTick) - }, - }, - }) - - paladin.RegisterAura(core.Aura{ - Label: "Ashtongue Talisman of Zeal", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.Flags.Matches(SpellFlagPrimaryJudgement) && sim.RandomFloat("AshtongueTalismanOfZeal") < 0.5 { - dotSpell.Dot(result.Target).Apply(sim) - } - }, - }) - }) - -} diff --git a/sim/_paladin/paladin.go b/sim/_paladin/paladin.go deleted file mode 100644 index f60a38d80..000000000 --- a/sim/_paladin/paladin.go +++ /dev/null @@ -1,196 +0,0 @@ -package paladin - -import ( - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" - "github.com/wowsims/classic/sim/core/stats" -) - -const ( - SpellFlagSecondaryJudgement = core.SpellFlagAgentReserved1 - SpellFlagPrimaryJudgement = core.SpellFlagAgentReserved2 -) - -var TalentTreeSizes = [3]int{26, 26, 26} - -type Paladin struct { - core.Character - - PaladinAura proto.PaladinAura - - Talents *proto.PaladinTalents - - CurrentSeal *core.Aura - CurrentJudgement *core.Aura - - DivinePlea *core.Spell - DivineStorm *core.Spell - HolyWrath *core.Spell - Consecration *core.Spell - CrusaderStrike *core.Spell - Exorcism *core.Spell - HolyShield *core.Spell - HammerOfTheRighteous *core.Spell - HandOfReckoning *core.Spell - ShieldOfRighteousness *core.Spell - AvengersShield *core.Spell - JudgementOfWisdom *core.Spell - JudgementOfLight *core.Spell - HammerOfWrath *core.Spell - SealOfVengeance *core.Spell - SealOfRighteousness *core.Spell - SealOfCommand *core.Spell - AvengingWrath *core.Spell - DivineProtection *core.Spell - SovDotSpell *core.Spell - // SealOfWisdom *core.Spell - // SealOfLight *core.Spell - - HolyShieldAura *core.Aura - RighteousFuryAura *core.Aura - DivinePleaAura *core.Aura - JudgementOfWisdomAura *core.Aura - JudgementOfLightAura *core.Aura - SealOfVengeanceAura *core.Aura - SealOfCommandAura *core.Aura - SealOfRighteousnessAura *core.Aura - AvengingWrathAura *core.Aura - DivineProtectionAura *core.Aura - ForbearanceAura *core.Aura - VengeanceAura *core.Aura - - // SealOfWisdomAura *core.Aura - // SealOfLightAura *core.Aura - - ArtOfWarInstantCast *core.Aura - - SpiritualAttunementMetrics *core.ResourceMetrics - - HasTuralyonsOrLiadrinsBattlegear2Pc bool - - DemonAndUndeadTargetCount int32 - - mutualLockoutDPAW *core.Timer -} - -// Implemented by each Paladin spec. -type PaladinAgent interface { - GetPaladin() *Paladin -} - -func (paladin *Paladin) GetCharacter() *core.Character { - return &paladin.Character -} - -func (paladin *Paladin) GetPaladin() *Paladin { - return paladin -} - -func (paladin *Paladin) AddRaidBuffs(raidBuffs *proto.RaidBuffs) { - raidBuffs.DevotionAura = max(raidBuffs.DevotionAura, core.MakeTristateValue( - paladin.PaladinAura == proto.PaladinAura_DevotionAura, - paladin.Talents.ImprovedDevotionAura == 5)) - - raidBuffs.RetributionAura = max(raidBuffs.RetributionAura, core.MakeTristateValue( - paladin.PaladinAura == proto.PaladinAura_RetributionAura, - paladin.Talents.ImprovedRetributionAura == 2)) - - if paladin.Talents.SanctifiedRetribution { - raidBuffs.SanctifiedRetribution = true - } - - if paladin.Talents.SwiftRetribution == 3 { - raidBuffs.SwiftRetribution = paladin.Talents.SwiftRetribution == 3 // TODO: Fix-- though having something between 0/3 and 3/3 is unlikely - } - - // TODO: Figure out a way to just start with 1 DG cooldown available without making a redundant Spell - //if paladin.Talents.DivineGuardian == 2 { - // raidBuffs.divineGuardians++ - //} -} - -func (paladin *Paladin) AddPartyBuffs(_ *proto.PartyBuffs) { -} - -func (paladin *Paladin) Initialize() { - // Update auto crit multipliers now that we have the targets. - paladin.AutoAttacks.MHConfig().CritMultiplier = paladin.MeleeCritMultiplier() - - paladin.registerSealOfVengeanceSpellAndAura() - paladin.registerSealOfRighteousnessSpellAndAura() - paladin.registerSealOfCommandSpellAndAura() - // paladin.setupSealOfTheCrusader() - // paladin.setupSealOfWisdom() - // paladin.setupSealOfLight() - // paladin.setupSealOfRighteousness() - // paladin.setupJudgementRefresh() - - paladin.registerCrusaderStrikeSpell() - paladin.registerDivineStormSpell() - paladin.registerConsecrationSpell() - paladin.registerHammerOfWrathSpell() - paladin.registerHolyWrathSpell() - - paladin.registerExorcismSpell() - paladin.registerHolyShieldSpell() - paladin.registerHammerOfTheRighteousSpell() - // paladin.registerHandOfReckoningSpell() - paladin.registerShieldOfRighteousnessSpell() - paladin.registerAvengersShieldSpell() - paladin.registerJudgements() - - paladin.registerSpiritualAttunement() - paladin.registerDivinePleaSpell() - paladin.registerDivineProtectionSpell() - paladin.registerForbearanceDebuff() - - for i := int32(0); i < paladin.Env.GetNumTargets(); i++ { - unit := paladin.Env.GetTargetUnit(i) - if unit.MobType == proto.MobType_MobTypeDemon || unit.MobType == proto.MobType_MobTypeUndead { - paladin.DemonAndUndeadTargetCount += 1 - } - } -} - -func (paladin *Paladin) Reset(_ *core.Simulation) { - paladin.CurrentSeal = nil - paladin.CurrentJudgement = nil -} - -// maybe need to add stat dependencies -func NewPaladin(character *core.Character, options *proto.Player, pallyAura proto.PaladinAura) *Paladin { - paladin := &Paladin{ - Character: *character, - Talents: &proto.PaladinTalents{}, - } - core.FillTalentsProto(paladin.Talents.ProtoReflect(), talentsStr, TalentTreeSizes) - - // This is used to cache its effect in talents.go - paladin.HasTuralyonsOrLiadrinsBattlegear2Pc = paladin.HasSetBonus(ItemSetTuralyonsBattlegear, 2) - - paladin.PseudoStats.CanParry = true - - paladin.EnableManaBar() - paladin.AddStatDependency(stats.Strength, stats.AttackPower, 2.0) - paladin.AddStatDependency(stats.Agility, stats.MeleeCrit, core.CritPerAgiAtLevel[character.Class][int(paladin.Level)]*core.CritRatingPerCritChance) - paladin.AddStatDependency(stats.Intellect, stats.SpellCrit, core.CritPerIntAtLevel[character.Class][int(paladin.Level)]*core.SpellCritRatingPerCritChance) - - // 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 - - // Paladins get 1 block value per 2 str - paladin.AddStatDependency(stats.Strength, stats.BlockValue, .5) - - // Bonus Armor and Armor are treated identically for Paladins - paladin.AddStatDependency(stats.BonusArmor, stats.Armor, 1) - - return paladin -} - -// Shared 30sec cooldown for Divine Protection and Avenging Wrath -func (paladin *Paladin) GetMutualLockoutDPAW() *core.Timer { - return paladin.Character.GetOrInitTimer(&paladin.mutualLockoutDPAW) -} diff --git a/sim/_paladin/protection/protection.go b/sim/_paladin/protection/protection.go deleted file mode 100644 index 9b6be4d93..000000000 --- a/sim/_paladin/protection/protection.go +++ /dev/null @@ -1,92 +0,0 @@ -package protection - -import ( - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" - "github.com/wowsims/classic/sim/paladin" -) - -func RegisterProtectionPaladin() { - core.RegisterAgentFactory( - proto.Player_ProtectionPaladin{}, - proto.Spec_SpecProtectionPaladin, - func(character *core.Character, options *proto.Player) core.Agent { - return NewProtectionPaladin(character, options) - }, - func(player *proto.Player, spec interface{}) { - playerSpec, ok := spec.(*proto.Player_ProtectionPaladin) // I don't really understand this line - if !ok { - panic("Invalid spec value for Protection Paladin!") - } - player.Spec = playerSpec - }, - ) -} - -func NewProtectionPaladin(character *core.Character, options *proto.Player) *ProtectionPaladin { - protOptions := options.GetProtectionPaladin() - - prot := &ProtectionPaladin{ - Paladin: paladin.NewPaladin(character, options, protOptions.Options.Aura), - Options: protOptions.Options, - Seal: protOptions.Options.Seal, - } - - prot.PaladinAura = protOptions.Options.Aura - - prot.EnableAutoAttacks(prot, core.AutoAttackOptions{ - MainHand: prot.WeaponFromMainHand(), // Set crit multiplier later when we have targets. - AutoSwingMelee: true, - }) - - healingModel := options.HealingModel - if healingModel != nil { - if healingModel.InspirationUptime > 0.0 { - core.ApplyInspiration(prot.GetCharacter(), healingModel.InspirationUptime) - } - } - - return prot -} - -type ProtectionPaladin struct { - *paladin.Paladin - - Options *proto.ProtectionPaladin_Options - - Judgement proto.PaladinJudgement - - Seal proto.PaladinSeal -} - -func (prot *ProtectionPaladin) GetPaladin() *paladin.Paladin { - return prot.Paladin -} - -func (prot *ProtectionPaladin) Initialize() { - prot.Paladin.Initialize() - prot.ActivateRighteousFury() - - if prot.Options.UseAvengingWrath { - prot.RegisterAvengingWrathCD() - } -} - -func (prot *ProtectionPaladin) Reset(sim *core.Simulation) { - prot.Paladin.Reset(sim) - - switch prot.Seal { - case proto.PaladinSeal_Vengeance: - prot.CurrentSeal = prot.SealOfVengeanceAura - prot.SealOfVengeanceAura.Activate(sim) - case proto.PaladinSeal_Command: - prot.CurrentSeal = prot.SealOfCommandAura - prot.SealOfCommandAura.Activate(sim) - case proto.PaladinSeal_Righteousness: - prot.CurrentSeal = prot.SealOfRighteousnessAura - prot.SealOfRighteousnessAura.Activate(sim) - } - - prot.RighteousFuryAura.Activate(sim) - prot.Paladin.PseudoStats.Stunned = false -} diff --git a/sim/_paladin/protection/protection_test.go b/sim/_paladin/protection/protection_test.go deleted file mode 100644 index 004d1d1b5..000000000 --- a/sim/_paladin/protection/protection_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package protection - -import ( - "testing" - - _ "github.com/wowsims/classic/sim/common" // imported to get item effects included. - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -func init() { - RegisterProtectionPaladin() -} - -func TestProtection(t *testing.T) { - core.RunTestSuite(t, t.Name(), core.FullCharacterTestSuiteGenerator(core.CharacterSuiteConfig{ - Class: proto.Class_ClassPaladin, - Race: proto.Race_RaceHuman, - OtherRaces: []proto.Race{proto.Race_RaceHuman}, - - GearSet: core.GetGearSet("../../../ui/protection_paladin/gear_sets", "p1"), - Talents: StandardTalents, - Consumes: FullConsumes, - SpecOptions: core.SpecOptionsCombo{Label: "Protection Paladin SOV", SpecOptions: DefaultOptions}, - OtherSpecOptions: []core.SpecOptionsCombo{ - { - Label: "Protection Paladin SOC", - SpecOptions: &proto.Player_ProtectionPaladin{ - ProtectionPaladin: &proto.ProtectionPaladin{ - Options: &proto.ProtectionPaladin_Options{ - Judgement: proto.PaladinJudgement_JudgementOfWisdom, - Seal: proto.PaladinSeal_Command, - Aura: proto.PaladinAura_RetributionAura, - }, - }, - }, - }, - { - Label: "Protection Paladin SOR", - SpecOptions: &proto.Player_ProtectionPaladin{ - ProtectionPaladin: &proto.ProtectionPaladin{ - Options: &proto.ProtectionPaladin_Options{ - Judgement: proto.PaladinJudgement_JudgementOfWisdom, - Seal: proto.PaladinSeal_Righteousness, - Aura: proto.PaladinAura_RetributionAura, - }, - }, - }, - }, - }, - Rotation: core.GetAplRotation("../../../ui/protection_paladin/apls", "default"), - - IsTank: true, - InFrontOfTarget: true, - - ItemFilter: core.ItemFilter{ - WeaponTypes: []proto.WeaponType{ - proto.WeaponType_WeaponTypeSword, - proto.WeaponType_WeaponTypePolearm, - proto.WeaponType_WeaponTypeMace, - proto.WeaponType_WeaponTypeShield, - }, - ArmorType: proto.ArmorType_ArmorTypePlate, - RangedWeaponTypes: []proto.RangedWeaponType{ - proto.RangedWeaponType_RangedWeaponTypeLibram, - }, - }, - })) -} - -var StandardTalents = "-05005135200132311333312321-511302012003" - -var defaultProtOptions = &proto.ProtectionPaladin_Options{ - Judgement: proto.PaladinJudgement_JudgementOfWisdom, - Seal: proto.PaladinSeal_Vengeance, - Aura: proto.PaladinAura_RetributionAura, -} - -var DefaultOptions = &proto.Player_ProtectionPaladin{ - ProtectionPaladin: &proto.ProtectionPaladin{ - Options: defaultProtOptions, - }, -} - -var FullConsumes = &proto.Consumes{ - Flask: proto.Flask_FlaskOfStoneblood, - Food: proto.Food_FoodDragonfinFilet, - DefaultPotion: proto.Potions_IndestructiblePotion, - PrepopPotion: proto.Potions_IndestructiblePotion, - DefaultConjured: proto.Conjured_ConjuredDarkRune, -} diff --git a/sim/_paladin/retribution/retribution.go b/sim/_paladin/retribution/retribution.go deleted file mode 100644 index c16aca6f3..000000000 --- a/sim/_paladin/retribution/retribution.go +++ /dev/null @@ -1,75 +0,0 @@ -package retribution - -import ( - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" - "github.com/wowsims/classic/sim/paladin" -) - -func RegisterRetributionPaladin() { - core.RegisterAgentFactory( - proto.Player_RetributionPaladin{}, - proto.Spec_SpecRetributionPaladin, - func(character *core.Character, options *proto.Player) core.Agent { - return NewRetributionPaladin(character, options) - }, - func(player *proto.Player, spec interface{}) { - playerSpec, ok := spec.(*proto.Player_RetributionPaladin) // I don't really understand this line - if !ok { - panic("Invalid spec value for Retribution Paladin!") - } - player.Spec = playerSpec - }, - ) -} - -func NewRetributionPaladin(character *core.Character, options *proto.Player) *RetributionPaladin { - retOptions := options.GetRetributionPaladin() - - pal := paladin.NewPaladin(character, options, retOptions.Options.Aura) - - ret := &RetributionPaladin{ - Paladin: pal, - Seal: retOptions.Options.Seal, - } - - ret.PaladinAura = retOptions.Options.Aura - - ret.EnableAutoAttacks(ret, core.AutoAttackOptions{ - MainHand: ret.WeaponFromMainHand(), // Set crit multiplier later when we have targets. - AutoSwingMelee: true, - }) - - return ret -} - -type RetributionPaladin struct { - *paladin.Paladin - - Seal proto.PaladinSeal -} - -func (ret *RetributionPaladin) GetPaladin() *paladin.Paladin { - return ret.Paladin -} - -func (ret *RetributionPaladin) Initialize() { - ret.Paladin.Initialize() - ret.RegisterAvengingWrathCD() -} - -func (ret *RetributionPaladin) Reset(sim *core.Simulation) { - ret.Paladin.Reset(sim) - - switch ret.Seal { - case proto.PaladinSeal_Vengeance: - ret.CurrentSeal = ret.SealOfVengeanceAura - ret.SealOfVengeanceAura.Activate(sim) - case proto.PaladinSeal_Command: - ret.CurrentSeal = ret.SealOfCommandAura - ret.SealOfCommandAura.Activate(sim) - case proto.PaladinSeal_Righteousness: - ret.CurrentSeal = ret.SealOfRighteousnessAura - ret.SealOfRighteousnessAura.Activate(sim) - } -} diff --git a/sim/_paladin/retribution/retribution_test.go b/sim/_paladin/retribution/retribution_test.go deleted file mode 100644 index 89a0de28d..000000000 --- a/sim/_paladin/retribution/retribution_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package retribution - -import ( - "testing" - - _ "github.com/wowsims/classic/sim/common" // imported to get item effects included. - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" -) - -func init() { - RegisterRetributionPaladin() -} - -func TestRetribution(t *testing.T) { - core.RunTestSuite(t, t.Name(), core.FullCharacterTestSuiteGenerator(core.CharacterSuiteConfig{ - Class: proto.Class_ClassPaladin, - Race: proto.Race_RaceHuman, - OtherRaces: []proto.Race{proto.Race_RaceHuman, proto.Race_RaceDwarf}, - - GearSet: core.GetGearSet("../../../ui/retribution_paladin/gear_sets", "p1"), - Talents: StandardTalents, - Consumes: FullConsumes, - SpecOptions: core.SpecOptionsCombo{Label: "Retribution Paladin SOV", SpecOptions: DefaultOptions}, - OtherSpecOptions: []core.SpecOptionsCombo{ - { - Label: "Retribution Paladin SOC", - SpecOptions: &proto.Player_RetributionPaladin{ - RetributionPaladin: &proto.RetributionPaladin{ - Options: &proto.RetributionPaladin_Options{ - Judgement: proto.PaladinJudgement_JudgementOfWisdom, - Seal: proto.PaladinSeal_Command, - Aura: proto.PaladinAura_RetributionAura, - }, - }, - }, - }, - { - Label: "Retribution Paladin SOR", - SpecOptions: &proto.Player_RetributionPaladin{ - RetributionPaladin: &proto.RetributionPaladin{ - Options: &proto.RetributionPaladin_Options{ - Judgement: proto.PaladinJudgement_JudgementOfWisdom, - Seal: proto.PaladinSeal_Righteousness, - Aura: proto.PaladinAura_RetributionAura, - }, - }, - }, - }, - { - Label: "Retribution Paladin SOV 2 Target Swapping", - SpecOptions: &proto.Player_RetributionPaladin{ - RetributionPaladin: &proto.RetributionPaladin{ - Options: &proto.RetributionPaladin_Options{ - Judgement: proto.PaladinJudgement_JudgementOfWisdom, - Seal: proto.PaladinSeal_Vengeance, - Aura: proto.PaladinAura_RetributionAura, - }, - }, - }, - }, - }, - Rotation: core.GetAplRotation("../../../ui/retribution_paladin/apls", "default"), - - ItemFilter: core.ItemFilter{ - WeaponTypes: []proto.WeaponType{ - proto.WeaponType_WeaponTypeAxe, - proto.WeaponType_WeaponTypeSword, - proto.WeaponType_WeaponTypePolearm, - proto.WeaponType_WeaponTypeMace, - }, - ArmorType: proto.ArmorType_ArmorTypePlate, - RangedWeaponTypes: []proto.RangedWeaponType{ - proto.RangedWeaponType_RangedWeaponTypeLibram, - }, - }, - })) -} - -var StandardTalents = "050501-05-05232051203331302133231331" - -var DefaultOptions = &proto.Player_RetributionPaladin{ - RetributionPaladin: &proto.RetributionPaladin{ - Options: &proto.RetributionPaladin_Options{ - Judgement: proto.PaladinJudgement_JudgementOfWisdom, - Seal: proto.PaladinSeal_Vengeance, - Aura: proto.PaladinAura_RetributionAura, - }, - }, -} - -var FullConsumes = &proto.Consumes{ - Flask: proto.Flask_FlaskOfRelentlessAssault, - DefaultPotion: proto.Potions_HastePotion, - DefaultConjured: proto.Conjured_ConjuredDarkRune, - Food: proto.Food_FoodRoastedClefthoof, - ThermalSapper: true, -} diff --git a/sim/_paladin/righteous_fury.go b/sim/_paladin/righteous_fury.go deleted file mode 100644 index fc890c729..000000000 --- a/sim/_paladin/righteous_fury.go +++ /dev/null @@ -1,37 +0,0 @@ -package paladin - -import ( - "github.com/wowsims/classic/sim/core" -) - -func (paladin *Paladin) ActivateRighteousFury() { - - var holySpells []*core.Spell - paladin.OnSpellRegistered(func(spell *core.Spell) { - if spell.SpellSchool == core.SpellSchoolHoly { - holySpells = append(holySpells, spell) - } - }) - - dtmMul := 1 - 0.02*float64(paladin.Talents.ImprovedRighteousFury) - - paladin.RighteousFuryAura = paladin.RegisterAura(core.Aura{ - Label: "Righteous Fury", - ActionID: core.ActionID{SpellID: 25780}, - Duration: core.NeverExpires, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - paladin.PseudoStats.ThreatMultiplier *= 1.43 - paladin.PseudoStats.DamageTakenMultiplier *= dtmMul - for _, spell := range holySpells { - spell.ThreatMultiplier *= 1.8 - } - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - paladin.PseudoStats.ThreatMultiplier /= 1.43 - paladin.PseudoStats.DamageTakenMultiplier /= dtmMul - for _, spell := range holySpells { - spell.ThreatMultiplier /= 1.8 - } - }, - }) -} diff --git a/sim/_paladin/shield_of_righteousness.go b/sim/_paladin/shield_of_righteousness.go deleted file mode 100644 index 03f01d045..000000000 --- a/sim/_paladin/shield_of_righteousness.go +++ /dev/null @@ -1,59 +0,0 @@ -package paladin - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/stats" -) - -func (paladin *Paladin) registerShieldOfRighteousnessSpell() { - var aegisPlateProcAura *core.Aura - if paladin.HasSetBonus(ItemSetAegisPlate, 4) { - aegisPlateProcAura = paladin.NewTemporaryStatsAura("Aegis", core.ActionID{SpellID: 64883}, stats.Stats{stats.BlockValue: 225}, time.Second*6) - } - - paladin.ShieldOfRighteousness = paladin.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 61411}, - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskMeleeMHSpecial, - Flags: core.SpellFlagMeleeMetrics | core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.06, - Multiplier: 1 - 0.02*float64(paladin.Talents.Benediction), - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - IgnoreHaste: true, - CD: core.Cooldown{ - Timer: paladin.NewTimer(), - Duration: time.Second * 6, - }, - }, - - DamageMultiplier: 1, - CritMultiplier: paladin.MeleeCritMultiplier(), - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - if aegisPlateProcAura != nil { - aegisPlateProcAura.Activate(sim) - } - - var baseDamage float64 - // TODO: Derive or find accurate source for DR curve - bv := paladin.BlockValue() - if bv <= 2400.0 { - baseDamage = 520.0 + bv - } else { - bv = 2400.0 + (bv-2400.0)/2 - baseDamage = 520.0 + core.TernaryFloat64(bv > 2760.0, 2760.0, bv) - } - - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) - }, - }) -} diff --git a/sim/_paladin/soc.go b/sim/_paladin/soc.go deleted file mode 100644 index f1ea98406..000000000 --- a/sim/_paladin/soc.go +++ /dev/null @@ -1,173 +0,0 @@ -package paladin - -import ( - "github.com/wowsims/classic/sim/core" -) - -func (paladin *Paladin) registerSealOfCommandSpellAndAura() { - /* - * Seal of Command is an Spell/Aura that when active makes the paladin capable of procing - * 2 different SpellIDs depending on a paladin's casted spell or melee swing. - * - * SpellID 20467 (Judgement of Command): - * - Procs off of any "Primary" Judgement (JoL, JoW, JoJ). - * - Cannot miss or be dodged/parried. - * - Deals hybrid AP/SP damage. - * - Crits off of a melee modifier. - * - * SpellID 20424 (Seal of Command): - * - Procs off of any melee special ability, or white hit. - * - If the ability is SINGLE TARGET, it hits up to 2 extra targets. - * - Deals hybrid AP/SP damage * current weapon speed. - * - Crits off of a melee modifier. - * - CAN MISS, BE DODGED/PARRIED/BLOCKED. - */ - - numHits := min(3, paladin.Env.GetNumTargets()) // primary target + 2 others - results := make([]*core.SpellResult, numHits) - - onJudgementProc := paladin.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 20467}, // Judgement of Command - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskMeleeSpecial, - Flags: core.SpellFlagMeleeMetrics | SpellFlagSecondaryJudgement, - - BonusCritRating: (6 * float64(paladin.Talents.Fanaticism) * core.CritRatingPerCritChance) + - (core.TernaryFloat64(paladin.HasSetBonus(ItemSetTuralyonsBattlegear, 4), 5, 0) * core.CritRatingPerCritChance), - - DamageMultiplier: 1 * - (1 + paladin.getItemSetLightswornBattlegearBonus4() + - paladin.getTalentTheArtOfWarBonus()) * - (1 + paladin.getTalentTwoHandedWeaponSpecializationBonus()), - CritMultiplier: paladin.MeleeCritMultiplier(), - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - mhWeaponDamage := 0 + - spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) + - spell.BonusWeaponDamage() - baseDamage := 0.19*mhWeaponDamage + - 0.08*spell.MeleeAttackPower() + - 0.13*spell.SpellDamage() - - // Secondary Judgements cannot miss if the Primary Judgement hit, only roll for crit. - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialCritOnly) - }, - }) - - onSpecialOrSwingActionID := core.ActionID{SpellID: 20424} - onSpecialOrSwingProcCleave := paladin.RegisterSpell(core.SpellConfig{ - ActionID: onSpecialOrSwingActionID, - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagMeleeMetrics, - - DamageMultiplier: 1 * - (1 + paladin.getItemSetLightswornBattlegearBonus4()) * - (1 + paladin.getTalentTwoHandedWeaponSpecializationBonus()) * - 0.36, // Only 36% of weapon damage. - CritMultiplier: paladin.MeleeCritMultiplier(), - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - curTarget := target - for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { - baseDamage := 0 + - spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) + - spell.BonusWeaponDamage() - - results[hitIndex] = spell.CalcDamage(sim, curTarget, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) - curTarget = sim.Environment.NextTargetUnit(curTarget) - } - - curTarget = target - for hitIndex := int32(0); hitIndex < numHits; hitIndex++ { - spell.DealDamage(sim, results[hitIndex]) - curTarget = sim.Environment.NextTargetUnit(curTarget) - } - }, - }) - - onSpecialOrSwingProc := paladin.RegisterSpell(core.SpellConfig{ - ActionID: onSpecialOrSwingActionID, - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskEmpty, // unlike SoV, SoC crits don't proc Vengeance - Flags: core.SpellFlagMeleeMetrics, - - DamageMultiplier: 1 * - (1 + paladin.getItemSetLightswornBattlegearBonus4()) * - (1 + paladin.getTalentTwoHandedWeaponSpecializationBonus()) * - 0.36, // Only 36% of weapon damage. - CritMultiplier: paladin.MeleeCritMultiplier(), - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := 0 + - spell.Unit.MHWeaponDamage(sim, spell.MeleeAttackPower()) + - spell.BonusWeaponDamage() - - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialHitAndCrit) - }, - }) - - // Seal of Command aura. - auraActionID := core.ActionID{SpellID: 20375} - paladin.SealOfCommandAura = paladin.RegisterAura(core.Aura{ - Label: "Seal of Command", - Tag: "Seal", - ActionID: auraActionID, - Duration: SealDuration, - - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - // Don't proc on misses or our own procs. - if !result.Landed() || spell == onJudgementProc || spell.SameAction(onSpecialOrSwingActionID) { - return - } - - // Differ between judgements and other melee abilities. - if spell.Flags.Matches(SpellFlagPrimaryJudgement) { - onJudgementProc.Cast(sim, result.Target) - if paladin.Talents.JudgementsOfTheJust > 0 { - // Special JoJ talent behavior, procs swing seal on judgements - // For SoC this is a cleave. - onSpecialOrSwingProcCleave.Cast(sim, result.Target) - } - } else if spell.IsMelee() { - // Temporary check to avoid AOE double procing. - if spell.SpellID == paladin.HammerOfTheRighteous.SpellID || spell.SpellID == paladin.DivineStorm.SpellID { - onSpecialOrSwingProc.Cast(sim, result.Target) - } else { - onSpecialOrSwingProcCleave.Cast(sim, result.Target) - } - } - }, - }) - - aura := paladin.SealOfCommandAura - paladin.SealOfCommand = paladin.RegisterSpell(core.SpellConfig{ - ActionID: auraActionID, // Seal of Command self buff. - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.14, - Multiplier: 1 - 0.02*float64(paladin.Talents.Benediction), - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { - if paladin.CurrentSeal != nil { - paladin.CurrentSeal.Deactivate(sim) - } - paladin.CurrentSeal = aura - paladin.CurrentSeal.Activate(sim) - }, - }) -} diff --git a/sim/_paladin/sor.go b/sim/_paladin/sor.go deleted file mode 100644 index a98683848..000000000 --- a/sim/_paladin/sor.go +++ /dev/null @@ -1,128 +0,0 @@ -package paladin - -import ( - "github.com/wowsims/classic/sim/core" -) - -func (paladin *Paladin) registerSealOfRighteousnessSpellAndAura() { - /* - * Seal of Righteousness is an Spell/Aura that when active makes the paladin capable of procing - * 2 different SpellIDs depending on a paladin's casted spell or melee swing. - * NOTE: - * Seal of Righteousness is unique in that it is the only seal that can proc off its own judgements. - * - * SpellID 20187 (Judgement of Righteousness): - * - Procs off of any "Primary" Judgement (JoL, JoW, JoJ). - * - Cannot miss or be dodged/parried. - * - Deals hybrid AP/SP damage. - * - Crits off of a melee modifier. - * - * SpellID 20154 (Seal of Righteousness): - * - Procs off of any melee special ability, or white hit. - * - Cannot miss or be dodged/parried. - * - Deals hybrid AP/SP damage * current weapon speed. - * - CANNOT CRIT. - */ - - onJudgementProc := paladin.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 20187}, // Judgement of Righteousness. - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskMeleeSpecial, - Flags: core.SpellFlagMeleeMetrics | SpellFlagSecondaryJudgement, - - BonusCritRating: (6 * float64(paladin.Talents.Fanaticism) * core.CritRatingPerCritChance) + - (core.TernaryFloat64(paladin.HasSetBonus(ItemSetTuralyonsBattlegear, 4), 5, 0) * core.CritRatingPerCritChance), - - DamageMultiplier: 1 * - (1 + paladin.getItemSetLightswornBattlegearBonus4() + paladin.getTalentSealsOfThePureBonus() + - paladin.getTalentTheArtOfWarBonus()) * - (1 + paladin.getTalentTwoHandedWeaponSpecializationBonus()), - CritMultiplier: paladin.MeleeCritMultiplier(), - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - // i = 1 + 0.2 * AP + 0.32 * HolP - baseDamage := 1 + - .20*spell.MeleeAttackPower() + - .32*spell.SpellDamage() - - // Secondary Judgements cannot miss if the Primary Judgement hit, only roll for crit. - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialCritOnly) - }, - }) - - onSpecialOrSwingProc := paladin.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 20154}, // Seal of Righteousness damage bonus. - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagMeleeMetrics, - - DamageMultiplier: 1 * - (1 + paladin.getItemSetLightswornBattlegearBonus4() + paladin.getItemSetAegisPlateBonus2() + paladin.getTalentSealsOfThePureBonus()) * - (1 + paladin.getTalentTwoHandedWeaponSpecializationBonus()), - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - // weapon_speed * (0.022* AP + 0.044*HolP) - baseDamage := paladin.GetMHWeapon().SwingSpeed * (.022*spell.MeleeAttackPower() + .044*spell.SpellDamage()) - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeAlwaysHit) - }, - }) - - // Seal of Righteousness aura. - auraActionID := core.ActionID{SpellID: 21084} - paladin.SealOfRighteousnessAura = paladin.RegisterAura(core.Aura{ - Label: "Seal of Righteousness", - Tag: "Seal", - ActionID: auraActionID, - Duration: SealDuration, - - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - // Don't proc on misses or our own procs. - if !result.Landed() || spell.SpellID == onJudgementProc.SpellID || spell.SpellID == onSpecialOrSwingProc.SpellID { - return - } - - // Differ between judgements and other melee abilities. - if spell.Flags.Matches(SpellFlagPrimaryJudgement) { - // SoR is the only seal that can proc off its own judgement. - onSpecialOrSwingProc.Cast(sim, result.Target) - onJudgementProc.Cast(sim, result.Target) - if paladin.Talents.JudgementsOfTheJust > 0 { - // Special JoJ talent behavior, procs swing seal on judgements - // Yes, for SoR this means it proces TWICE on one judgement. - onSpecialOrSwingProc.Cast(sim, result.Target) - } - } else { - if spell.IsMelee() { - onSpecialOrSwingProc.Cast(sim, result.Target) - } - } - }, - }) - - aura := paladin.SealOfRighteousnessAura - paladin.SealOfRighteousness = paladin.RegisterSpell(core.SpellConfig{ - ActionID: auraActionID, // Seal of Righteousness self buff. - SpellSchool: core.SpellSchoolHoly, - Flags: core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.14, - Multiplier: 1 - 0.02*float64(paladin.Talents.Benediction), - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { - if paladin.CurrentSeal != nil { - paladin.CurrentSeal.Deactivate(sim) - } - paladin.CurrentSeal = aura - paladin.CurrentSeal.Activate(sim) - }, - }) -} diff --git a/sim/_paladin/sov.go b/sim/_paladin/sov.go deleted file mode 100644 index fba641b1b..000000000 --- a/sim/_paladin/sov.go +++ /dev/null @@ -1,224 +0,0 @@ -package paladin - -import ( - "time" - - "github.com/wowsims/classic/sim/core" -) - -func (paladin *Paladin) registerSealOfVengeanceSpellAndAura() { - /* - * Seal of Vengeance is an Spell/Aura that when active makes the paladin capable of procing - * 3 different SpellIDs depending on a paladin's casted spell or melee swing. - * - * SpellID 31803 (Holy Vengeance): - * - "Hidden" proc that does a second melee roll on white hit to apply a DoT of - * the same SpellID. - * - Since this is a second roll, it can miss or be dodged/parried. - * - Does no damage on its own, only the DoT does damage, DoT scales based on AP/SP. - * - The DoT applied by this modifies all other procs. - * - Cannot crit by default. - * - * SpellID 31804 (Judgement of Vengeance): - * - Procs off of any "Primary" Judgement (JoL, JoW, JoJ). - * - Cannot miss or be dodged/parried. - * - Deals hybrid AP/SP damage, increased by 10% per stack of Holy Vengeance. - * - Crits off of a melee modifier. - * - * SpellID 42463 (Seal of Vengeance): - * - Procs off of any melee special ability, or white hit. - * - Cannot miss or be dodged/parried. - * - Deals % weapon damage, only after reaching 1 stack, increased by ~7% per stack of Holy Vengeance for a total of ~33%. - * - Crits off of a melee modifier. - * - * TODO: - * - Add set bonus and talent related modifiers. - */ - // TODO: Test whether T8 Prot 2pc also affects Judgement, once available - // TODO: Verify whether these bonuses should indeed be additive with similar - - dotSpell := paladin.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 31803, Tag: 2}, - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagMeleeMetrics, - - DamageMultiplier: 1 * - (1 + paladin.getItemSetLightswornBattlegearBonus4() + paladin.getItemSetAegisPlateBonus2() + paladin.getTalentSealsOfThePureBonus()), - ThreatMultiplier: 1, - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Holy Vengeance (DoT)", - MaxStacks: 5, - }, - NumberOfTicks: 5, - TickLength: time.Second * 3, // ticking every three seconds for a grand total of 15s of duration - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - tickValue := 0 + - .013*dot.Spell.SpellDamage() + - .025*dot.Spell.MeleeAttackPower() - dot.SnapshotBaseDamage = tickValue * float64(dot.GetStacks()) - - dot.SnapshotAttackerMultiplier = dot.Spell.AttackerDamageMultiplier(dot.Spell.Unit.AttackTables[target.UnitIndex][dot.Spell.CastType]) - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.Spell.OutcomeAlwaysHit) - }, - }, - }) - paladin.SovDotSpell = dotSpell - - dotApplicationSpell := paladin.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 31803, Tag: 1}, - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskProc, - - DamageMultiplier: 1 * - (1 + paladin.getItemSetLightswornBattlegearBonus4() + paladin.getItemSetAegisPlateBonus2() + paladin.getTalentSealsOfThePureBonus()), - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - // Does no damage, just applies dot and rolls. - result := spell.CalcAndDealOutcome(sim, target, spell.OutcomeMeleeSpecialHit) - - if result.Landed() { - dot := dotSpell.Dot(target) - if !dot.IsActive() { - dot.Apply(sim) - } - dot.AddStack(sim) - dot.TakeSnapshot(sim, false) - dot.Activate(sim) - } - }, - }) - - onJudgementProc := paladin.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 31804}, // Judgement of Vengeance. - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskMeleeSpecial, - Flags: core.SpellFlagMeleeMetrics | SpellFlagSecondaryJudgement, - - BonusCritRating: (6 * float64(paladin.Talents.Fanaticism) * core.CritRatingPerCritChance) + - (core.TernaryFloat64(paladin.HasSetBonus(ItemSetTuralyonsBattlegear, 4), 5, 0) * core.CritRatingPerCritChance), - DamageMultiplier: 1 * - (1 + paladin.getItemSetLightswornBattlegearBonus4() + - paladin.getTalentSealsOfThePureBonus() + paladin.getTalentTheArtOfWarBonus()) * - (1 + paladin.getTalentTwoHandedWeaponSpecializationBonus()), - CritMultiplier: paladin.MeleeCritMultiplier(), - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - // i = 1 + 0.22 * HolP + 0.14 * AP - baseDamage := 1 + - .22*spell.SpellDamage() + - .14*spell.MeleeAttackPower() - - // i = i * (1 + (0.10 * stacks)) - baseDamage *= 1 + .1*float64(dotSpell.Dot(target).GetStacks()) - - // Secondary Judgements cannot miss if the Primary Judgement hit, only roll for crit. - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialCritOnly) - }, - }) - - onSpecialOrSwingProc := paladin.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 42463}, // Seal of Vengeance damage bonus. - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskProc, // does proc certain spell damage-based items, e.g. Black Magic, Pendulum of Telluric Currents - Flags: core.SpellFlagMeleeMetrics, - - // (mult * weaponScaling / stacks) - DamageMultiplier: 1 * - (1 + paladin.getItemSetLightswornBattlegearBonus4() + paladin.getItemSetAegisPlateBonus2() + paladin.getTalentSealsOfThePureBonus()) * - (1 + paladin.getTalentTwoHandedWeaponSpecializationBonus()) * .33 / 5, - CritMultiplier: paladin.MeleeCritMultiplier(), - ThreatMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := paladin.MHWeaponDamage(sim, spell.MeleeAttackPower()) * - float64(dotSpell.Dot(target).GetStacks()) - - // can't miss if melee swing landed, but can crit - result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeSpecialCritOnly) - paladin.maybeProcVengeance(sim, result) - }, - }) - - // Seal of Vengeance aura. - auraActionID := core.ActionID{SpellID: 31801} - paladin.SealOfVengeanceAura = paladin.RegisterAura(core.Aura{ - Label: "Seal of Vengeance", - Tag: "Seal", - ActionID: auraActionID, - Duration: SealDuration, - - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - // Don't proc on misses or our own procs. - if !result.Landed() || spell == dotSpell || spell == onJudgementProc || spell == onSpecialOrSwingProc { - return - } - - // Differ between judgements and other melee abilities. - if spell.Flags.Matches(SpellFlagPrimaryJudgement) { - onJudgementProc.Cast(sim, result.Target) - if paladin.Talents.JudgementsOfTheJust > 0 { - // Special JoJ talent behavior, procs swing seal on judgements - if dotSpell.Dot(result.Target).GetStacks() > 0 { - onSpecialOrSwingProc.Cast(sim, result.Target) - } - } - } else { - if spell.IsMelee() { - if dotSpell.Dot(result.Target).GetStacks() > 0 { - onSpecialOrSwingProc.Cast(sim, result.Target) - } - } - } - - dotApplicableSpells := []*core.Spell{ - paladin.HammerOfTheRighteous, - paladin.CrusaderStrike, - paladin.DivineStorm, - paladin.HammerOfWrath, - paladin.ShieldOfRighteousness, - } - isApplicableSpell := false - for _, validSpell := range dotApplicableSpells { - if spell == validSpell { - isApplicableSpell = true - } - } - - if spell.ProcMask.Matches(core.ProcMaskMeleeWhiteHit) || isApplicableSpell { - dotApplicationSpell.Cast(sim, result.Target) - } - }, - }) - - aura := paladin.SealOfVengeanceAura - paladin.SealOfVengeance = paladin.RegisterSpell(core.SpellConfig{ - ActionID: auraActionID, // Seal of Vengeance self buff. - SpellSchool: core.SpellSchoolHoly, - Flags: core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.14, - Multiplier: 1 - 0.02*float64(paladin.Talents.Benediction), - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - }, - }, - - ApplyEffects: func(sim *core.Simulation, _ *core.Unit, spell *core.Spell) { - if paladin.CurrentSeal != nil { - paladin.CurrentSeal.Deactivate(sim) - } - paladin.CurrentSeal = aura - paladin.CurrentSeal.Activate(sim) - }, - }) -} diff --git a/sim/_paladin/spiritual_attunement.go b/sim/_paladin/spiritual_attunement.go deleted file mode 100644 index 148abed5a..000000000 --- a/sim/_paladin/spiritual_attunement.go +++ /dev/null @@ -1,36 +0,0 @@ -package paladin - -import ( - "github.com/wowsims/classic/sim/core" -) - -func (paladin *Paladin) registerSpiritualAttunement() { - if paladin.Talents.SpiritualAttunement == 0 { - return - } - - // No longer baseline in WotLK, affected by talent points. Ignoring the old set bonus here. - SpiritualAttunementScalar := 0.05 * float64(paladin.Talents.SpiritualAttunement) - - 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/talents.go b/sim/_paladin/talents.go deleted file mode 100644 index fd270acee..000000000 --- a/sim/_paladin/talents.go +++ /dev/null @@ -1,652 +0,0 @@ -package paladin - -import ( - "time" - - "github.com/wowsims/classic/sim/core" - "github.com/wowsims/classic/sim/core/proto" - "github.com/wowsims/classic/sim/core/stats" -) - -// TODO: -// Sanctified Wrath (Damage penetration, questions over affected stats) - -func (paladin *Paladin) ToughnessArmorMultiplier() float64 { - return 1.0 + 0.02*float64(paladin.Talents.Toughness) -} - -func (paladin *Paladin) ApplyTalents() { - paladin.AddStat(stats.MeleeCrit, float64(paladin.Talents.Conviction)*core.CritRatingPerCritChance) - paladin.AddStat(stats.SpellCrit, float64(paladin.Talents.Conviction)*core.CritRatingPerCritChance) - paladin.AddStat(stats.MeleeCrit, float64(paladin.Talents.SanctityOfBattle)*core.CritRatingPerCritChance) - paladin.AddStat(stats.SpellCrit, float64(paladin.Talents.SanctityOfBattle)*core.CritRatingPerCritChance) - - paladin.AddStat(stats.Parry, 1*float64(paladin.Talents.Deflection)) - paladin.AddStat(stats.Dodge, 1*float64(paladin.Talents.Anticipation)) - - paladin.ApplyEquipScaling(stats.Armor, paladin.ToughnessArmorMultiplier()) - - if paladin.Talents.DivineStrength > 0 { - paladin.MultiplyStat(stats.Strength, 1.0+0.03*float64(paladin.Talents.DivineStrength)) - } - if paladin.Talents.DivineIntellect > 0 { - paladin.MultiplyStat(stats.Intellect, 1.0+0.02*float64(paladin.Talents.DivineIntellect)) - } - - if paladin.Talents.SheathOfLight > 0 { - // doesn't implement HOT - percentage := 0.10 * float64(paladin.Talents.SheathOfLight) - paladin.AddStatDependency(stats.AttackPower, stats.SpellPower, percentage) - } - - if paladin.Talents.TouchedByTheLight > 0 { - percentage := 0.20 * float64(paladin.Talents.TouchedByTheLight) - paladin.AddStatDependency(stats.Strength, stats.SpellPower, percentage) - } - - if paladin.Talents.SacredDuty > 0 { - paladin.MultiplyStat(stats.Stamina, 1.0+0.02*float64(paladin.Talents.SacredDuty)) - } - - if paladin.Talents.CombatExpertise > 0 { - // Expertise is replaced by weapon skill in SoD - // paladin.AddStat(stats.Expertise, core.ExpertisePerQuarterPercentReduction*2*float64(paladin.Talents.CombatExpertise)) - paladin.AddStat(stats.MeleeCrit, core.CritRatingPerCritChance*2*float64(paladin.Talents.CombatExpertise)) - paladin.AddStat(stats.SpellCrit, core.CritRatingPerCritChance*2*float64(paladin.Talents.CombatExpertise)) - paladin.MultiplyStat(stats.Stamina, 1.0+0.02*float64(paladin.Talents.CombatExpertise)) - } - - if paladin.Talents.ShieldOfTheTemplar > 0 { - paladin.PseudoStats.DamageTakenMultiplier *= 1 - 0.01*float64(paladin.Talents.ShieldOfTheTemplar) - } - - paladin.applyRedoubt() - paladin.applyReckoning() - paladin.applyArdentDefender() - paladin.applyCrusade() - paladin.applyWeaponSpecialization() - paladin.applyVengeance() - paladin.applyHeartOfTheCrusader() - paladin.applyVindication() - paladin.applyArtOfWar() - paladin.applyJudgementsOfTheJust() - paladin.applyJudgementsOfTheWise() - paladin.applyRighteousVengeance() - paladin.applyGuardedByTheLight() -} - -func (paladin *Paladin) getTalentSealsOfThePureBonus() float64 { - return 0.03 * float64(paladin.Talents.SealsOfThePure) -} - -func (paladin *Paladin) getTalentTwoHandedWeaponSpecializationBonus() float64 { - return 0.02 * float64(paladin.Talents.TwoHandedWeaponSpecialization) -} - -func (paladin *Paladin) getTalentSanctityOfBattleBonus() float64 { - return 0.05 * float64(paladin.Talents.SanctityOfBattle) -} - -func (paladin *Paladin) getTalentTheArtOfWarBonus() float64 { - return 0.05 * float64(paladin.Talents.TheArtOfWar) -} - -func (paladin *Paladin) applyRedoubt() { - if paladin.Talents.Redoubt == 0 { - return - } - - actionID := core.ActionID{SpellID: 20132} - - paladin.PseudoStats.BlockValueMultiplier += 0.10 * float64(paladin.Talents.Redoubt) - - bonusBlockRating := 10 * core.BlockRatingPerBlockChance * float64(paladin.Talents.Redoubt) - - procAura := paladin.RegisterAura(core.Aura{ - Label: "Redoubt Proc", - ActionID: actionID, - Duration: time.Second * 10, - MaxStacks: 5, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - paladin.AddStatDynamic(sim, stats.Block, bonusBlockRating) - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - paladin.AddStatDynamic(sim, stats.Block, -bonusBlockRating) - }, - OnSpellHitTaken: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if result.DidBlock() { - aura.RemoveStack(sim) - } - }, - }) - - paladin.RegisterAura(core.Aura{ - Label: "Redoubt", - 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) { - if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMeleeOrRanged) { - if sim.RandomFloat("Redoubt") < 0.1 { - procAura.Activate(sim) - procAura.SetStacks(sim, 5) - } - } - }, - }) -} - -func (paladin *Paladin) applyReckoning() { - if paladin.Talents.Reckoning == 0 { - return - } - - actionID := core.ActionID{SpellID: 20182} - procChance := 0.02 * float64(paladin.Talents.Reckoning) - - var reckoningSpell *core.Spell - - procAura := paladin.RegisterAura(core.Aura{ - Label: "Reckoning Proc", - ActionID: actionID, - Duration: time.Second * 8, - MaxStacks: 4, - OnInit: func(aura *core.Aura, sim *core.Simulation) { - config := *paladin.AutoAttacks.MHConfig() - config.ActionID = actionID - reckoningSpell = paladin.GetOrRegisterSpell(config) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell == paladin.AutoAttacks.MHAuto() { - reckoningSpell.Cast(sim, result.Target) - } - }, - }) - - paladin.RegisterAura(core.Aura{ - Label: "Reckoning", - 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) { - if result.Landed() && sim.RandomFloat("Reckoning") < procChance { - procAura.Activate(sim) - procAura.SetStacks(sim, 4) - } - }, - }) -} - -func (paladin *Paladin) applyArdentDefender() { - if paladin.Talents.ArdentDefender == 0 { - return - } - - var ardentDamageReduction float64 - switch paladin.Talents.ArdentDefender { - case 1: - ardentDamageReduction = 0.07 - case 2: - ardentDamageReduction = 0.13 - case 3: - ardentDamageReduction = 0.20 - } - - // 540 defense (+140) yields the full heal amount - ardentHealAmount := max(1.0, float64(paladin.GetStat(stats.Defense))/core.DefenseRatingPerDefense/140.0) * 0.10 * float64(paladin.Talents.ArdentDefender) - - // TBD? Buff to mark time spent fully below 35% and attribute absorbs - // rangeAura := paladin.RegisterAura(core.Aura{ - // Label: "Ardent Defender (Active)", - // ActionID: core.ActionID{SpellID: 31852}, - // Duration: core.NeverExpires, - // }) - - // paladin.RegisterAura(core.Aura{ - // Label: "Ardent Defender Talent", - // 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) { - // if aura.Unit.CurrentHealthPercent() < 0.35 { - // procAura.Activate(sim) - // } - // }, - // }) - - // Debuff to show that AD has procced - procAura := paladin.RegisterAura(core.Aura{ - Label: "Ardent Defender", - ActionID: core.ActionID{SpellID: 66233}, - Duration: time.Second * 120.0, - }) - - // Spell to heal you when AD has procced; fire this before fatal damage so that a Death is not detected - procHeal := paladin.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 66233}, - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskSpellHealing, - - CritMultiplier: 1, // Assuming this can't really crit? - - ThreatMultiplier: 0.25, - DamageMultiplier: 1, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - spell.CalcAndDealHealing(sim, &paladin.Unit, ardentHealAmount*paladin.MaxHealth(), spell.OutcomeHealingCrit) - }, - }) - - // >= 0.35, no effect - // < 0.35, pro-rated DR - // =< 0, proc death save - paladin.AddDynamicDamageTakenModifier(func(sim *core.Simulation, _ *core.Spell, result *core.SpellResult) { - incomingDamage := result.Damage - if (paladin.CurrentHealth()-incomingDamage)/paladin.MaxHealth() <= 0.35 { - //rangeAura.Activate(sim) - result.Damage -= (paladin.MaxHealth()*0.35 - (paladin.CurrentHealth() - incomingDamage)) * ardentDamageReduction - if sim.Log != nil { - paladin.Log(sim, "Ardent Defender reduced damage by %d", int32(incomingDamage-result.Damage)) - } - - incomingDamage2 := result.Damage - - // Now check death save, based on the reduced damage - if (result.Damage >= paladin.CurrentHealth()) && !procAura.IsActive() { - if paladin.CurrentHealth()+ardentHealAmount*paladin.MaxHealth() > paladin.MaxHealth() { - // We will overheal and wind up at the wrong HP value... Let's work around this - // TODO: Find a cleaner way to do this, using absorbs? - procHeal.Cast(sim, &paladin.Unit) - result.Damage = paladin.CurrentHealth() - ardentHealAmount*paladin.MaxHealth() - if sim.Log != nil { - paladin.Log(sim, "Ardent Defender proc reduced overkill damage by %d, compensating for overheal", int32(incomingDamage2-result.Damage)) - } - } else { - // Cleanest handling for < 70% HP, includes proper healing amount in metrics - result.Damage = paladin.CurrentHealth() - procHeal.Cast(sim, &paladin.Unit) - if sim.Log != nil { - paladin.Log(sim, "Ardent Defender proc reduced overkill damage by %d", int32(incomingDamage2-result.Damage)) - } - } - procAura.Activate(sim) - } - } - // TODO: Metrics, attribute reduced damage as absorption - }) -} - -// Because Crusade modifies unit specific attack tables, it must be applied at start of sim. -func (paladin *Paladin) applyCrusade() { - if paladin.Talents.Crusade == 0 { - return - } - - var applied bool - - paladin.RegisterResetEffect( - func(s *core.Simulation) { - if !applied { - for i := int32(0); i < paladin.Env.GetNumTargets(); i++ { - unit := paladin.Env.GetTargetUnit(i) - crusadeMod := 1.0 + (0.01 * float64(paladin.Talents.Crusade)) - switch unit.MobType { - case proto.MobType_MobTypeHumanoid, proto.MobType_MobTypeDemon, proto.MobType_MobTypeUndead, proto.MobType_MobTypeElemental: - paladin.AttackTables[unit.UnitIndex][proto.CastType_CastTypeMainHand].DamageDealtMultiplier *= crusadeMod * crusadeMod - default: - paladin.AttackTables[unit.UnitIndex][proto.CastType_CastTypeMainHand].DamageDealtMultiplier *= crusadeMod - } - } - applied = true - } - }, - ) -} - -// Prior to WOTLK, behavior was to double dip. -func (paladin *Paladin) MeleeCritMultiplier() float64 { - // return paladin.Character.MeleeCritMultiplier(paladin.crusadeMultiplier(), 0) - return paladin.DefaultMeleeCritMultiplier() -} -func (paladin *Paladin) SpellCritMultiplier() float64 { - // return paladin.Character.SpellCritMultiplier(paladin.crusadeMultiplier(), 0) - return paladin.DefaultSpellCritMultiplier() -} - -// Affects all physical damage or spells that can be rolled as physical -// It affects white, Windfury, Crusader Strike, Seals, and Judgement of Command / Blood -func (paladin *Paladin) applyWeaponSpecialization() { - // This impacts Crusader Strike, Melee Attacks, WF attacks - // Seals + Judgements need to be implemented separately - mhWeapon := paladin.GetMHWeapon() - - if mhWeapon == nil { - return - } - - switch mhWeapon.HandType { - case proto.HandType_HandTypeTwoHand: - paladin.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] *= 1 + 0.02*float64(paladin.Talents.TwoHandedWeaponSpecialization) - case proto.HandType_HandTypeOneHand, proto.HandType_HandTypeMainHand: - if paladin.Talents.OneHandedWeaponSpecialization > 0 { - // Talent points are 4%, 7%, 10% - paladin.PseudoStats.DamageDealtMultiplier *= 1.01 + 0.03*float64(paladin.Talents.OneHandedWeaponSpecialization) - } - } -} - -func (paladin *Paladin) maybeProcVengeance(sim *core.Simulation, result *core.SpellResult) { - if result.DidCrit() && paladin.Talents.Vengeance > 0 { - paladin.VengeanceAura.Activate(sim) - paladin.VengeanceAura.AddStack(sim) - } -} - -// I don't know if the new stack of vengeance applies to the crit that triggered it or not -// Need to check this -func (paladin *Paladin) applyVengeance() { - if paladin.Talents.Vengeance == 0 { - return - } - - bonusPerStack := 0.01 * float64(paladin.Talents.Vengeance) - paladin.VengeanceAura = paladin.RegisterAura(core.Aura{ - Label: "Vengeance Proc", - ActionID: core.ActionID{SpellID: 20057}, - Duration: time.Second * 30, - MaxStacks: 3, - OnStacksChange: func(aura *core.Aura, sim *core.Simulation, oldStacks int32, newStacks int32) { - aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexHoly] /= 1 + (bonusPerStack * float64(oldStacks)) - aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] /= 1 + (bonusPerStack * float64(oldStacks)) - - aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexHoly] *= 1 + (bonusPerStack * float64(newStacks)) - aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexPhysical] *= 1 + (bonusPerStack * float64(newStacks)) - }, - }) - - paladin.RegisterAura(core.Aura{ - Label: "Vengeance", - 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) { - paladin.maybeProcVengeance(sim, result) - }, - }) -} - -func (paladin *Paladin) applyHeartOfTheCrusader() { - if paladin.Talents.HeartOfTheCrusader == 0 { - return - } - - hotcAuras := paladin.NewEnemyAuraArray(func(target *core.Unit) *core.Aura { - return core.HeartOfTheCrusaderDebuff(target, paladin.Talents.HeartOfTheCrusader) - }) - - paladin.RegisterAura(core.Aura{ - Label: "Heart of the Crusader", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.Flags.Matches(SpellFlagSecondaryJudgement) { - hotcAuras.Get(result.Target).Activate(sim) - } - }, - }) -} - -func (paladin *Paladin) applyVindication() { - if paladin.Talents.Vindication == 0 { - return - } - - vindicationAuras := paladin.NewEnemyAuraArray(func(target *core.Unit) *core.Aura { - return core.VindicationAura(target, paladin.Talents.Vindication) - }) - paladin.RegisterAura(core.Aura{ - Label: "Vindication Talent", - 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) { - // TODO: Replace with actual proc mask / proc chance - if result.Landed() && spell.ProcMask.Matches(core.ProcMaskMelee) { - vindicationAuras.Get(result.Target).Activate(sim) - } - }, - }) -} - -func (paladin *Paladin) applyArtOfWar() { - if paladin.Talents.TheArtOfWar == 0 { - return - } - - castTimeReduction := 0.5 * float64(paladin.Talents.TheArtOfWar) - paladin.ArtOfWarInstantCast = paladin.RegisterAura(core.Aura{ - Label: "Art Of War", - ActionID: core.ActionID{SpellID: 53488}, - Duration: time.Second * 15, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - paladin.Exorcism.CastTimeMultiplier -= castTimeReduction - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - paladin.Exorcism.CastTimeMultiplier += castTimeReduction - }, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - if spell == paladin.Exorcism { - aura.Deactivate(sim) - } - }, - }) - - paladin.RegisterAura(core.Aura{ - Label: "The Art of War", - 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.IsMelee() && !spell.Flags.Matches(SpellFlagSecondaryJudgement) { - return - } - - if spell == paladin.HammerOfWrath { - return - } - - if !result.Outcome.Matches(core.OutcomeCrit) { - return - } - - paladin.ArtOfWarInstantCast.Activate(sim) - }, - }) -} - -func (paladin *Paladin) applyJudgementsOfTheJust() { - if paladin.Talents.JudgementsOfTheJust == 0 { - return - } - - jojAuras := paladin.NewEnemyAuraArray(func(target *core.Unit) *core.Aura { - return core.JudgementsOfTheJustAura(target, paladin.Talents.JudgementsOfTheJust) - }) - // This application can proc stuff - jojApplicationSpell := paladin.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 68055}, - ProcMask: core.ProcMaskProc, - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - jojAuras.Get(target).Activate(sim) - }, - }) - - paladin.RegisterAura(core.Aura{ - Label: "Judgements Of The Just Talent", - 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() && spell.Flags.Matches(SpellFlagPrimaryJudgement) { - jojApplicationSpell.Cast(sim, result.Target) - } - }, - }) -} - -func (paladin *Paladin) applyJudgementsOfTheWise() { - if paladin.Talents.JudgementsOfTheWise == 0 { - return - } - - procChance := float64(paladin.Talents.JudgementsOfTheWise) / 3 - paladin.JowiseManaMetrics = paladin.NewManaMetrics(core.ActionID{SpellID: 31878}) - - procSpell := paladin.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 31878}, - ApplyEffects: func(sim *core.Simulation, unit *core.Unit, _ *core.Spell) { - paladin.AddMana(sim, paladin.BaseMana*0.25, paladin.JowiseManaMetrics) - }, - }) - - paladin.RegisterAura(core.Aura{ - Label: "Judgements of the Wise", - Duration: core.NeverExpires, - OnReset: func(aura *core.Aura, sim *core.Simulation) { - aura.Activate(sim) - }, - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if !spell.Flags.Matches(SpellFlagSecondaryJudgement) || !result.Landed() { - return - } - - if procChance == 1 || sim.RandomFloat("judgements of the wise") < procChance { - procSpell.Cast(sim, nil) - } - }, - }) -} - -func (paladin *Paladin) applyRighteousVengeance() { - // Righteous Vengeance is a MAGIC debuff that pools 10/20/30% crit damage from Crusader Strike, Divine Storm, and Judgements. - // It drains the pool every 2 seconds at a rate of 1/4 of the pool size. - // And then deals that 1/4 as PHYSICAL damage. - if paladin.Talents.RighteousVengeance == 0 { - return - } - - dotActionID := core.ActionID{SpellID: 61840} // Righteous Vengeance - - rvDot := paladin.RegisterSpell(core.SpellConfig{ - ActionID: dotActionID.WithTag(2), - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagMeleeMetrics | core.SpellFlagIgnoreModifiers, - - DamageMultiplier: 1, - CritMultiplier: paladin.MeleeCritMultiplier(), - ThreatMultiplier: 1, - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Righteous Vengeance", - }, - NumberOfTicks: 4, - TickLength: time.Second * 2, - - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - if paladin.HasTuralyonsOrLiadrinsBattlegear2Pc { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.Spell.OutcomeMeleeSpecialCritOnly) - } else { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.Spell.OutcomeAlwaysHit) - } - }, - }, - }) - - rvSpell := paladin.RegisterSpell(core.SpellConfig{ - ActionID: dotActionID.WithTag(1), - SpellSchool: core.SpellSchoolHoly, - ProcMask: core.ProcMaskProc, - Flags: core.SpellFlagNoOnCastComplete | core.SpellFlagIgnoreModifiers, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - rvDot.Dot(target).ApplyOrReset(sim) - spell.CalcAndDealOutcome(sim, target, spell.OutcomeAlwaysHit) - }, - }) - - paladin.RegisterAura(core.Aura{ - Label: "Righteous Vengeance", - 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() { - return - } - if spell != paladin.CrusaderStrike && spell != paladin.DivineStorm && !spell.Flags.Matches(SpellFlagSecondaryJudgement) { - return - } - - dot := rvDot.Dot(result.Target) - - newDamage := result.Damage * (0.10 * float64(paladin.Talents.RighteousVengeance)) - outstandingDamage := core.TernaryFloat64(dot.IsActive(), dot.SnapshotBaseDamage*float64(dot.NumberOfTicks-dot.TickCount), 0) - - dot.SnapshotAttackerMultiplier = 1 - dot.SnapshotBaseDamage = (outstandingDamage + newDamage) / float64(dot.NumberOfTicks) - rvSpell.Cast(sim, result.Target) - }, - }) -} - -//nolint:unused -func (paladin *Paladin) applyFanaticism() { - // TODO: Possibly implement as aura. - if paladin.Talents.Fanaticism == 0 { - return - } - - paladin.PseudoStats.ThreatMultiplier *= 1 - 0.10*float64(paladin.Talents.Fanaticism) -} - -func (paladin *Paladin) applyGuardedByTheLight() { - if paladin.Talents.GuardedByTheLight == 0 { - return - } - - paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexArcane] *= 1 - 0.03*float64(paladin.Talents.GuardedByTheLight) - paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFire] *= 1 - 0.03*float64(paladin.Talents.GuardedByTheLight) - paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexFrost] *= 1 - 0.03*float64(paladin.Talents.GuardedByTheLight) - paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexHoly] *= 1 - 0.03*float64(paladin.Talents.GuardedByTheLight) - paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexNature] *= 1 - 0.03*float64(paladin.Talents.GuardedByTheLight) - paladin.PseudoStats.SchoolDamageTakenMultiplier[stats.SchoolIndexShadow] *= 1 - 0.03*float64(paladin.Talents.GuardedByTheLight) - - paladin.RegisterAura(core.Aura{ - Label: "Guarded By The Light", - 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 paladin.DivinePleaAura.IsActive() { - paladin.DivinePleaAura.Refresh(sim) - } - }, - }) -}