From b6562fff12b454389f197070381e70a9c555e389 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sat, 29 Jun 2024 16:30:03 -0400 Subject: [PATCH 01/10] lots of warlock updates --- proto/common.proto | 8 +- sim/core/flags.go | 1 + sim/core/spell_result.go | 7 +- sim/core/target.go | 12 +- sim/warlock/_curses.go | 11 +- sim/warlock/_drain_soul.go | 11 +- sim/warlock/chaos_bolt.go | 16 +- sim/warlock/conflagrate.go | 16 +- sim/warlock/corruption.go | 9 +- sim/warlock/curses.go | 17 +- sim/warlock/dark_pact.go | 2 +- sim/warlock/death_coil.go | 6 +- sim/warlock/demonic_grace.go | 2 +- sim/warlock/dps/TestAffliction.results | 160 +++---- sim/warlock/dps/TestDemonology.results | 26 +- sim/warlock/drain_life.go | 8 +- sim/warlock/haunt.go | 16 +- sim/warlock/immolate.go | 38 +- sim/warlock/immolation_aura.go | 13 +- sim/warlock/incinerate.go | 16 +- sim/warlock/lifetap.go | 2 +- sim/warlock/metamorphosis.go | 2 +- sim/warlock/pet_abilities.go | 6 +- sim/warlock/rain_of_fire.go | 14 +- sim/warlock/runes.go | 34 +- sim/warlock/searing_pain.go | 22 +- sim/warlock/shadow_cleave.go | 23 +- sim/warlock/shadowbolt.go | 28 +- sim/warlock/shadowburn.go | 13 +- sim/warlock/shadowflame.go | 44 +- sim/warlock/siphon_life.go | 6 +- sim/warlock/soul_fire.go | 18 +- sim/warlock/talents.go | 459 ++++++++++++-------- sim/warlock/tank/TestDemonology.results | 26 +- sim/warlock/unstable_affliction.go | 22 +- sim/warlock/warlock.go | 13 +- ui/core/components/inputs/shaman_imbues.ts | 79 ++-- ui/core/components/inputs/warlock_inputs.ts | 102 +++++ ui/tank_warlock/inputs.ts | 70 +-- ui/tank_warlock/sim.ts | 59 +-- ui/warlock/inputs.ts | 82 +--- ui/warlock/sim.ts | 6 +- 42 files changed, 750 insertions(+), 775 deletions(-) create mode 100644 ui/core/components/inputs/warlock_inputs.ts diff --git a/proto/common.proto b/proto/common.proto index 7daab2c879..4253b45a2f 100644 --- a/proto/common.proto +++ b/proto/common.proto @@ -469,6 +469,10 @@ enum WeaponImbue { SolidWeightstone = 18; DenseWeightstone = 19; + // Spell Oils + ShadowOil = 21; + FrostOil = 22; + // Windfury Imbues WildStrikes = 7; Windfury = 8; @@ -479,10 +483,6 @@ enum WeaponImbue { FrostbrandWeapon = 11; WindfuryWeapon = 12; - // Spell Oils - ShadowOil = 21; - FrostOil = 22; - // Rogue imbues InstantPoison = 23; DeadlyPoison = 24; diff --git a/sim/core/flags.go b/sim/core/flags.go index 7f09e1ecf3..7bc3f2c189 100644 --- a/sim/core/flags.go +++ b/sim/core/flags.go @@ -168,6 +168,7 @@ const ( SpellFlagBinary // Does not do partial resists and could need a different hit roll. SpellFlagChanneled // Spell is channeled SpellFlagDisease // Spell is categorized as disease + SpellFlagHauntSE // Spell benefits from haunt/SE effects SpellFlagPoison // Spell is categorized as poison SpellFlagHelpful // For healing spells / buffs. SpellFlagMeleeMetrics // Marks a spell as a melee ability for metrics. diff --git a/sim/core/spell_result.go b/sim/core/spell_result.go index 1f90da6ab5..615abceaa5 100644 --- a/sim/core/spell_result.go +++ b/sim/core/spell_result.go @@ -555,6 +555,10 @@ func (spell *Spell) TargetDamageMultiplier(attackTable *AttackTable, isPeriodic multiplier *= attackTable.Defender.PseudoStats.PoisonDamageTakenMultiplier } + if spell.Flags.Matches(SpellFlagHauntSE) { + multiplier *= attackTable.HauntSEDamageTakenMultiplier + } + if isPeriodic && spell.SpellSchool.Matches(SpellSchoolPhysical) { multiplier *= attackTable.Defender.PseudoStats.BleedDamageTakenMultiplier } @@ -579,5 +583,6 @@ func (spell *Spell) applyTargetHealingModifiers(damage float64, attackTable *Att } return damage * - attackTable.Defender.PseudoStats.HealingTakenMultiplier + attackTable.Defender.PseudoStats.HealingTakenMultiplier * + attackTable.HealingDealtMultiplier } diff --git a/sim/core/target.go b/sim/core/target.go index 16c7200f1d..9c0b64abbf 100644 --- a/sim/core/target.go +++ b/sim/core/target.go @@ -265,8 +265,10 @@ type AttackTable struct { // Explicitly for hunters' "Monster Slaying" and "Humanoid Slaying", but likewise for rogues' "Murder", or trolls' "Beastslaying". CritMultiplier float64 - DamageDealtMultiplier float64 // attacker buff, applied in applyAttackerModifiers() - DamageTakenMultiplier float64 // defender debuff, applied in applyTargetModifiers() + DamageDealtMultiplier float64 // attacker buff, applied in applyAttackerModifiers() + DamageTakenMultiplier float64 // defender debuff, applied in applyTargetModifiers() + HauntSEDamageTakenMultiplier float64 // attacker buff for Haunt/SE-type effects. Applied in applyTargetModifiers() + HealingDealtMultiplier float64 // This is for "Apply Aura: Mod Damage Done By Caster" effects. // If set, the damage taken multiplier is multiplied by the callbacks result. @@ -282,8 +284,10 @@ func NewAttackTable(attacker *Unit, defender *Unit, weapon *Item) *AttackTable { CritMultiplier: 1, - DamageDealtMultiplier: 1, - DamageTakenMultiplier: 1, + DamageDealtMultiplier: 1, + DamageTakenMultiplier: 1, + HauntSEDamageTakenMultiplier: 1, + HealingDealtMultiplier: 1, } if defender.Type == EnemyUnit { diff --git a/sim/warlock/_curses.go b/sim/warlock/_curses.go index f086d33458..8335037c55 100644 --- a/sim/warlock/_curses.go +++ b/sim/warlock/_curses.go @@ -18,8 +18,7 @@ func (warlock *Warlock) registerCurseOfWeaknessSpell() { Flags: core.SpellFlagAPL, ManaCost: core.ManaCostOptions{ - BaseCost: 0.1, - Multiplier: 1 - 0.02*float64(warlock.Talents.Suppression), + BaseCost: 0.1, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ @@ -60,8 +59,7 @@ func (warlock *Warlock) registerCurseOfTonguesSpell() { Flags: core.SpellFlagAPL, ManaCost: core.ManaCostOptions{ - BaseCost: 0.04, - Multiplier: 1 - 0.02*float64(warlock.Talents.Suppression), + BaseCost: 0.04, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ @@ -91,8 +89,7 @@ func (warlock *Warlock) registerCurseOfDoomSpell() { Flags: core.SpellFlagAPL, ManaCost: core.ManaCostOptions{ - BaseCost: 0.15, - Multiplier: 1 - 0.02*float64(warlock.Talents.Suppression), + BaseCost: 0.15, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ @@ -104,8 +101,6 @@ func (warlock *Warlock) registerCurseOfDoomSpell() { }, }, - DamageMultiplierAdditive: 1 + - 0.03*float64(warlock.Talents.ShadowMastery), ThreatMultiplier: 1 - 0.1*float64(warlock.Talents.ImprovedDrainSoul), FlatThreatBonus: 160, diff --git a/sim/warlock/_drain_soul.go b/sim/warlock/_drain_soul.go index b9714bf1ce..81aff68b42 100644 --- a/sim/warlock/_drain_soul.go +++ b/sim/warlock/_drain_soul.go @@ -39,14 +39,14 @@ func (warlock *Warlock) registerDrainSoulSpell() { } warlock.DrainSoul = warlock.RegisterSpell(core.SpellConfig{ + SpellCode: SpellCode_WarlockDrainSoul, ActionID: core.ActionID{SpellID: 47855}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagChanneled | SpellFlagHaunt | core.SpellFlagAPL, + Flags: core.SpellFlagChanneled | core.SpellFlagHauntSE | core.SpellFlagAPL, ManaCost: core.ManaCostOptions{ - BaseCost: 0.14, - Multiplier: 1 - 0.02*float64(warlock.Talents.Suppression), + BaseCost: 0.14, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ @@ -55,9 +55,6 @@ func (warlock *Warlock) registerDrainSoulSpell() { }, }, - DamageMultiplierAdditive: 1 + - warlock.GrandSpellstoneBonus() + - 0.03*float64(warlock.Talents.ShadowMastery), DamageMultiplier: 1, ThreatMultiplier: 1 - 0.1*float64(warlock.Talents.ImprovedDrainSoul), @@ -118,8 +115,6 @@ func (warlock *Warlock) registerDrainSoulSpell() { dot := spell.Dot(target) dot.Apply(sim) dot.UpdateExpires(dot.ExpiresAt()) - - warlock.everlastingAfflictionRefresh(sim, target) } }, ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, useSnapshot bool) *core.SpellResult { diff --git a/sim/warlock/chaos_bolt.go b/sim/warlock/chaos_bolt.go index 8ea0788a7f..861c4409fb 100644 --- a/sim/warlock/chaos_bolt.go +++ b/sim/warlock/chaos_bolt.go @@ -22,11 +22,10 @@ func (warlock *Warlock) registerChaosBoltSpell() { SpellSchool: core.SpellSchoolFire, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing, + Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | WarlockFlagDestruction, ManaCost: core.ManaCostOptions{ - BaseCost: 0.07, - Multiplier: 1 - float64(warlock.Talents.Cataclysm)*0.01, + BaseCost: 0.07, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ @@ -39,14 +38,9 @@ func (warlock *Warlock) registerChaosBoltSpell() { }, }, - BonusCritRating: float64(warlock.Talents.Devastation) * core.SpellCritRatingPerCritChance, - - CritDamageBonus: warlock.ruin(), - - DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.Emberstorm), - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: spellCoeff, + DamageMultiplier: 1, + ThreatMultiplier: 1, + BonusCoefficient: spellCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := sim.Roll(baseLowDamage, baseHighDamage) diff --git a/sim/warlock/conflagrate.go b/sim/warlock/conflagrate.go index 57c79e73cf..5a7cb66965 100644 --- a/sim/warlock/conflagrate.go +++ b/sim/warlock/conflagrate.go @@ -23,13 +23,12 @@ func (warlock *Warlock) getConflagrateConfig(rank int) core.SpellConfig { SpellSchool: core.SpellSchoolFire, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL, + Flags: core.SpellFlagAPL | WarlockFlagDestruction, Rank: rank, RequiredLevel: level, ManaCost: core.ManaCostOptions{ - FlatCost: manaCost, - Multiplier: 1 - float64(warlock.Talents.Cataclysm)*0.01, + FlatCost: manaCost, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ @@ -44,14 +43,9 @@ func (warlock *Warlock) getConflagrateConfig(rank int) core.SpellConfig { return warlock.getActiveImmolateSpell(target) != nil || (warlock.ShadowflameDot != nil && warlock.ShadowflameDot.Dot(target).IsActive()) }, - BonusCritRating: float64(warlock.Talents.Devastation) * core.CritRatingPerCritChance, - - CritDamageBonus: warlock.ruin(), - - DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.Emberstorm), - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: spCoeff, + DamageMultiplier: 1, + ThreatMultiplier: 1, + BonusCoefficient: spCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := sim.Roll(baseDamageMin, baseDamageMax) diff --git a/sim/warlock/corruption.go b/sim/warlock/corruption.go index 708a0b89e5..5830536cb4 100644 --- a/sim/warlock/corruption.go +++ b/sim/warlock/corruption.go @@ -26,7 +26,7 @@ func (warlock *Warlock) getCorruptionConfig(rank int) core.SpellConfig { SpellCode: SpellCode_WarlockCorruption, ProcMask: core.ProcMaskSpellDamage, DefenseType: core.DefenseTypeMagic, - Flags: core.SpellFlagAPL | SpellFlagHaunt | core.SpellFlagResetAttackSwing | core.SpellFlagPureDot, + Flags: core.SpellFlagAPL | core.SpellFlagHauntSE | core.SpellFlagResetAttackSwing | core.SpellFlagPureDot | WarlockFlagAffliction, Rank: rank, RequiredLevel: level, @@ -40,13 +40,10 @@ func (warlock *Warlock) getCorruptionConfig(rank int) core.SpellConfig { }, }, - BonusHitRating: float64(warlock.Talents.Suppression) * 2 * core.SpellHitRatingPerHitChance, - CritDamageBonus: core.TernaryFloat64(hasPandemicRune, 1, 0), - DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.ShadowMastery), - DamageMultiplier: 1, - ThreatMultiplier: 1, + DamageMultiplier: 1, + ThreatMultiplier: 1, Dot: core.DotConfig{ Aura: core.Aura{ diff --git a/sim/warlock/curses.go b/sim/warlock/curses.go index 1c05ce80d5..89e3c07c84 100644 --- a/sim/warlock/curses.go +++ b/sim/warlock/curses.go @@ -18,7 +18,7 @@ func (warlock *Warlock) getCurseOfAgonyBaseConfig(rank int) core.SpellConfig { hasInvocationRune := warlock.HasRune(proto.WarlockRune_RuneBeltInvocation) hasPandemicRune := warlock.HasRune(proto.WarlockRune_RuneHelmPandemic) - baseDamage *= 1 + 0.02*float64(warlock.Talents.ImprovedCurseOfWeakness) + 0.02*float64(warlock.Talents.ShadowMastery) + baseDamage *= 1 + 0.02*float64(warlock.Talents.ImprovedCurseOfWeakness) + warlock.shadowMasteryBonus() snapshotBaseDmgNoBonus := 0.0 @@ -28,7 +28,7 @@ func (warlock *Warlock) getCurseOfAgonyBaseConfig(rank int) core.SpellConfig { ActionID: core.ActionID{SpellID: spellId}, SpellSchool: core.SpellSchoolShadow, DefenseType: core.DefenseTypeMagic, - Flags: core.SpellFlagAPL | SpellFlagHaunt | core.SpellFlagResetAttackSwing | core.SpellFlagPureDot, + Flags: core.SpellFlagAPL | core.SpellFlagHauntSE | core.SpellFlagResetAttackSwing | core.SpellFlagPureDot | WarlockFlagAffliction, ProcMask: core.ProcMaskSpellDamage, RequiredLevel: level, Rank: rank, @@ -42,8 +42,6 @@ func (warlock *Warlock) getCurseOfAgonyBaseConfig(rank int) core.SpellConfig { }, }, - BonusHitRating: 2 * float64(warlock.Talents.Suppression) * core.SpellHitRatingPerHitChance, - CritDamageBonus: core.TernaryFloat64(hasPandemicRune, 1, 0), DamageMultiplierAdditive: 1, @@ -151,7 +149,7 @@ func (warlock *Warlock) registerCurseOfRecklessnessSpell() { ActionID: core.ActionID{SpellID: spellID}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagAPL, + Flags: core.SpellFlagAPL | WarlockFlagAffliction, Rank: rank, ManaCost: core.ManaCostOptions{ @@ -163,7 +161,6 @@ func (warlock *Warlock) registerCurseOfRecklessnessSpell() { }, }, - BonusHitRating: float64(warlock.Talents.Suppression) * 2 * core.CritRatingPerCritChance, ThreatMultiplier: 1, FlatThreatBonus: 156, @@ -208,7 +205,7 @@ func (warlock *Warlock) registerCurseOfElementsSpell() { ActionID: core.ActionID{SpellID: spellID}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagAPL, + Flags: core.SpellFlagAPL | WarlockFlagAffliction, Rank: rank, ManaCost: core.ManaCostOptions{ @@ -220,7 +217,6 @@ func (warlock *Warlock) registerCurseOfElementsSpell() { }, }, - BonusHitRating: float64(warlock.Talents.Suppression) * 2 * core.CritRatingPerCritChance, ThreatMultiplier: 1, FlatThreatBonus: 156, @@ -262,7 +258,7 @@ func (warlock *Warlock) registerCurseOfShadowSpell() { ActionID: core.ActionID{SpellID: spellID}, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagAPL, + Flags: core.SpellFlagAPL | WarlockFlagAffliction, Rank: rank, ManaCost: core.ManaCostOptions{ @@ -274,7 +270,6 @@ func (warlock *Warlock) registerCurseOfShadowSpell() { }, }, - BonusHitRating: float64(warlock.Talents.Suppression) * 2 * core.CritRatingPerCritChance, ThreatMultiplier: 1, FlatThreatBonus: 156, @@ -305,7 +300,7 @@ func (warlock *Warlock) registerAmplifyCurseSpell() { warlock.AmplifyCurse = warlock.GetOrRegisterSpell(core.SpellConfig{ ActionID: actionID, SpellSchool: core.SpellSchoolShadow, - Flags: core.SpellFlagAPL, + Flags: core.SpellFlagAPL | WarlockFlagAffliction, Cast: core.CastConfig{ CD: core.Cooldown{ diff --git a/sim/warlock/dark_pact.go b/sim/warlock/dark_pact.go index 98c9a3abef..35d3cd4453 100644 --- a/sim/warlock/dark_pact.go +++ b/sim/warlock/dark_pact.go @@ -17,7 +17,7 @@ func (warlock *Warlock) getDarkPactConfig(rank int) core.SpellConfig { ActionID: actionID, SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagAPL, + Flags: core.SpellFlagAPL | WarlockFlagAffliction, Rank: rank, RequiredLevel: level, diff --git a/sim/warlock/death_coil.go b/sim/warlock/death_coil.go index 6d8b5f9189..1bb24f2bb3 100644 --- a/sim/warlock/death_coil.go +++ b/sim/warlock/death_coil.go @@ -13,14 +13,14 @@ func (warlock *Warlock) getDeathCoilBaseConfig(rank int) core.SpellConfig { level := [4]int{0, 42, 50, 58}[rank] spellCoeff := 0.214 - baseDamage *= 1 + 0.02*float64(warlock.Talents.ShadowMastery) + baseDamage *= 1 + warlock.shadowMasteryBonus() return core.SpellConfig{ ActionID: core.ActionID{SpellID: spellId}, SpellSchool: core.SpellSchoolShadow, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | core.SpellFlagBinary, + Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | core.SpellFlagBinary | WarlockFlagAffliction, RequiredLevel: level, Rank: rank, MissileSpeed: 24, @@ -38,8 +38,6 @@ func (warlock *Warlock) getDeathCoilBaseConfig(rank int) core.SpellConfig { }, }, - BonusHitRating: float64(warlock.Talents.Suppression) * 2 * core.SpellHitRatingPerHitChance, - DamageMultiplierAdditive: 1, DamageMultiplier: 1, ThreatMultiplier: 1, diff --git a/sim/warlock/demonic_grace.go b/sim/warlock/demonic_grace.go index 283a7e948b..94f5abd37f 100644 --- a/sim/warlock/demonic_grace.go +++ b/sim/warlock/demonic_grace.go @@ -41,7 +41,7 @@ func (warlock *Warlock) registerDemonicGraceSpell() { warlock.DemonicGrace = warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 425463}, - Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing, + Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | WarlockFlagDemonology, Cast: core.CastConfig{ CD: core.Cooldown{ diff --git a/sim/warlock/dps/TestAffliction.results b/sim/warlock/dps/TestAffliction.results index e937d7c43b..713c995f40 100644 --- a/sim/warlock/dps/TestAffliction.results +++ b/sim/warlock/dps/TestAffliction.results @@ -151,9 +151,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: -0.20648 + weights: -0.74339 weights: 0 - weights: 0.12816 + weights: 0.41648 weights: 0 weights: 0 weights: 0 @@ -161,8 +161,8 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 4.03621 - weights: 2.20586 + weights: 3.24996 + weights: 1.36107 weights: 0 weights: 0 weights: 0 @@ -200,9 +200,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: -4.37128 + weights: -4.338 weights: 0 - weights: -0.35183 + weights: -0.31801 weights: 0 weights: 0 weights: 0 @@ -210,8 +210,8 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 11.7238 - weights: 7.88288 + weights: 11.85581 + weights: 7.57835 weights: 0 weights: 0 weights: 0 @@ -249,9 +249,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 3.59883 + weights: -1.73762 weights: 0 - weights: 6.03618 + weights: -0.31541 weights: 0 weights: 0 weights: 0 @@ -259,8 +259,8 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 15.0114 - weights: 17.78207 + weights: 19.73578 + weights: 18.38375 weights: 0 weights: 0 weights: 0 @@ -295,238 +295,238 @@ stat_weights_results: { dps_results: { key: "TestAffliction-Lvl40-AllItems-DeathmistRaiment" value: { - dps: 173.364 - tps: 141.13433 + dps: 174.10272 + tps: 141.81072 } } dps_results: { key: "TestAffliction-Lvl40-Average-Default" value: { - dps: 575.49806 - tps: 554.16037 + dps: 564.79239 + tps: 543.97591 } } dps_results: { key: "TestAffliction-Lvl40-Settings-Orc-shadow-Affliction Warlock-affliction-FullBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 571.32651 - tps: 1185.79166 + dps: 556.8524 + tps: 1181.11206 } } dps_results: { key: "TestAffliction-Lvl40-Settings-Orc-shadow-Affliction Warlock-affliction-FullBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 571.32651 - tps: 549.81098 + dps: 556.8524 + tps: 535.59065 } } dps_results: { key: "TestAffliction-Lvl40-Settings-Orc-shadow-Affliction Warlock-affliction-FullBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 557.15713 - tps: 524.87614 + dps: 544.63529 + tps: 512.68446 } } dps_results: { key: "TestAffliction-Lvl40-Settings-Orc-shadow-Affliction Warlock-affliction-NoBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 370.66401 - tps: 1007.70457 + dps: 363.35476 + tps: 1007.28615 } } dps_results: { key: "TestAffliction-Lvl40-Settings-Orc-shadow-Affliction Warlock-affliction-NoBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 370.66401 - tps: 361.2251 + dps: 363.35476 + tps: 354.54914 } } dps_results: { key: "TestAffliction-Lvl40-Settings-Orc-shadow-Affliction Warlock-affliction-NoBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 371.98664 - tps: 347.52273 + dps: 364.58223 + tps: 340.33509 } } dps_results: { key: "TestAffliction-Lvl40-SwitchInFrontOfTarget-Default" value: { - dps: 576.6039 - tps: 554.77993 + dps: 564.88757 + tps: 544.42356 } } dps_results: { key: "TestAffliction-Lvl50-AllItems-DeathmistRaiment" value: { - dps: 320.42693 - tps: 216.4294 + dps: 320.02943 + tps: 216.03189 } } dps_results: { key: "TestAffliction-Lvl50-Average-Default" value: { - dps: 1414.10127 - tps: 1219.41286 + dps: 1406.25459 + tps: 1211.56618 } } dps_results: { key: "TestAffliction-Lvl50-Settings-Orc-nf.ruin-Destruction Warlock-nf.ruin-FullBuffs-Phase 3 Consumes-LongMultiTarget" value: { - dps: 2026.27626 - tps: 2802.25359 + dps: 2019.45049 + tps: 2795.42782 } } dps_results: { key: "TestAffliction-Lvl50-Settings-Orc-nf.ruin-Destruction Warlock-nf.ruin-FullBuffs-Phase 3 Consumes-LongSingleTarget" value: { - dps: 1392.50503 - tps: 1196.0849 + dps: 1386.477 + tps: 1190.05688 } } dps_results: { key: "TestAffliction-Lvl50-Settings-Orc-nf.ruin-Destruction Warlock-nf.ruin-FullBuffs-Phase 3 Consumes-ShortSingleTarget" value: { - dps: 1444.65417 - tps: 1237.5729 + dps: 1438.67932 + tps: 1231.59805 } } dps_results: { key: "TestAffliction-Lvl50-Settings-Orc-nf.ruin-Destruction Warlock-nf.ruin-NoBuffs-Phase 3 Consumes-LongMultiTarget" value: { - dps: 1317.61707 - tps: 2214.60337 + dps: 1315.20111 + tps: 2212.18742 } } dps_results: { key: "TestAffliction-Lvl50-Settings-Orc-nf.ruin-Destruction Warlock-nf.ruin-NoBuffs-Phase 3 Consumes-LongSingleTarget" value: { - dps: 848.31124 - tps: 720.17434 + dps: 846.9146 + tps: 718.77771 } } dps_results: { key: "TestAffliction-Lvl50-Settings-Orc-nf.ruin-Destruction Warlock-nf.ruin-NoBuffs-Phase 3 Consumes-ShortSingleTarget" value: { - dps: 835.77627 - tps: 722.01974 + dps: 833.85644 + tps: 720.09992 } } dps_results: { key: "TestAffliction-Lvl50-SwitchInFrontOfTarget-Default" value: { - dps: 1398.0175 - tps: 1202.43978 + dps: 1390.50635 + tps: 1194.92863 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-BloodGuard'sDreadweave" value: { - dps: 1299.0405 - tps: 1178.27818 + dps: 1288.27028 + tps: 1168.82942 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-DeathmistRaiment" value: { - dps: 1058.64357 - tps: 965.55038 + dps: 1042.74258 + tps: 949.49911 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-EmeraldEnchantedVestments" value: { - dps: 1321.86295 - tps: 1202.96589 + dps: 1273.47309 + tps: 1153.70915 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-InfernalPactEssence-216509" value: { - dps: 2237.57648 - tps: 2006.08013 + dps: 2163.42828 + tps: 1932.5405 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-Knight-Lieutenant'sDreadweave" value: { - dps: 1299.0405 - tps: 1178.27818 + dps: 1288.27028 + tps: 1168.82942 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-MalevolentProphet'sVestments" value: { - dps: 1737.55968 - tps: 1603.93538 + dps: 1668.2331 + tps: 1532.6869 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-NightmareProphet'sGarb" value: { - dps: 1715.59172 - tps: 1579.4036 + dps: 1649.7997 + tps: 1515.68843 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-ZilaGular-223214" value: { - dps: 2228.69223 - tps: 2000.99352 + dps: 2142.39438 + tps: 1916.71035 } } dps_results: { key: "TestAffliction-Lvl60-Average-Default" value: { - dps: 2296.15387 - tps: 2064.40629 + dps: 2204.99701 + tps: 1974.02229 } } dps_results: { key: "TestAffliction-Lvl60-Settings-Orc-affliction-Destruction Warlock-affliction-FullBuffs-Phase 4 Consumes-LongMultiTarget" value: { - dps: 2260.61221 - tps: 3093.64947 + dps: 2186.42048 + tps: 3037.73708 } } dps_results: { key: "TestAffliction-Lvl60-Settings-Orc-affliction-Destruction Warlock-affliction-FullBuffs-Phase 4 Consumes-LongSingleTarget" value: { - dps: 2260.61221 - tps: 2027.40018 + dps: 2186.42048 + tps: 1954.20575 } } dps_results: { key: "TestAffliction-Lvl60-Settings-Orc-affliction-Destruction Warlock-affliction-FullBuffs-Phase 4 Consumes-ShortSingleTarget" value: { - dps: 2246.19839 - tps: 2022.04564 + dps: 2149.30857 + tps: 1925.87209 } } dps_results: { key: "TestAffliction-Lvl60-Settings-Orc-affliction-Destruction Warlock-affliction-NoBuffs-Phase 4 Consumes-LongMultiTarget" value: { - dps: 1289.7472 - tps: 2299.77573 + dps: 1238.78445 + tps: 2263.83873 } } dps_results: { key: "TestAffliction-Lvl60-Settings-Orc-affliction-Destruction Warlock-affliction-NoBuffs-Phase 4 Consumes-LongSingleTarget" value: { - dps: 1289.7472 - tps: 1138.44508 + dps: 1238.78445 + tps: 1088.50571 } } dps_results: { key: "TestAffliction-Lvl60-Settings-Orc-affliction-Destruction Warlock-affliction-NoBuffs-Phase 4 Consumes-ShortSingleTarget" value: { - dps: 1268.37875 - tps: 1112.2606 + dps: 1215.18407 + tps: 1055.29129 } } dps_results: { key: "TestAffliction-Lvl60-SwitchInFrontOfTarget-Default" value: { - dps: 2274.60728 - tps: 2040.49658 + dps: 2198.50605 + tps: 1968.22763 } } diff --git a/sim/warlock/dps/TestDemonology.results b/sim/warlock/dps/TestDemonology.results index 2b282a744f..c8684c38ef 100644 --- a/sim/warlock/dps/TestDemonology.results +++ b/sim/warlock/dps/TestDemonology.results @@ -53,9 +53,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 1.05192 + weights: 1.03455 weights: 0 - weights: 1.26838 + weights: 1.26434 weights: 0 weights: 0 weights: 0 @@ -63,8 +63,8 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 7.12358 - weights: 1.81022 + weights: 7.18926 + weights: 1.8095 weights: 0 weights: 0 weights: 0 @@ -99,63 +99,63 @@ stat_weights_results: { dps_results: { key: "TestDemonology-Lvl40-AllItems-DeathmistRaiment" value: { - dps: 180.56632 + dps: 183.26102 tps: 61.69125 } } dps_results: { key: "TestDemonology-Lvl40-Average-Default" value: { - dps: 639.42262 + dps: 642.44832 tps: 503.73313 } } dps_results: { key: "TestDemonology-Lvl40-Settings-Orc-fire.succubus-Demonology Warlock-demonology-FullBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 642.6727 + dps: 645.72032 tps: 826.57738 } } dps_results: { key: "TestDemonology-Lvl40-Settings-Orc-fire.succubus-Demonology Warlock-demonology-FullBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 642.6727 + dps: 645.72032 tps: 508.64632 } } dps_results: { key: "TestDemonology-Lvl40-Settings-Orc-fire.succubus-Demonology Warlock-demonology-FullBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 684.19566 + dps: 687.5351 tps: 543.59299 } } dps_results: { key: "TestDemonology-Lvl40-Settings-Orc-fire.succubus-Demonology Warlock-demonology-NoBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 389.22637 + dps: 391.60663 tps: 676.11347 } } dps_results: { key: "TestDemonology-Lvl40-Settings-Orc-fire.succubus-Demonology Warlock-demonology-NoBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 389.22637 + dps: 391.60663 tps: 336.15736 } } dps_results: { key: "TestDemonology-Lvl40-Settings-Orc-fire.succubus-Demonology Warlock-demonology-NoBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 443.6328 + dps: 446.23256 tps: 373.24835 } } dps_results: { key: "TestDemonology-Lvl40-SwitchInFrontOfTarget-Default" value: { - dps: 644.27254 + dps: 647.33621 tps: 510.04287 } } diff --git a/sim/warlock/drain_life.go b/sim/warlock/drain_life.go index ed18960e96..1e551c59d2 100644 --- a/sim/warlock/drain_life.go +++ b/sim/warlock/drain_life.go @@ -24,7 +24,7 @@ func (warlock *Warlock) getDrainLifeBaseConfig(rank int) core.SpellConfig { manaCost *= 2 } - baseDamage *= 1 + 0.02*float64(warlock.Talents.ShadowMastery) + 0.02*float64(warlock.Talents.ImprovedDrainLife) + baseDamage *= 1 + warlock.shadowMasteryBonus() + 0.02*float64(warlock.Talents.ImprovedDrainLife) actionID := core.ActionID{SpellID: spellId} healthMetrics := warlock.NewHealthMetrics(actionID) @@ -34,7 +34,7 @@ func (warlock *Warlock) getDrainLifeBaseConfig(rank int) core.SpellConfig { SpellSchool: core.SpellSchoolShadow, SpellCode: SpellCode_WarlockDrainLife, ProcMask: core.ProcMaskSpellDamage, - Flags: SpellFlagHaunt | core.SpellFlagAPL | core.SpellFlagResetAttackSwing, + Flags: core.SpellFlagHauntSE | core.SpellFlagAPL | core.SpellFlagResetAttackSwing | WarlockFlagAffliction, RequiredLevel: level, Rank: rank, @@ -48,8 +48,6 @@ func (warlock *Warlock) getDrainLifeBaseConfig(rank int) core.SpellConfig { }, }, - BonusHitRating: float64(warlock.Talents.Suppression) * 2 * core.SpellHitRatingPerHitChance, - DamageMultiplierAdditive: 1, DamageMultiplier: 1, ThreatMultiplier: 1, @@ -126,8 +124,6 @@ func (warlock *Warlock) getDrainLifeBaseConfig(rank int) core.SpellConfig { dot := spell.Dot(target) dot.Apply(sim) - - warlock.EverlastingAfflictionRefresh(sim, target) } }, ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, useSnapshot bool) *core.SpellResult { diff --git a/sim/warlock/haunt.go b/sim/warlock/haunt.go index 289855a3ac..5b1e3c0001 100644 --- a/sim/warlock/haunt.go +++ b/sim/warlock/haunt.go @@ -7,10 +7,6 @@ import ( "github.com/wowsims/sod/sim/core/proto" ) -func hauntMultiplier(spell *core.Spell, _ *core.AttackTable) float64 { - return core.TernaryFloat64(spell.Flags.Matches(SpellFlagHaunt), 1.2, 1) -} - func (warlock *Warlock) registerHauntSpell() { if !warlock.HasRune(proto.WarlockRune_RuneHandsHaunt) { return @@ -28,15 +24,16 @@ func (warlock *Warlock) registerHauntSpell() { ActionID: actionID, Duration: time.Second * 12, OnGain: func(aura *core.Aura, sim *core.Simulation) { - warlock.AttackTables[aura.Unit.UnitIndex][proto.CastType_CastTypeMainHand].DamageDoneByCasterMultiplier = hauntMultiplier + warlock.AttackTables[aura.Unit.UnitIndex][proto.CastType_CastTypeMainHand].HauntSEDamageTakenMultiplier *= 1.2 }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { - warlock.AttackTables[aura.Unit.UnitIndex][proto.CastType_CastTypeMainHand].DamageDoneByCasterMultiplier = nil + warlock.AttackTables[aura.Unit.UnitIndex][proto.CastType_CastTypeMainHand].HauntSEDamageTakenMultiplier /= 1.2 }, }) }) warlock.Haunt = warlock.RegisterSpell(core.SpellConfig{ + SpellCode: SpellCode_WarlockHaunt, ActionID: actionID, SpellSchool: core.SpellSchoolShadow, DefenseType: core.DefenseTypeMagic, @@ -57,9 +54,9 @@ func (warlock *Warlock) registerHauntSpell() { }, }, - DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.ShadowMastery), - ThreatMultiplier: 1, - BonusCoefficient: spellCoeff, + DamageMultiplier: 1, + ThreatMultiplier: 1, + BonusCoefficient: spellCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := sim.Roll(baseLowDamage, baseHighDamage) @@ -68,7 +65,6 @@ func (warlock *Warlock) registerHauntSpell() { spell.DealDamage(sim, result) if result.Landed() { warlock.HauntDebuffAuras.Get(result.Target).Activate(sim) - warlock.EverlastingAfflictionRefresh(sim, target) } }) }, diff --git a/sim/warlock/immolate.go b/sim/warlock/immolate.go index 71bbbce72b..42e987d127 100644 --- a/sim/warlock/immolate.go +++ b/sim/warlock/immolate.go @@ -20,25 +20,26 @@ func (warlock *Warlock) getImmolateConfig(rank int) core.SpellConfig { hasInvocationRune := warlock.HasRune(proto.WarlockRune_RuneBeltInvocation) hasPandemicRune := warlock.HasRune(proto.WarlockRune_RuneHelmPandemic) hasUnstableAffliction := warlock.HasRune(proto.WarlockRune_RuneBracerUnstableAffliction) + hasShadowflameRune := warlock.HasRune(proto.WarlockRune_RuneBootsShadowflame) return core.SpellConfig{ - SpellCode: SpellCode_WarlockImmolate, - ActionID: core.ActionID{SpellID: spellId}, - SpellSchool: core.SpellSchoolFire, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | core.SpellFlagBinary, + SpellCode: SpellCode_WarlockImmolate, + ActionID: core.ActionID{SpellID: spellId}, + SpellSchool: core.SpellSchoolFire, + DefenseType: core.DefenseTypeMagic, + ProcMask: core.ProcMaskSpellDamage, + Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | core.SpellFlagBinary | WarlockFlagDestruction, + Rank: rank, RequiredLevel: level, ManaCost: core.ManaCostOptions{ - FlatCost: manaCost, - Multiplier: 1 - float64(warlock.Talents.Cataclysm)*0.01, + FlatCost: manaCost, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ GCD: core.GCDDefault, - CastTime: time.Millisecond * (2000 - 100*time.Duration(warlock.Talents.Bane)), + CastTime: time.Millisecond * 2000, }, ModifyCast: func(sim *core.Simulation, spell *core.Spell, cast *core.Cast) { cast.CastTime = spell.CastTime() @@ -52,14 +53,9 @@ func (warlock *Warlock) getImmolateConfig(rank int) core.SpellConfig { }, }, - BonusCritRating: float64(warlock.Talents.Devastation) * core.SpellCritRatingPerCritChance, - - CritDamageBonus: warlock.ruin(), - - DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.Emberstorm), - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: directCoeff, + DamageMultiplier: 1, + ThreatMultiplier: 1, + BonusCoefficient: directCoeff, Dot: core.DotConfig{ Aura: core.Aura{ @@ -91,17 +87,23 @@ func (warlock *Warlock) getImmolateConfig(rank int) core.SpellConfig { oldMultiplier := spell.DamageMultiplier // TODO should most likely just be done statically (?) // Would require splitting Immolate into 2 separate spells - spell.DamageMultiplier *= 1 + 0.05*float64(warlock.Talents.ImprovedImmolate) + spell.DamageMultiplier *= 1 + warlock.improvedImmolateBonus() result := spell.CalcDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) spell.DamageMultiplier = oldMultiplier if result.Landed() { + // UA, Immo, Shadowflame exclusivity if hasUnstableAffliction && warlock.UnstableAffliction.Dot(target).IsActive() { warlock.UnstableAffliction.Dot(target).Deactivate(sim) } + if hasShadowflameRune && warlock.Shadowflame.Dot(target).IsActive() { + warlock.Shadowflame.Dot(target).Deactivate(sim) + } + if hasInvocationRune && spell.Dot(target).IsActive() { warlock.InvocationRefresh(sim, spell.Dot(target)) } + spell.Dot(target).Apply(sim) } diff --git a/sim/warlock/immolation_aura.go b/sim/warlock/immolation_aura.go index 17f986cdab..cadff06d9c 100644 --- a/sim/warlock/immolation_aura.go +++ b/sim/warlock/immolation_aura.go @@ -22,14 +22,9 @@ func (warlock *Warlock) registerImmolationAuraSpell() { DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskEmpty, - BonusCritRating: float64(warlock.Talents.Devastation) * core.SpellCritRatingPerCritChance, - - CritDamageBonus: warlock.ruin(), - - DamageMultiplierAdditive: 1, // + 0.02*float64(warlock.Talents.Emberstorm), Not affected by any talent atm - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: spellCoeff, + DamageMultiplier: 1, + ThreatMultiplier: 1, + BonusCoefficient: spellCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { for _, aoeTarget := range sim.Encounter.TargetUnits { @@ -72,7 +67,7 @@ func (warlock *Warlock) registerImmolationAuraSpell() { warlock.ImmolationAura = warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: int32(proto.WarlockRune_RuneBracerImmolationAura)}, SpellSchool: core.SpellSchoolFire, - Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | core.SpellFlagNoOnCastComplete, + Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | core.SpellFlagNoOnCastComplete | WarlockFlagDestruction, Cast: core.CastConfig{ DefaultCast: core.Cast{ diff --git a/sim/warlock/incinerate.go b/sim/warlock/incinerate.go index bd3eb3c701..13d2a70368 100644 --- a/sim/warlock/incinerate.go +++ b/sim/warlock/incinerate.go @@ -34,12 +34,11 @@ func (warlock *Warlock) registerIncinerateSpell() { SpellSchool: core.SpellSchoolFire, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | core.SpellFlagBinary, + Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | core.SpellFlagBinary | WarlockFlagDestruction, MissileSpeed: 24, ManaCost: core.ManaCostOptions{ - BaseCost: 0.14, - Multiplier: 1 - float64(warlock.Talents.Cataclysm)*0.01, + BaseCost: 0.14, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ @@ -48,14 +47,9 @@ func (warlock *Warlock) registerIncinerateSpell() { }, }, - BonusCritRating: float64(warlock.Talents.Devastation) * core.SpellCritRatingPerCritChance, - - CritDamageBonus: warlock.ruin(), - - DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.Emberstorm), - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: spellCoeff, + DamageMultiplier: 1, + ThreatMultiplier: 1, + BonusCoefficient: spellCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { var baseDamage = sim.Roll(baseLowDamage, baseHighDamage) diff --git a/sim/warlock/lifetap.go b/sim/warlock/lifetap.go index 931622b1b2..07dad9ee24 100644 --- a/sim/warlock/lifetap.go +++ b/sim/warlock/lifetap.go @@ -22,7 +22,7 @@ func (warlock *Warlock) getLifeTapBaseConfig(rank int) core.SpellConfig { SpellSchool: core.SpellSchoolShadow, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing, + Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | WarlockFlagAffliction, RequiredLevel: level, Rank: rank, diff --git a/sim/warlock/metamorphosis.go b/sim/warlock/metamorphosis.go index b5e9f5c1d9..a1e957b534 100644 --- a/sim/warlock/metamorphosis.go +++ b/sim/warlock/metamorphosis.go @@ -34,7 +34,7 @@ func (warlock *Warlock) registerMetamorphosisSpell() { warlock.Metamorphosis = warlock.RegisterSpell(core.SpellConfig{ ActionID: actionID, - Flags: core.SpellFlagAPL, + Flags: core.SpellFlagAPL | WarlockFlagDemonology, Cast: core.CastConfig{ DefaultCast: core.Cast{ GCD: core.GCDDefault, diff --git a/sim/warlock/pet_abilities.go b/sim/warlock/pet_abilities.go index 1e40700435..787530ed17 100644 --- a/sim/warlock/pet_abilities.go +++ b/sim/warlock/pet_abilities.go @@ -80,7 +80,7 @@ func (wp *WarlockPet) registerSuccubusLashOfPainSpell() { } spellCoeff := [7]float64{0, .429, .429, .429, .429, .429, .429}[rank] - baseDamage := [7]float64{0, 33, 44, 60, 73, 87, 99}[rank] + baseDamage := [7]float64{0, 33, 44, 60, 73, 87, 99}[rank] * (1 + .10*float64(wp.owner.Talents.ImprovedSayaad)) spellId := [7]int32{0, 7814, 7815, 7816, 11778, 11779, 11780}[rank] manaCost := [7]float64{0, 65, 80, 105, 125, 145, 160}[rank] level := [7]int{0, 20, 28, 36, 44, 52, 60}[rank] @@ -230,9 +230,7 @@ func (wp *WarlockPet) registerFelguardDemonicFrenzyAura() { // }, // }, -// BonusCritDamage: wp.owner.ruin(), - -// DamageMultiplier: 1 + 0.03*float64(wp.owner.Talents.ShadowMastery), +// DamageMultiplier: 1 + warlock.shadowMasteryBonus(), // ThreatMultiplier: 1, // ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { diff --git a/sim/warlock/rain_of_fire.go b/sim/warlock/rain_of_fire.go index 8cb69cd79b..ce603adf98 100644 --- a/sim/warlock/rain_of_fire.go +++ b/sim/warlock/rain_of_fire.go @@ -17,7 +17,7 @@ func (warlock *Warlock) getRainOfFireBaseConfig(rank int) core.SpellConfig { manaCost := [5]float64{0, 295, 605, 885, 1185}[rank] level := [5]int{0, 20, 34, 46, 58}[rank] - flags := core.SpellFlagAPL | core.SpellFlagResetAttackSwing + flags := core.SpellFlagAPL | core.SpellFlagResetAttackSwing | WarlockFlagDestruction if !hasLakeOfFireRune { flags |= core.SpellFlagChanneled } @@ -31,17 +31,11 @@ func (warlock *Warlock) getRainOfFireBaseConfig(rank int) core.SpellConfig { RequiredLevel: level, Rank: rank, - BonusCritRating: float64(warlock.Talents.Devastation) * core.SpellCritRatingPerCritChance, - - CritDamageBonus: warlock.ruin(), - - DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.Emberstorm), - DamageMultiplier: 1, - ThreatMultiplier: 1, + DamageMultiplier: 1, + ThreatMultiplier: 1, ManaCost: core.ManaCostOptions{ - FlatCost: manaCost, - Multiplier: 1 - float64(warlock.Talents.Cataclysm)*0.01, + FlatCost: manaCost, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ diff --git a/sim/warlock/runes.go b/sim/warlock/runes.go index 2494a81106..dcd2ece40e 100644 --- a/sim/warlock/runes.go +++ b/sim/warlock/runes.go @@ -18,10 +18,18 @@ func (warlock *Warlock) ApplyRunes() { // Cloak Runes warlock.applyDecimation() + // Chest Runes warlock.applyDemonicTactics() - warlock.applyDemonicPact() + + // Belt Runes warlock.applyGrimoireOfSynergy() warlock.applyShadowAndFlame() + + // Pants Runes + warlock.applyEverlastingAffliction() + warlock.applyDemonicPact() + + // Boots Runes warlock.applyDemonicKnowledge() warlock.applyDanceOfTheWicked() } @@ -153,16 +161,28 @@ func (warlock *Warlock) InvocationRefresh(sim *core.Simulation, dot *core.Dot) { } } -func (warlock *Warlock) EverlastingAfflictionRefresh(sim *core.Simulation, target *core.Unit) { +func (warlock *Warlock) applyEverlastingAffliction() { if !warlock.HasRune(proto.WarlockRune_RuneLegsEverlastingAffliction) { return } - for _, spell := range warlock.Corruption { - if spell.Dot(target).IsActive() { - spell.Dot(target).Rollover(sim) - } - } + affectedSpellCodes := []int32{SpellCode_WarlockDrainLife, SpellCode_WarlockDrainSoul, SpellCode_WarlockShadowBolt, SpellCode_WarlockShadowCleave, SpellCode_WarlockSearingPain, SpellCode_WarlockIncinerate, SpellCode_WarlockHaunt} + warlock.RegisterAura(core.Aura{ + Label: "Everlasting Affliction Trigger", + 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() && slices.Contains(affectedSpellCodes, spell.SpellCode) { + for _, spell := range warlock.Corruption { + if spell.Dot(result.Target).IsActive() { + spell.Dot(result.Target).Rollover(sim) + } + } + } + }, + }) } func (warlock *Warlock) applyDanceOfTheWicked() { diff --git a/sim/warlock/searing_pain.go b/sim/warlock/searing_pain.go index 2467305113..c8318b38fc 100644 --- a/sim/warlock/searing_pain.go +++ b/sim/warlock/searing_pain.go @@ -15,17 +15,17 @@ func (warlock *Warlock) getSearingPainBaseConfig(rank int) core.SpellConfig { castTime := time.Millisecond * 1500 return core.SpellConfig{ + SpellCode: SpellCode_WarlockSearingPain, ActionID: core.ActionID{SpellID: spellId}, SpellSchool: core.SpellSchoolFire, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing, + Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | WarlockFlagDestruction, RequiredLevel: level, Rank: rank, ManaCost: core.ManaCostOptions{ - FlatCost: manaCost, - Multiplier: 1 - float64(warlock.Talents.Cataclysm)*0.01, + FlatCost: manaCost, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ @@ -40,23 +40,15 @@ func (warlock *Warlock) getSearingPainBaseConfig(rank int) core.SpellConfig { } }, }, - BonusCritRating: 0.0 + - float64(warlock.Talents.Devastation)*core.CritRatingPerCritChance + - 2.0*float64(warlock.Talents.ImprovedSearingPain)*core.CritRatingPerCritChance, + BonusCritRating: 2.0 * float64(warlock.Talents.ImprovedSearingPain) * core.CritRatingPerCritChance, - CritDamageBonus: warlock.ruin(), - - DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.Emberstorm), - DamageMultiplier: 1, - ThreatMultiplier: 2, - BonusCoefficient: spellCoeff, + DamageMultiplier: 1, + ThreatMultiplier: 2, + BonusCoefficient: spellCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { damage := sim.Roll(baseDamage[0], baseDamage[1]) spell.CalcAndDealDamage(sim, target, damage, spell.OutcomeMagicHitAndCrit) - - // TODO: does this happen on miss? - warlock.EverlastingAfflictionRefresh(sim, target) }, } } diff --git a/sim/warlock/shadow_cleave.go b/sim/warlock/shadow_cleave.go index 4a8da7ac62..5f87c9ca28 100644 --- a/sim/warlock/shadow_cleave.go +++ b/sim/warlock/shadow_cleave.go @@ -22,7 +22,7 @@ func (warlock *Warlock) getShadowCleaveBaseConfig(rank int) core.SpellConfig { SpellCode: SpellCode_WarlockShadowCleave, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing, + Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | WarlockFlagDestruction, RequiredLevel: level, Rank: rank, @@ -42,14 +42,9 @@ func (warlock *Warlock) getShadowCleaveBaseConfig(rank int) core.SpellConfig { return warlock.MetamorphosisAura.IsActive() }, - BonusCritRating: float64(warlock.Talents.Devastation) * core.SpellCritRatingPerCritChance, - - CritDamageBonus: warlock.ruin(), - - DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.ShadowMastery), - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: spellCoeff, + DamageMultiplier: 1, + ThreatMultiplier: 1, + BonusCoefficient: spellCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { for idx := range results { @@ -60,16 +55,6 @@ func (warlock *Warlock) getShadowCleaveBaseConfig(rank int) core.SpellConfig { for _, result := range results { spell.DealDamage(sim, result) - - if result.Landed() { - warlock.EverlastingAfflictionRefresh(sim, result.Target) - - if warlock.Talents.ImprovedShadowBolt > 0 && result.DidCrit() { - impShadowBoltAura := warlock.ImprovedShadowBoltAuras.Get(result.Target) - impShadowBoltAura.Activate(sim) - impShadowBoltAura.SetStacks(sim, 4) - } - } } }, } diff --git a/sim/warlock/shadowbolt.go b/sim/warlock/shadowbolt.go index 4784c31e15..b22299d273 100644 --- a/sim/warlock/shadowbolt.go +++ b/sim/warlock/shadowbolt.go @@ -26,32 +26,26 @@ func (warlock *Warlock) getShadowBoltBaseConfig(rank int) core.SpellConfig { SpellSchool: core.SpellSchoolShadow, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing, + Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | WarlockFlagDestruction, RequiredLevel: level, Rank: rank, ManaCost: core.ManaCostOptions{ - FlatCost: manaCost, - Multiplier: 1 - float64(warlock.Talents.Cataclysm)*0.01, + FlatCost: manaCost, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ GCD: core.GCDDefault, - CastTime: time.Millisecond * time.Duration(castTime-100*warlock.Talents.Bane), + CastTime: time.Millisecond * time.Duration(castTime), }, }, ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { return warlock.MetamorphosisAura == nil || !warlock.MetamorphosisAura.IsActive() }, - BonusCritRating: float64(warlock.Talents.Devastation) * core.SpellCritRatingPerCritChance, - - CritDamageBonus: warlock.ruin(), - - DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.ShadowMastery), - DamageMultiplier: damageMulti, - ThreatMultiplier: 1, - BonusCoefficient: spellCoeff, + DamageMultiplier: damageMulti, + ThreatMultiplier: 1, + BonusCoefficient: spellCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { for idx := range results { @@ -62,16 +56,6 @@ func (warlock *Warlock) getShadowBoltBaseConfig(rank int) core.SpellConfig { for _, result := range results { spell.DealDamage(sim, result) - - if result.Landed() { - warlock.EverlastingAfflictionRefresh(sim, result.Target) - - if warlock.Talents.ImprovedShadowBolt > 0 && result.DidCrit() { - impShadowBoltAura := warlock.ImprovedShadowBoltAuras.Get(result.Target) - impShadowBoltAura.Activate(sim) - impShadowBoltAura.SetStacks(sim, 4) - } - } } }, } diff --git a/sim/warlock/shadowburn.go b/sim/warlock/shadowburn.go index e2c8599342..986ca169c7 100644 --- a/sim/warlock/shadowburn.go +++ b/sim/warlock/shadowburn.go @@ -19,7 +19,7 @@ func (warlock *Warlock) registerShadowBurnBaseConfig(rank int) core.SpellConfig SpellSchool: core.SpellSchoolShadow, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | core.SpellFlagBinary, + Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | core.SpellFlagBinary | WarlockFlagDestruction, RequiredLevel: level, Rank: rank, @@ -36,14 +36,9 @@ func (warlock *Warlock) registerShadowBurnBaseConfig(rank int) core.SpellConfig }, }, - BonusCritRating: float64(warlock.Talents.Devastation) * core.SpellCritRatingPerCritChance, - - CritDamageBonus: warlock.ruin(), - - DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.ShadowMastery), - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: spellCoeff, + DamageMultiplier: 1, + ThreatMultiplier: 1, + BonusCoefficient: spellCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { baseDamage := sim.Roll(baseDamage[0], baseDamage[1]) diff --git a/sim/warlock/shadowflame.go b/sim/warlock/shadowflame.go index e1de1a2d42..2a46b895ad 100644 --- a/sim/warlock/shadowflame.go +++ b/sim/warlock/shadowflame.go @@ -12,25 +12,26 @@ func (warlock *Warlock) registerShadowflameSpell() { return } + hasInvocationRune := warlock.HasRune(proto.WarlockRune_RuneBeltInvocation) hasPandemicRune := warlock.HasRune(proto.WarlockRune_RuneHelmPandemic) + hasUnstableAffliction := warlock.HasRune(proto.WarlockRune_RuneBracerUnstableAffliction) + // https://www.wowhead.com/classic/spell=18275/shadow-mastery + // The Shadowflame periodic damage is NOT buffed by Shadow Mastery as it's not affected by haunt baseSpellCoeff := 0.20 dotSpellCoeff := 0.107 baseDamage := warlock.baseRuneAbilityDamage() * 2.26 dotDamage := warlock.baseRuneAbilityDamage() * 0.61 - // TODO: Probably merge this into the base spell now warlock.ShadowflameDot = warlock.RegisterSpell(core.SpellConfig{ ActionID: core.ActionID{SpellID: 426325}, SpellSchool: core.SpellSchoolFire | core.SpellSchoolShadow, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskEmpty, + Flags: WarlockFlagAffliction | WarlockFlagDestruction, - BonusCritRating: float64(warlock.Talents.Devastation) * core.SpellCritRatingPerCritChance, - - DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.ShadowMastery) + 0.02*float64(warlock.Talents.Emberstorm), - DamageMultiplier: 1, - ThreatMultiplier: 1, + DamageMultiplier: 1, + ThreatMultiplier: 1, Dot: core.DotConfig{ Aura: core.Aura{ @@ -70,16 +71,15 @@ func (warlock *Warlock) registerShadowflameSpell() { SpellSchool: core.SpellSchoolFire | core.SpellSchoolShadow, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing, + Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | WarlockFlagAffliction | WarlockFlagDestruction, ManaCost: core.ManaCostOptions{ - BaseCost: 0.27, - Multiplier: 1 - float64(warlock.Talents.Cataclysm)*0.01, + BaseCost: 0.27, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ GCD: core.GCDDefault, - CastTime: time.Millisecond * (2000 - 100*time.Duration(warlock.Talents.Bane)), + CastTime: time.Millisecond * 2000, }, CD: core.Cooldown{ Timer: warlock.NewTimer(), @@ -87,18 +87,26 @@ func (warlock *Warlock) registerShadowflameSpell() { }, }, - BonusCritRating: float64(warlock.Talents.Devastation) * core.SpellCritRatingPerCritChance, - - CritDamageBonus: warlock.ruin(), - - DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.ShadowMastery) + 0.02*float64(warlock.Talents.Emberstorm), - DamageMultiplier: 1 + 0.05*float64(warlock.Talents.ImprovedImmolate), - ThreatMultiplier: 1, - BonusCoefficient: baseSpellCoeff, + DamageMultiplier: 1 + warlock.improvedImmolateBonus(), + ThreatMultiplier: 1, + BonusCoefficient: baseSpellCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { result := spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicHitAndCrit) if result.Landed() { + // UA, Immo, Shadowflame exclusivity + if hasUnstableAffliction && warlock.UnstableAffliction.Dot(target).IsActive() { + warlock.UnstableAffliction.Dot(target).Deactivate(sim) + } + immoDot := warlock.getActiveImmolateSpell(target) + if immoDot != nil { + immoDot.Dot(target).Deactivate(sim) + } + + if hasInvocationRune && spell.Dot(target).IsActive() { + warlock.InvocationRefresh(sim, spell.Dot(target)) + } + warlock.ShadowflameDot.Cast(sim, target) } }, diff --git a/sim/warlock/siphon_life.go b/sim/warlock/siphon_life.go index 69dace3446..19c1ab8211 100644 --- a/sim/warlock/siphon_life.go +++ b/sim/warlock/siphon_life.go @@ -18,7 +18,7 @@ func (warlock *Warlock) getSiphonLifeBaseConfig(rank int) core.SpellConfig { actionID := core.ActionID{SpellID: spellId} healthMetrics := warlock.NewHealthMetrics(actionID) - baseDamage *= 1 + 0.02*float64(warlock.Talents.ShadowMastery) + baseDamage *= 1 + warlock.shadowMasteryBonus() hasInvocationRune := warlock.HasRune(proto.WarlockRune_RuneBeltInvocation) hasPandemicRune := warlock.HasRune(proto.WarlockRune_RuneHelmPandemic) @@ -28,7 +28,7 @@ func (warlock *Warlock) getSiphonLifeBaseConfig(rank int) core.SpellConfig { SpellSchool: core.SpellSchoolShadow, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskSpellDamage, - Flags: SpellFlagHaunt | core.SpellFlagAPL | core.SpellFlagResetAttackSwing | core.SpellFlagBinary, + Flags: core.SpellFlagHauntSE | core.SpellFlagAPL | core.SpellFlagResetAttackSwing | core.SpellFlagBinary | WarlockFlagAffliction, RequiredLevel: level, Rank: rank, @@ -41,8 +41,6 @@ func (warlock *Warlock) getSiphonLifeBaseConfig(rank int) core.SpellConfig { }, }, - BonusHitRating: float64(warlock.Talents.Suppression) * 2 * core.SpellHitRatingPerHitChance, - CritDamageBonus: core.TernaryFloat64(hasPandemicRune, 1, 0), DamageMultiplierAdditive: 1, diff --git a/sim/warlock/soul_fire.go b/sim/warlock/soul_fire.go index b1aa0d5270..b873c1ccf7 100644 --- a/sim/warlock/soul_fire.go +++ b/sim/warlock/soul_fire.go @@ -22,30 +22,24 @@ func (warlock *Warlock) getSoulFireBaseConfig(rank int) core.SpellConfig { SpellSchool: core.SpellSchoolFire, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing, + Flags: core.SpellFlagAPL | core.SpellFlagResetAttackSwing | WarlockFlagDestruction, RequiredLevel: level, Rank: rank, MissileSpeed: 24, ManaCost: core.ManaCostOptions{ - FlatCost: manaCost, - Multiplier: 1 - float64(warlock.Talents.Cataclysm)*0.01, + FlatCost: manaCost, }, Cast: core.CastConfig{ DefaultCast: core.Cast{ GCD: core.GCDDefault, - CastTime: time.Millisecond * (6000 - 400*time.Duration(warlock.Talents.Bane)), + CastTime: time.Millisecond * 6000, }, }, - BonusCritRating: float64(warlock.Talents.Devastation) * core.SpellCritRatingPerCritChance, - - CritDamageBonus: warlock.ruin(), - - DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.Emberstorm), - DamageMultiplier: 1, - ThreatMultiplier: 1, - BonusCoefficient: spellCoeff, + DamageMultiplier: 1, + ThreatMultiplier: 1, + BonusCoefficient: spellCoeff, ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { damage := sim.Roll(baseDamage[0], baseDamage[1]) diff --git a/sim/warlock/talents.go b/sim/warlock/talents.go index 06294f3961..524798c339 100644 --- a/sim/warlock/talents.go +++ b/sim/warlock/talents.go @@ -9,25 +9,226 @@ import ( ) func (warlock *Warlock) ApplyTalents() { - // Demonic Embrace - if warlock.Talents.DemonicEmbrace > 0 { - warlock.MultiplyStat(stats.Stamina, 1+.03*(float64(warlock.Talents.DemonicEmbrace))) - warlock.MultiplyStat(stats.Spirit, 1-.01*(float64(warlock.Talents.DemonicEmbrace))) + warlock.applyWeaponImbue() + + // Affliction + warlock.applySuppression() + warlock.applyNightfall() + warlock.applyShadowMastery() + + // Demonology + warlock.applyDemonicEmbrace() + warlock.applyFelIntellect() + warlock.applyMasterDemonologist() + warlock.applyDemonicSacrifice() + warlock.applySoulLink() + + // Destruction + warlock.applyImprovedShadowBolt() + warlock.applyCataclysm() + warlock.applyBane() + warlock.applyDevastation() + warlock.applyRuin() + warlock.applyEmberstorm() +} + +func (warlock *Warlock) applyWeaponImbue() { + if warlock.GetCharacter().Equipment.OffHand().Type != proto.ItemType_ItemTypeUnknown { + return } - if warlock.Pet != nil && warlock.Talents.FelIntellect > 0 { - warlock.Pet.MultiplyStat(stats.Mana, 1+0.03*float64(warlock.Talents.FelIntellect)) + level := warlock.Level + if warlock.Options.WeaponImbue == proto.WarlockOptions_Firestone { + warlock.applyFirestone() + } + if warlock.Options.WeaponImbue == proto.WarlockOptions_Spellstone { + if level >= 55 { + warlock.AddStat(stats.SpellCrit, 1*core.SpellCritRatingPerCritChance) + } } +} + +func (warlock *Warlock) applyFirestone() { + level := warlock.Level + + damageMin := 0.0 + damageMax := 0.0 + + // TODO: Test for spell scaling + spellCoeff := 0.0 + spellId := int32(0) - if warlock.Talents.ImprovedShadowBolt > 0 { - warlock.applyImprovedShadowBolt() + // TODO: Test PPM + ppm := warlock.AutoAttacks.NewPPMManager(8, core.ProcMaskMelee) + + firestoneMulti := 1.0 + float64(warlock.Talents.ImprovedFirestone)*0.15 + + if level >= 56 { + warlock.AddStat(stats.FirePower, 21*firestoneMulti) + damageMin = 80.0 + damageMax = 120.0 + spellId = 17949 + } else if level >= 46 { + warlock.AddStat(stats.FirePower, 17*firestoneMulti) + damageMin = 60.0 + damageMax = 90.0 + spellId = 17947 + } else if level >= 36 { + warlock.AddStat(stats.FirePower, 14*firestoneMulti) + damageMin = 40.0 + damageMax = 60.0 + spellId = 17945 + } else if level >= 28 { + warlock.AddStat(stats.FirePower, 10*firestoneMulti) + damageMin = 25.0 + damageMax = 35.0 + spellId = 758 } - warlock.applyWeaponImbue() - warlock.applyNightfall() - warlock.applyMasterDemonologist() - warlock.applyDemonicSacrifice() - warlock.applySoulLink() + if level >= 28 && warlock.Consumes.MainHandImbue == proto.WeaponImbue_WeaponImbueUnknown { + fireProcSpell := warlock.GetOrRegisterSpell(core.SpellConfig{ + ActionID: core.ActionID{SpellID: spellId}, + SpellSchool: core.SpellSchoolFire, + DefenseType: core.DefenseTypeMagic, + ProcMask: core.ProcMaskEmpty, + + DamageMultiplier: firestoneMulti, + ThreatMultiplier: 1, + DamageMultiplierAdditive: 1, + BonusCoefficient: spellCoeff, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + baseDamage := sim.Roll(damageMin, damageMax) + + spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicCrit) + }, + }) + + core.MakePermanent(warlock.GetOrRegisterAura(core.Aura{ + Label: "Firestone Proc", + OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { + if !result.Landed() { + return + } + + if !spell.ProcMask.Matches(core.ProcMaskMelee) { + return + } + + if !ppm.Proc(sim, core.ProcMaskMelee, "Firestone Proc") { + return + } + + fireProcSpell.Cast(sim, result.Target) + }, + })) + } +} + +/////////////////////////////////////////////////////////////////////////// +// Affliction +/////////////////////////////////////////////////////////////////////////// + +func (warlock *Warlock) applySuppression() { + if warlock.Talents.Suppression == 0 { + return + } + + points := float64(warlock.Talents.Suppression) + warlock.OnSpellRegistered(func(spell *core.Spell) { + if spell.Flags.Matches(WarlockFlagAffliction) { + spell.BonusHitRating += 2 * points * core.CritRatingPerCritChance + } + }) +} + +func (warlock *Warlock) applyNightfall() { + if warlock.Talents.Nightfall <= 0 { + return + } + + has6PCorruptedFelheart := warlock.HasSetBonus(ItemSetCorruptedFelheart, 6) + + nightfallProcChance := 0.02 * float64(warlock.Talents.Nightfall) + if has6PCorruptedFelheart { + nightfallProcChance += .04 + } + + warlock.NightfallProcAura = warlock.RegisterAura(core.Aura{ + Label: "Nightfall Shadow Trance", + ActionID: core.ActionID{SpellID: 17941}, + Duration: time.Second * 10, + OnGain: func(aura *core.Aura, sim *core.Simulation) { + warlock.ShadowBolt.CastTimeMultiplier -= 1 + }, + OnExpire: func(aura *core.Aura, sim *core.Simulation) { + warlock.ShadowBolt.CastTimeMultiplier += 1 + }, + OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { + // Check if the shadowbolt was instant cast and not a normal one + if spell == warlock.ShadowBolt && spell.CurCast.CastTime == 0 { + aura.Deactivate(sim) + } + }, + }) + + warlock.RegisterAura(core.Aura{ + Label: "Nightfall Hidden Aura", + 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.SpellCode == SpellCode_WarlockCorruption || spell.SpellCode == SpellCode_WarlockDrainLife { + if sim.Proc(nightfallProcChance, "Nightfall") { + warlock.NightfallProcAura.Activate(sim) + + for _, spell := range warlock.ShadowCleave { + spell.CD.Reset() + } + } + } + }, + }) +} + +func (warlock *Warlock) applyShadowMastery() { + if warlock.Talents.ShadowMastery == 0 { + return + } + + warlock.OnSpellRegistered(func(spell *core.Spell) { + // Shadow Mastery applies a base damage modifier to all dots / channeled spells instead + if spell.SpellSchool.Matches(core.SpellSchoolShadow) && isWarlockSpell(spell) && !spell.Flags.Matches(core.SpellFlagPureDot) && !spell.Flags.Matches(core.SpellFlagHauntSE) { + spell.DamageMultiplierAdditive += warlock.shadowMasteryBonus() + } + }) +} + +func (warlock *Warlock) shadowMasteryBonus() float64 { + return .02 * float64(warlock.Talents.ShadowMastery) +} + +/////////////////////////////////////////////////////////////////////////// +// Demonology Talents +/////////////////////////////////////////////////////////////////////////// + +func (warlock *Warlock) applyDemonicEmbrace() { + if warlock.Talents.DemonicEmbrace == 0 { + return + } + + points := float64(warlock.Talents.DemonicEmbrace) + warlock.MultiplyStat(stats.Stamina, 1+.03*(points)) + warlock.MultiplyStat(stats.Spirit, 1-.01*(points)) +} + +func (warlock *Warlock) applyFelIntellect() { + if warlock.Talents.FelIntellect == 0 || warlock.Pet == nil { + return + } + + warlock.Pet.MultiplyStat(stats.Mana, 1+0.03*float64(warlock.Talents.FelIntellect)) } func (warlock *Warlock) applyMasterDemonologist() { @@ -272,190 +473,100 @@ func (warlock *Warlock) applyDemonicSacrifice() { }) } +/////////////////////////////////////////////////////////////////////////// +// Destruction Talents +/////////////////////////////////////////////////////////////////////////// + func (warlock *Warlock) applyImprovedShadowBolt() { + if warlock.Talents.ImprovedShadowBolt == 0 { + return + } + warlock.ImprovedShadowBoltAuras = warlock.NewEnemyAuraArray(func(unit *core.Unit, level int32) *core.Aura { return core.ImprovedShadowBoltAura(unit, warlock.Talents.ImprovedShadowBolt) }) + + warlock.RegisterAura(core.Aura{ + Label: "ISB Trigger", + 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() && result.DidCrit() && (spell.SpellCode == SpellCode_WarlockShadowBolt || spell.SpellCode == SpellCode_WarlockShadowCleave) { + impShadowBoltAura := warlock.ImprovedShadowBoltAuras.Get(result.Target) + impShadowBoltAura.Activate(sim) + impShadowBoltAura.SetStacks(sim, 4) + } + }, + }) } -func (warlock *Warlock) applyWeaponImbue() { - if warlock.GetCharacter().Equipment.OffHand().Type != proto.ItemType_ItemTypeUnknown { +func (warlock *Warlock) applyCataclysm() { + if warlock.Talents.Cataclysm == 0 { return } - level := warlock.Level - if warlock.Options.WeaponImbue == proto.WarlockOptions_Firestone { - warlock.applyFirestone() - } - if warlock.Options.WeaponImbue == proto.WarlockOptions_Spellstone { - if level >= 55 { - warlock.AddStat(stats.SpellCrit, 1*core.SpellCritRatingPerCritChance) + points := float64(warlock.Talents.Cataclysm) + warlock.OnSpellRegistered(func(spell *core.Spell) { + if spell.Flags.Matches(WarlockFlagDestruction) { + spell.CostMultiplier *= 1 - .01*points } - } + }) } -func (warlock *Warlock) applyFirestone() { - level := warlock.Level - - damageMin := 0.0 - damageMax := 0.0 - - // TODO: Test for spell scaling - spellCoeff := 0.0 - spellId := int32(0) - - // TODO: Test PPM - ppm := warlock.AutoAttacks.NewPPMManager(8, core.ProcMaskMelee) - - firestoneMulti := 1.0 + float64(warlock.Talents.ImprovedFirestone)*0.15 - - if level >= 56 { - warlock.AddStat(stats.FirePower, 21*firestoneMulti) - damageMin = 80.0 - damageMax = 120.0 - spellId = 17949 - } else if level >= 46 { - warlock.AddStat(stats.FirePower, 17*firestoneMulti) - damageMin = 60.0 - damageMax = 90.0 - spellId = 17947 - } else if level >= 36 { - warlock.AddStat(stats.FirePower, 14*firestoneMulti) - damageMin = 40.0 - damageMax = 60.0 - spellId = 17945 - } else if level >= 28 { - warlock.AddStat(stats.FirePower, 10*firestoneMulti) - damageMin = 25.0 - damageMax = 35.0 - spellId = 758 +func (warlock *Warlock) applyBane() { + if warlock.Talents.Bane == 0 { + return } - if level >= 28 && warlock.Consumes.MainHandImbue == proto.WeaponImbue_WeaponImbueUnknown { - fireProcSpell := warlock.GetOrRegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: spellId}, - SpellSchool: core.SpellSchoolFire, - DefenseType: core.DefenseTypeMagic, - ProcMask: core.ProcMaskEmpty, - - DamageMultiplier: firestoneMulti, - ThreatMultiplier: 1, - DamageMultiplierAdditive: 1, - BonusCoefficient: spellCoeff, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - baseDamage := sim.Roll(damageMin, damageMax) - - spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMagicCrit) - }, - }) - - core.MakePermanent(warlock.GetOrRegisterAura(core.Aura{ - Label: "Firestone Proc", - OnSpellHitDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if !result.Landed() { - return - } - - if !spell.ProcMask.Matches(core.ProcMaskMelee) { - return - } - - if !ppm.Proc(sim, core.ProcMaskMelee, "Firestone Proc") { - return - } - - fireProcSpell.Cast(sim, result.Target) - }, - })) - } + points := time.Duration(warlock.Talents.Bane) + warlock.OnSpellRegistered(func(spell *core.Spell) { + if spell.SpellCode == SpellCode_WarlockShadowBolt || spell.SpellCode == SpellCode_WarlockImmolate { + spell.DefaultCast.CastTime -= time.Millisecond * 100 * points + } else if spell.SpellCode == SpellCode_WarlockSoulFire { + spell.DefaultCast.CastTime -= time.Millisecond * 400 * points + } + }) } -func (warlock *Warlock) applyNightfall() { - if warlock.Talents.Nightfall <= 0 { +func (warlock *Warlock) applyDevastation() { + if warlock.Talents.Devastation == 0 { return } - has6PCorruptedFelheart := warlock.HasSetBonus(ItemSetCorruptedFelheart, 6) - - nightfallProcChance := 0.02 * float64(warlock.Talents.Nightfall) - if has6PCorruptedFelheart { - nightfallProcChance += .04 - } - - warlock.NightfallProcAura = warlock.RegisterAura(core.Aura{ - Label: "Nightfall Shadow Trance", - ActionID: core.ActionID{SpellID: 17941}, - Duration: time.Second * 10, - OnGain: func(aura *core.Aura, sim *core.Simulation) { - warlock.ShadowBolt.CastTimeMultiplier -= 1 - }, - OnExpire: func(aura *core.Aura, sim *core.Simulation) { - warlock.ShadowBolt.CastTimeMultiplier += 1 - }, - OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { - // Check if the shadowbolt was instant cast and not a normal one - if spell == warlock.ShadowBolt && spell.CurCast.CastTime == 0 { - aura.Deactivate(sim) - } - }, + points := float64(warlock.Talents.Devastation) + warlock.OnSpellRegistered(func(spell *core.Spell) { + if spell.Flags.Matches(WarlockFlagDestruction) { + spell.BonusCritRating += points * core.CritRatingPerCritChance + } }) +} - warlock.RegisterAura(core.Aura{ - Label: "Nightfall Hidden Aura", - 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.SpellCode == SpellCode_WarlockCorruption || spell.SpellCode == SpellCode_WarlockDrainLife { - if sim.Proc(nightfallProcChance, "Nightfall") { - warlock.NightfallProcAura.Activate(sim) +func (warlock *Warlock) improvedImmolateBonus() float64 { + return 0.05 * float64(warlock.Talents.ImprovedImmolate) +} - for _, spell := range warlock.ShadowCleave { - spell.CD.Reset() - } - } - } - }, +func (warlock *Warlock) applyRuin() { + if !warlock.Talents.Ruin { + return + } + warlock.OnSpellRegistered(func(spell *core.Spell) { + if spell.Flags.Matches(WarlockFlagDestruction) { + spell.CritDamageBonus += 1 + } }) } -func (warlock *Warlock) ruin() float64 { - return core.TernaryFloat64(warlock.Talents.Ruin, 1, 0) -} +func (warlock *Warlock) applyEmberstorm() { + if warlock.Talents.Emberstorm == 0 { + return + } -// func (warlock *Warlock) setupPyroclasm() { -// if warlock.Talents.Pyroclasm <= 0 { -// return -// } - -// pyroclasmDamageBonus := 1 + 0.02*float64(warlock.Talents.Pyroclasm) - -// warlock.PyroclasmAura = warlock.RegisterAura(core.Aura{ -// Label: "Pyroclasm", -// ActionID: core.ActionID{SpellID: 63244}, -// Duration: time.Second * 10, -// OnGain: func(aura *core.Aura, sim *core.Simulation) { -// aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexShadow] *= pyroclasmDamageBonus -// aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexFire] *= pyroclasmDamageBonus -// }, -// OnExpire: func(aura *core.Aura, sim *core.Simulation) { -// aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexShadow] /= pyroclasmDamageBonus -// aura.Unit.PseudoStats.SchoolDamageDealtMultiplier[stats.SchoolIndexFire] /= pyroclasmDamageBonus -// }, -// }) - -// warlock.RegisterAura(core.Aura{ -// Label: "Pyroclasm Talent Hidden Aura", -// 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 == warlock.Conflagrate || spell == warlock.SearingPain) && result.DidCrit() { -// warlock.PyroclasmAura.Activate(sim) -// } -// }, -// }) -// } + points := float64(warlock.Talents.Emberstorm) + warlock.OnSpellRegistered(func(spell *core.Spell) { + if spell.SpellSchool.Matches(core.SpellSchoolFire) && isWarlockSpell(spell) { + spell.DamageMultiplierAdditive += 0.02 * points + } + }) +} diff --git a/sim/warlock/tank/TestDemonology.results b/sim/warlock/tank/TestDemonology.results index 622948ab3b..b999cd772d 100644 --- a/sim/warlock/tank/TestDemonology.results +++ b/sim/warlock/tank/TestDemonology.results @@ -53,9 +53,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: -0.89806 + weights: -0.89001 weights: 0 - weights: 0.50035 + weights: 0.50101 weights: 0 weights: 0 weights: 0 @@ -63,8 +63,8 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 7.05802 - weights: 1.71868 + weights: 7.11698 + weights: 1.719 weights: 0 weights: 0 weights: 0 @@ -99,63 +99,63 @@ stat_weights_results: { dps_results: { key: "TestDemonology-Lvl40-AllItems-DeathmistRaiment" value: { - dps: 254.30666 + dps: 256.96615 tps: 145.30325 } } dps_results: { key: "TestDemonology-Lvl40-Average-Default" value: { - dps: 608.96109 + dps: 612.09464 tps: 1110.02305 } } dps_results: { key: "TestDemonology-Lvl40-Settings-Orc-p2.demo.tank-Demonology Warlock-p2.demo.tank-FullBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 571.32438 + dps: 574.46224 tps: 1555.10272 } } dps_results: { key: "TestDemonology-Lvl40-Settings-Orc-p2.demo.tank-Demonology Warlock-p2.demo.tank-FullBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 571.32438 + dps: 574.46224 tps: 1048.37234 } } dps_results: { key: "TestDemonology-Lvl40-Settings-Orc-p2.demo.tank-Demonology Warlock-p2.demo.tank-FullBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 600.89673 + dps: 604.34302 tps: 1104.69318 } } dps_results: { key: "TestDemonology-Lvl40-Settings-Orc-p2.demo.tank-Demonology Warlock-p2.demo.tank-NoBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 338.06768 + dps: 340.50081 tps: 1234.64982 } } dps_results: { key: "TestDemonology-Lvl40-Settings-Orc-p2.demo.tank-Demonology Warlock-p2.demo.tank-NoBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 338.06768 + dps: 340.50081 tps: 682.83019 } } dps_results: { key: "TestDemonology-Lvl40-Settings-Orc-p2.demo.tank-Demonology Warlock-p2.demo.tank-NoBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 363.89419 + dps: 366.5883 tps: 728.53424 } } dps_results: { key: "TestDemonology-Lvl40-SwitchInFrontOfTarget-Default" value: { - dps: 604.64884 + dps: 607.84565 tps: 1103.61567 } } diff --git a/sim/warlock/unstable_affliction.go b/sim/warlock/unstable_affliction.go index 5058952755..dc11f05988 100644 --- a/sim/warlock/unstable_affliction.go +++ b/sim/warlock/unstable_affliction.go @@ -14,6 +14,7 @@ func (warlock *Warlock) registerUnstableAfflictionSpell() { hasInvocationRune := warlock.HasRune(proto.WarlockRune_RuneBeltInvocation) hasPandemicRune := warlock.HasRune(proto.WarlockRune_RuneHelmPandemic) + hasShadowflameRune := warlock.HasRune(proto.WarlockRune_RuneBootsShadowflame) baseDamage := warlock.baseRuneAbilityDamage() * 1.1 @@ -22,7 +23,7 @@ func (warlock *Warlock) registerUnstableAfflictionSpell() { SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, DefenseType: core.DefenseTypeMagic, - Flags: core.SpellFlagAPL | SpellFlagHaunt | core.SpellFlagBinary | core.SpellFlagResetAttackSwing | core.SpellFlagPureDot, + Flags: core.SpellFlagAPL | core.SpellFlagHauntSE | core.SpellFlagBinary | core.SpellFlagResetAttackSwing | core.SpellFlagPureDot | WarlockFlagAffliction, ManaCost: core.ManaCostOptions{ BaseCost: 0.15, @@ -34,13 +35,10 @@ func (warlock *Warlock) registerUnstableAfflictionSpell() { }, }, - BonusHitRating: float64(warlock.Talents.Suppression) * 2 * core.SpellHitRatingPerHitChance, - CritDamageBonus: core.TernaryFloat64(hasPandemicRune, 1, 0), - DamageMultiplierAdditive: 1 + 0.02*float64(warlock.Talents.ShadowMastery), - DamageMultiplier: 1, - ThreatMultiplier: 1, + DamageMultiplier: 1, + ThreatMultiplier: 1, Dot: core.DotConfig{ Aura: core.Aura{ @@ -68,14 +66,18 @@ func (warlock *Warlock) registerUnstableAfflictionSpell() { if result.Landed() { spell.SpellMetrics[target.UnitIndex].Hits-- - if hasInvocationRune && spell.Dot(target).IsActive() { - warlock.InvocationRefresh(sim, spell.Dot(target)) - } - + // UA, Immo, Shadowflame exclusivity immoDot := warlock.getActiveImmolateSpell(target) if immoDot != nil { immoDot.Dot(target).Deactivate(sim) } + if hasShadowflameRune && warlock.Shadowflame.Dot(target).IsActive() { + warlock.Shadowflame.Dot(target).Deactivate(sim) + } + + if hasInvocationRune && spell.Dot(target).IsActive() { + warlock.InvocationRefresh(sim, spell.Dot(target)) + } spell.Dot(target).Apply(sim) } diff --git a/sim/warlock/warlock.go b/sim/warlock/warlock.go index 077b3b0bb9..587277c778 100644 --- a/sim/warlock/warlock.go +++ b/sim/warlock/warlock.go @@ -10,15 +10,22 @@ import ( var TalentTreeSizes = [3]int{17, 17, 16} -const SpellFlagHaunt = core.SpellFlagAgentReserved1 +const ( + WarlockFlagAffliction = core.SpellFlagAgentReserved1 + WarlockFlagDemonology = core.SpellFlagAgentReserved2 + WarlockFlagDestruction = core.SpellFlagAgentReserved3 +) const ( SpellCode_WarlockNone int32 = iota SpellCode_WarlockCorruption SpellCode_WarlockDrainLife + SpellCode_WarlockDrainSoul + SpellCode_WarlockHaunt SpellCode_WarlockImmolate SpellCode_WarlockIncinerate + SpellCode_WarlockSearingPain SpellCode_WarlockShadowflame SpellCode_WarlockShadowCleave SpellCode_WarlockShadowBolt @@ -210,3 +217,7 @@ func (warlock *Warlock) OnGCDReady(_ *core.Simulation) { type WarlockAgent interface { GetWarlock() *Warlock } + +func isWarlockSpell(spell *core.Spell) bool { + return spell.Flags.Matches(WarlockFlagAffliction) || spell.Flags.Matches(WarlockFlagDemonology) || spell.Flags.Matches(WarlockFlagDestruction) +} diff --git a/ui/core/components/inputs/shaman_imbues.ts b/ui/core/components/inputs/shaman_imbues.ts index 32a2d5f3dc..f1c7ff8359 100644 --- a/ui/core/components/inputs/shaman_imbues.ts +++ b/ui/core/components/inputs/shaman_imbues.ts @@ -1,54 +1,57 @@ -import { Class, WeaponImbue } from "../../proto/common.js"; - -import { ConsumableInputConfig } from "./consumables"; +import { Class, WeaponImbue } from '../../proto/common.js'; +import { ConsumableInputConfig } from './consumables'; // Shaman Imbues export const RockbiterWeaponImbue: ConsumableInputConfig = { - actionId: (player) => player.getMatchingSpellActionId([ - { id: 8017, minLevel: 1, maxLevel: 7 }, - { id: 8018, minLevel: 8, maxLevel: 15 }, - { id: 8019, minLevel: 16, maxLevel: 23 }, - { id: 10399, minLevel: 24, maxLevel: 33 }, - { id: 16314, minLevel: 34, maxLevel: 43 }, - { id: 16315, minLevel: 44, maxLevel: 53 }, - { id: 16316, minLevel: 54 }, - ]), - value: WeaponImbue.RockbiterWeapon, - showWhen: (player) => player.getClass() == Class.ClassShaman, + actionId: player => + player.getMatchingSpellActionId([ + { id: 8017, minLevel: 1, maxLevel: 7 }, + { id: 8018, minLevel: 8, maxLevel: 15 }, + { id: 8019, minLevel: 16, maxLevel: 23 }, + { id: 10399, minLevel: 24, maxLevel: 33 }, + { id: 16314, minLevel: 34, maxLevel: 43 }, + { id: 16315, minLevel: 44, maxLevel: 53 }, + { id: 16316, minLevel: 54 }, + ]), + value: WeaponImbue.RockbiterWeapon, + showWhen: player => player.getClass() == Class.ClassShaman, }; export const FlametongueWeaponImbue: ConsumableInputConfig = { - actionId: (player) => player.getMatchingSpellActionId([ - { id: 8024, minLevel: 10, maxLevel: 17 }, - { id: 8027, minLevel: 18, maxLevel: 25 }, - { id: 8030, minLevel: 26, maxLevel: 35 }, - { id: 16339, minLevel: 36, maxLevel: 45 }, - { id: 16341, minLevel: 46, maxLevel: 55 }, - { id: 16342, minLevel: 56 }, - ]), + actionId: player => + player.getMatchingSpellActionId([ + { id: 8024, minLevel: 10, maxLevel: 17 }, + { id: 8027, minLevel: 18, maxLevel: 25 }, + { id: 8030, minLevel: 26, maxLevel: 35 }, + { id: 16339, minLevel: 36, maxLevel: 45 }, + { id: 16341, minLevel: 46, maxLevel: 55 }, + { id: 16342, minLevel: 56 }, + ]), value: WeaponImbue.FlametongueWeapon, - showWhen: (player) => player.getClass() == Class.ClassShaman, + showWhen: player => player.getClass() == Class.ClassShaman, }; export const FrostbrandWeaponImbue: ConsumableInputConfig = { - actionId: (player) => player.getMatchingSpellActionId([ - { id: 8033, minLevel: 20, maxLevel: 27 }, - { id: 8038, minLevel: 28, maxLevel: 37 }, - { id: 10456, minLevel: 38, maxLevel: 47 }, - { id: 16355, minLevel: 48, maxLevel: 57 }, - { id: 16356, minLevel: 58, }, - ]), + actionId: player => + player.getMatchingSpellActionId([ + { id: 8033, minLevel: 20, maxLevel: 27 }, + { id: 8038, minLevel: 28, maxLevel: 37 }, + { id: 10456, minLevel: 38, maxLevel: 47 }, + { id: 16355, minLevel: 48, maxLevel: 57 }, + { id: 16356, minLevel: 58 }, + ]), value: WeaponImbue.FrostbrandWeapon, - showWhen: (player) => player.getClass() == Class.ClassShaman, + showWhen: player => player.getClass() == Class.ClassShaman, }; export const WindfuryWeaponImbue: ConsumableInputConfig = { - actionId: (player) => player.getMatchingSpellActionId([ - { id: 8232, minLevel: 30, maxLevel: 39 }, - { id: 8235, minLevel: 40, maxLevel: 49 }, - { id: 10486, minLevel: 50, maxLevel: 59 }, - { id: 16362, minLevel: 60 }, - ]), + actionId: player => + player.getMatchingSpellActionId([ + { id: 8232, minLevel: 30, maxLevel: 39 }, + { id: 8235, minLevel: 40, maxLevel: 49 }, + { id: 10486, minLevel: 50, maxLevel: 59 }, + { id: 16362, minLevel: 60 }, + ]), value: WeaponImbue.WindfuryWeapon, - showWhen: (player) => player.getClass() == Class.ClassShaman, + showWhen: player => player.getClass() == Class.ClassShaman, }; diff --git a/ui/core/components/inputs/warlock_inputs.ts b/ui/core/components/inputs/warlock_inputs.ts new file mode 100644 index 0000000000..3bbacaaf46 --- /dev/null +++ b/ui/core/components/inputs/warlock_inputs.ts @@ -0,0 +1,102 @@ +import { Player } from '../../player.js'; +import { ItemSlot } from '../../proto/common.js'; +import { + WarlockOptions_Armor as Armor, + WarlockOptions_MaxFireboltRank as MaxFireboltRank, + WarlockOptions_Summon as Summon, + WarlockOptions_WeaponImbue as WeaponImbue, + WarlockRune, +} from '../../proto/warlock.js'; +import { ActionId } from '../../proto_utils/action_id.js'; +import { WarlockSpecs } from '../../proto_utils/utils.js'; +import * as InputHelpers from '../input_helpers.js'; + +export const ArmorInput = () => + InputHelpers.makeSpecOptionsEnumIconInput({ + fieldName: 'armor', + values: [ + { value: Armor.NoArmor, tooltip: 'No Armor' }, + { + actionId: player => + player.getMatchingSpellActionId([ + { id: 706, minLevel: 20, maxLevel: 39 }, + { id: 11733, minLevel: 40, maxLevel: 49 }, + { id: 11734, minLevel: 50, maxLevel: 59 }, + { id: 11735, minLevel: 60 }, + ]), + value: Armor.DemonArmor, + }, + ], + }); + +export const WeaponImbueInput = () => + InputHelpers.makeSpecOptionsEnumIconInput({ + fieldName: 'weaponImbue', + values: [ + { value: WeaponImbue.NoWeaponImbue, tooltip: 'No Weapon Stone' }, + { + actionId: player => + player.getMatchingItemActionId([ + { id: 1254, minLevel: 28, maxLevel: 35 }, + { id: 13699, minLevel: 36, maxLevel: 45 }, + { id: 13700, minLevel: 46, maxLevel: 55 }, + { id: 13701, minLevel: 56 }, + ]), + value: WeaponImbue.Firestone, + }, + { + actionId: player => + player.getMatchingItemActionId([ + { id: 5522, minLevel: 36, maxLevel: 47 }, + { id: 13602, minLevel: 48, maxLevel: 59 }, + { id: 13603, minLevel: 60 }, + ]), + value: WeaponImbue.Spellstone, + }, + ], + showWhen: player => player.getEquippedItem(ItemSlot.ItemSlotOffHand) == null && player.getLevel() >= 28, + changeEmitter: (player: Player) => player.changeEmitter, + }); + +export const PetInput = () => + InputHelpers.makeSpecOptionsEnumIconInput({ + fieldName: 'summon', + values: [ + { value: Summon.NoSummon, tooltip: 'No Pet' }, + { actionId: () => ActionId.fromSpellId(688), value: Summon.Imp }, + { actionId: () => ActionId.fromSpellId(697), value: Summon.Voidwalker }, + { actionId: () => ActionId.fromSpellId(712), value: Summon.Succubus }, + { actionId: () => ActionId.fromSpellId(691), value: Summon.Felhunter }, + { + actionId: () => ActionId.fromSpellId(427733), + value: Summon.Felguard, + showWhen: player => player.getEquippedItem(ItemSlot.ItemSlotWrist)?.rune?.id == WarlockRune.RuneBracerSummonFelguard, + }, + ], + changeEmitter: (player: Player) => player.changeEmitter, + }); + +export const ImpFireboltRank = () => + InputHelpers.makeSpecOptionsEnumIconInput({ + fieldName: 'maxFireboltRank', + showWhen: player => player.getSpecOptions().summon == Summon.Imp, + values: [ + { value: MaxFireboltRank.NoMaximum, tooltip: 'Max' }, + { actionId: () => ActionId.fromSpellId(3110), value: MaxFireboltRank.Rank1 }, + { actionId: () => ActionId.fromSpellId(7799), value: MaxFireboltRank.Rank2 }, + { actionId: () => ActionId.fromSpellId(7800), value: MaxFireboltRank.Rank3 }, + { actionId: () => ActionId.fromSpellId(7801), value: MaxFireboltRank.Rank4 }, + { actionId: () => ActionId.fromSpellId(7802), value: MaxFireboltRank.Rank5 }, + { actionId: () => ActionId.fromSpellId(11762), value: MaxFireboltRank.Rank6 }, + { actionId: () => ActionId.fromSpellId(11763), value: MaxFireboltRank.Rank7 }, + ], + changeEmitter: (player: Player) => player.changeEmitter, + }); + +export const PetPoolManaInput = () => + InputHelpers.makeSpecOptionsBooleanInput({ + fieldName: 'petPoolMana', + label: 'No Pet Management', + labelTooltip: 'Should Pet keep trying to cast on every mana regen instead of waiting for mana', + changeEmitter: (player: Player) => player.changeEmitter, + }); diff --git a/ui/tank_warlock/inputs.ts b/ui/tank_warlock/inputs.ts index a3ed5804e9..f2b465f0b4 100644 --- a/ui/tank_warlock/inputs.ts +++ b/ui/tank_warlock/inputs.ts @@ -1,69 +1 @@ -import * as InputHelpers from '../core/components/input_helpers.js'; -import { Player } from '../core/player.js'; -import { ItemSlot, Spec } from '../core/proto/common.js'; -import { - WarlockOptions_Armor as Armor, - WarlockOptions_Summon as Summon, - WarlockOptions_WeaponImbue as WeaponImbue, - WarlockRune} from '../core/proto/warlock.js'; -import { ActionId } from '../core/proto_utils/action_id.js'; - -// Configuration for spec-specific UI elements on the settings tab. -// These don't need to be in a separate file but it keeps things cleaner. - -export const ArmorInput = InputHelpers.makeSpecOptionsEnumIconInput({ - fieldName: 'armor', - values: [ - { value: Armor.NoArmor, tooltip: 'No Armor' }, - { actionId: player => player.getMatchingSpellActionId([ - { id: 706, minLevel: 20, maxLevel: 39 }, - { id: 11733, minLevel: 40, maxLevel: 49 }, - { id: 11734, minLevel: 50, maxLevel: 59 }, - { id: 11735, minLevel: 60 }, - ]), value: Armor.DemonArmor }, - ], -}); - -export const WeaponImbueInput = InputHelpers.makeSpecOptionsEnumIconInput({ - fieldName: 'weaponImbue', - values: [ - { value: WeaponImbue.NoWeaponImbue, tooltip: 'No Weapon Stone' }, - { actionId: player => player.getMatchingItemActionId([ - { id: 1254, minLevel: 28, maxLevel: 35 }, - { id: 13699, minLevel: 36, maxLevel: 45 }, - { id: 13700, minLevel: 46, maxLevel: 55 }, - { id: 13701, minLevel: 56 }, - ]), value: WeaponImbue.Firestone }, - { actionId: player => player.getMatchingItemActionId([ - { id: 5522, minLevel: 36, maxLevel: 47 }, - { id: 13602, minLevel: 48, maxLevel: 59 }, - { id: 13603, minLevel: 60 }, - ]), value: WeaponImbue.Spellstone }, - ], - showWhen: player => player.getEquippedItem(ItemSlot.ItemSlotOffHand) == null && player.getLevel() >= 28, - changeEmitter: (player: Player) => player.changeEmitter, -}); - -export const PetInput = InputHelpers.makeSpecOptionsEnumIconInput({ - fieldName: 'summon', - values: [ - { value: Summon.NoSummon, tooltip: 'No Pet' }, - { actionId: () => ActionId.fromSpellId(688), value: Summon.Imp }, - { actionId: () => ActionId.fromSpellId(697), value: Summon.Voidwalker }, - { actionId: () => ActionId.fromSpellId(712), value: Summon.Succubus }, - { actionId: () => ActionId.fromSpellId(691), value: Summon.Felhunter }, - { actionId: () => ActionId.fromSpellId(427733), value: Summon.Felguard, showWhen: player => player.getEquippedItem(ItemSlot.ItemSlotWrist)?.rune?.id == WarlockRune.RuneBracerSummonFelguard }, - ], - changeEmitter: (player: Player) => player.changeEmitter, -}); - -export const PetPoolManaInput = InputHelpers.makeSpecOptionsBooleanInput({ - fieldName: 'petPoolMana', - label: "No Pet Management", - labelTooltip: 'Should Pet keep trying to cast on every mana regen instead of waiting for mana', - changeEmitter: (player: Player) => player.changeEmitter, -}); - -export const WarlockRotationConfig = { - inputs: [], -}; +// Shared DPS+Tank inputs in ui/core/components/inputs/warlock_inputs.ts diff --git a/ui/tank_warlock/sim.ts b/ui/tank_warlock/sim.ts index ca5a452efe..7aab3a6563 100644 --- a/ui/tank_warlock/sim.ts +++ b/ui/tank_warlock/sim.ts @@ -1,30 +1,21 @@ import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs'; import * as ConsumablesInputs from '../core/components/inputs/consumables.js'; +import * as WarlockInputs from '../core/components/inputs/warlock_inputs'; import * as OtherInputs from '../core/components/other_inputs.js'; import { Phase } from '../core/constants/other.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; import { Player } from '../core/player.js'; -import { - Class, - Faction, - ItemSlot, - PartyBuffs, - Race, - Spec, - Stat, -} from '../core/proto/common.js'; +import { Class, Faction, ItemSlot, PartyBuffs, Race, Spec, Stat } from '../core/proto/common.js'; import { WarlockRune } from '../core/proto/warlock.js'; import { Stats } from '../core/proto_utils/stats.js'; import { getSpecIcon } from '../core/proto_utils/utils.js'; -import * as WarlockInputs from './inputs.js'; import * as Presets from './presets.js'; const SPEC_CONFIG = registerSpecConfig(Spec.SpecTankWarlock, { cssClass: 'tank-warlock-sim-ui', cssScheme: 'warlock', // List any known bugs / issues here and they'll be shown on the site. - knownIssues: [ - ], + knownIssues: [], // All stats for which EP should be calculated. epStats: [ @@ -113,30 +104,16 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecTankWarlock, { }, // IconInputs to include in the 'Player' section on the settings tab. - playerIconInputs: [ - WarlockInputs.PetInput, - WarlockInputs.ArmorInput, - WarlockInputs.WeaponImbueInput, - ], - rotationInputs: WarlockInputs.WarlockRotationConfig, + playerIconInputs: [WarlockInputs.PetInput(), WarlockInputs.ArmorInput()], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. - includeBuffDebuffInputs: [ - BuffDebuffInputs.ResistanceBuff, - ], - excludeBuffDebuffInputs: [ - BuffDebuffInputs.BleedDebuff, - BuffDebuffInputs.SpellWintersChillDebuff, - ...ConsumablesInputs.FROST_POWER_CONFIG, - ], - petConsumeInputs: [ - ConsumablesInputs.PetScrollOfAgility, - ConsumablesInputs.PetScrollOfStrength, - ], + includeBuffDebuffInputs: [BuffDebuffInputs.ResistanceBuff], + excludeBuffDebuffInputs: [BuffDebuffInputs.BleedDebuff, BuffDebuffInputs.SpellWintersChillDebuff, ...ConsumablesInputs.FROST_POWER_CONFIG], + petConsumeInputs: [ConsumablesInputs.PetScrollOfAgility, ConsumablesInputs.PetScrollOfStrength], // Inputs to include in the 'Other' section on the settings tab. otherInputs: { inputs: [ - WarlockInputs.PetPoolManaInput, + WarlockInputs.PetPoolManaInput(), OtherInputs.TankAssignment, OtherInputs.IncomingHps, OtherInputs.HealingCadence, @@ -157,28 +134,16 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecTankWarlock, { presets: { // Preset talents that the user can quickly select. - talents: [ - ...Presets.TalentPresets[Phase.Phase3], - ...Presets.TalentPresets[Phase.Phase2], - ...Presets.TalentPresets[Phase.Phase1], - ], + talents: [...Presets.TalentPresets[Phase.Phase3], ...Presets.TalentPresets[Phase.Phase2], ...Presets.TalentPresets[Phase.Phase1]], // Preset rotations that the user can quickly select. - rotations: [ - ...Presets.APLPresets[Phase.Phase3], - ...Presets.APLPresets[Phase.Phase2], - ...Presets.APLPresets[Phase.Phase1], - ], + rotations: [...Presets.APLPresets[Phase.Phase3], ...Presets.APLPresets[Phase.Phase2], ...Presets.APLPresets[Phase.Phase1]], // Preset gear configurations that the user can quickly select. - gear: [ - ...Presets.GearPresets[Phase.Phase3], - ...Presets.GearPresets[Phase.Phase2], - ...Presets.GearPresets[Phase.Phase1], - ], + gear: [...Presets.GearPresets[Phase.Phase3], ...Presets.GearPresets[Phase.Phase2], ...Presets.GearPresets[Phase.Phase1]], }, autoRotation: player => { - const hasMasterChanneler = player.getEquippedItem(ItemSlot.ItemSlotChest)?.rune?.id == WarlockRune.RuneChestMasterChanneler + const hasMasterChanneler = player.getEquippedItem(ItemSlot.ItemSlotChest)?.rune?.id == WarlockRune.RuneChestMasterChanneler; // const hasLakeOfFire = player.getEquippedItem(ItemSlot.ItemSlotChest)?.rune?.id == WarlockRune.RuneChestLakeOfFire // MC vs LoF diff --git a/ui/warlock/inputs.ts b/ui/warlock/inputs.ts index f1966a194d..f2b465f0b4 100644 --- a/ui/warlock/inputs.ts +++ b/ui/warlock/inputs.ts @@ -1,81 +1 @@ -import * as InputHelpers from '../core/components/input_helpers.js'; -import { Player } from '../core/player.js'; -import { ItemSlot, Spec } from '../core/proto/common.js'; -import { - WarlockOptions_Armor as Armor, - WarlockOptions_MaxFireboltRank as MaxFireboltRank, - WarlockOptions_Summon as Summon, - WarlockOptions_WeaponImbue as WeaponImbue, WarlockRune} from '../core/proto/warlock.js'; -import { ActionId } from '../core/proto_utils/action_id.js'; - -// Configuration for spec-specific UI elements on the settings tab. -// These don't need to be in a separate file but it keeps things cleaner. - -export const ArmorInput = InputHelpers.makeSpecOptionsEnumIconInput({ - fieldName: 'armor', - values: [ - { value: Armor.NoArmor, tooltip: 'No Armor' }, - { actionId: player => player.getMatchingSpellActionId([ - { id: 706, minLevel: 20, maxLevel: 39 }, - { id: 11733, minLevel: 40, maxLevel: 49 }, - { id: 11734, minLevel: 50, maxLevel: 59 }, - { id: 11735, minLevel: 60 }, - ]), value: Armor.DemonArmor }, - ], -}); - -export const WeaponImbueInput = InputHelpers.makeSpecOptionsEnumIconInput({ - fieldName: 'weaponImbue', - values: [ - { value: WeaponImbue.NoWeaponImbue, tooltip: 'No Weapon Stone' }, - { actionId: player => player.getMatchingItemActionId([ - { id: 1254, minLevel: 28, maxLevel: 35 }, - { id: 13699, minLevel: 36, maxLevel: 45 }, - { id: 13700, minLevel: 46, maxLevel: 55 }, - { id: 13701, minLevel: 56 }, - ]), value: WeaponImbue.Firestone }, - { actionId: player => player.getMatchingItemActionId([ - { id: 5522, minLevel: 36, maxLevel: 47 }, - { id: 13602, minLevel: 48, maxLevel: 59 }, - { id: 13603, minLevel: 60 }, - ]), value: WeaponImbue.Spellstone }, - ], - showWhen: player => player.getEquippedItem(ItemSlot.ItemSlotOffHand) == null && player.getLevel() >= 28, - changeEmitter: (player: Player) => player.changeEmitter, -}); - -export const PetInput = InputHelpers.makeSpecOptionsEnumIconInput({ - fieldName: 'summon', - values: [ - { value: Summon.NoSummon, tooltip: 'No Pet' }, - { actionId: () => ActionId.fromSpellId(688), value: Summon.Imp }, - { actionId: () => ActionId.fromSpellId(697), value: Summon.Voidwalker }, - { actionId: () => ActionId.fromSpellId(712), value: Summon.Succubus }, - { actionId: () => ActionId.fromSpellId(691), value: Summon.Felhunter }, - { actionId: () => ActionId.fromSpellId(427733), value: Summon.Felguard, showWhen: player => player.getEquippedItem(ItemSlot.ItemSlotWrist)?.rune?.id == WarlockRune.RuneBracerSummonFelguard }, - ], - changeEmitter: (player: Player) => player.changeEmitter, -}); - -export const ImpFireboltRank = InputHelpers.makeSpecOptionsEnumIconInput({ - fieldName: 'maxFireboltRank', - showWhen: player => player.getSpecOptions().summon == Summon.Imp, - values: [ - { value: MaxFireboltRank.NoMaximum, tooltip: 'Max' }, - { actionId: () => ActionId.fromSpellId(3110), value: MaxFireboltRank.Rank1 }, - { actionId: () => ActionId.fromSpellId(7799), value: MaxFireboltRank.Rank2 }, - { actionId: () => ActionId.fromSpellId(7800), value: MaxFireboltRank.Rank3 }, - { actionId: () => ActionId.fromSpellId(7801), value: MaxFireboltRank.Rank4 }, - { actionId: () => ActionId.fromSpellId(7802), value: MaxFireboltRank.Rank5 }, - { actionId: () => ActionId.fromSpellId(11762), value: MaxFireboltRank.Rank6 }, - { actionId: () => ActionId.fromSpellId(11763), value: MaxFireboltRank.Rank7 }, - ], - changeEmitter: (player: Player) => player.changeEmitter, -}); - -export const PetPoolManaInput = InputHelpers.makeSpecOptionsBooleanInput({ - fieldName: 'petPoolMana', - label: "No Pet Management", - labelTooltip: 'Should Pet keep trying to cast on every mana regen instead of waiting for mana', - changeEmitter: (player: Player) => player.changeEmitter, -}); +// Shared DPS+Tank inputs in ui/core/components/inputs/warlock_inputs.ts diff --git a/ui/warlock/sim.ts b/ui/warlock/sim.ts index c5829d91ad..aad1e4011a 100644 --- a/ui/warlock/sim.ts +++ b/ui/warlock/sim.ts @@ -1,5 +1,6 @@ import * as BuffDebuffInputs from '../core/components/inputs/buffs_debuffs'; import * as ConsumablesInputs from '../core/components/inputs/consumables.js'; +import * as WarlockInputs from '../core/components/inputs/warlock_inputs'; import * as OtherInputs from '../core/components/other_inputs.js'; import { Phase } from '../core/constants/other.js'; import { IndividualSimUI, registerSpecConfig } from '../core/individual_sim_ui.js'; @@ -8,7 +9,6 @@ import { Class, Faction, ItemSlot, PartyBuffs, Race, Spec, Stat } from '../core/ import { WarlockRune } from '../core/proto/warlock'; import { Stats } from '../core/proto_utils/stats.js'; import { getSpecIcon } from '../core/proto_utils/utils.js'; -import * as WarlockInputs from './inputs.js'; import * as Presets from './presets.js'; const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarlock, { @@ -135,7 +135,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarlock, { }, // IconInputs to include in the 'Player' section on the settings tab. - playerIconInputs: [WarlockInputs.PetInput, WarlockInputs.ImpFireboltRank, WarlockInputs.ArmorInput, WarlockInputs.WeaponImbueInput], + playerIconInputs: [WarlockInputs.PetInput(), WarlockInputs.ImpFireboltRank(), WarlockInputs.ArmorInput(), WarlockInputs.WeaponImbueInput()], // Buff and Debuff inputs to include/exclude, overriding the EP-based defaults. includeBuffDebuffInputs: [ @@ -157,7 +157,7 @@ const SPEC_CONFIG = registerSpecConfig(Spec.SpecWarlock, { petConsumeInputs: [ConsumablesInputs.PetScrollOfAgility, ConsumablesInputs.PetScrollOfStrength], // Inputs to include in the 'Other' section on the settings tab. otherInputs: { - inputs: [WarlockInputs.PetPoolManaInput, OtherInputs.DistanceFromTarget, OtherInputs.ChannelClipDelay], + inputs: [WarlockInputs.PetPoolManaInput(), OtherInputs.DistanceFromTarget, OtherInputs.ChannelClipDelay], }, itemSwapConfig: { itemSlots: [ItemSlot.ItemSlotMainHand, ItemSlot.ItemSlotOffHand, ItemSlot.ItemSlotRanged], From 5e3c1e043692ca212e83316e91b4ec2460cee942 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sat, 29 Jun 2024 19:39:14 -0400 Subject: [PATCH 02/10] more warlock refactors --- sim/core/debuffs.go | 6 +- sim/core/flags.go | 1 - sim/core/spell_result.go | 4 - sim/core/target.go | 14 +-- sim/warlock/_curses.go | 130 -------------------- sim/warlock/_drain_soul.go | 139 ---------------------- sim/warlock/apl_values.go | 31 +++-- sim/warlock/conflagrate.go | 21 ++-- sim/warlock/corruption.go | 20 ++-- sim/warlock/curses.go | 152 +++++++++++++++++++----- sim/warlock/death_coil.go | 19 +-- sim/warlock/drain_life.go | 83 ++++--------- sim/warlock/drain_soul.go | 97 +++++++++++++++ sim/warlock/haunt.go | 8 +- sim/warlock/immolate.go | 22 ++-- sim/warlock/item_sets_pve.go | 28 ++--- sim/warlock/item_sets_pvp.go | 16 +-- sim/warlock/lifetap.go | 15 +-- sim/warlock/rain_of_fire.go | 21 ++-- sim/warlock/runes.go | 67 ++++++++++- sim/warlock/searing_pain.go | 21 ++-- sim/warlock/shadowbolt.go | 22 ++-- sim/warlock/shadowburn.go | 19 +-- sim/warlock/siphon_life.go | 21 ++-- sim/warlock/soul_fire.go | 20 ++-- sim/warlock/talents.go | 27 +++-- sim/warlock/unstable_affliction.go | 2 +- sim/warlock/warlock.go | 31 +++-- ui/warlock/apls/p4/affliction.apl.json | 47 ++++---- ui/warlock/apls/p4/destruction.apl.json | 4 +- 30 files changed, 537 insertions(+), 571 deletions(-) delete mode 100644 sim/warlock/_curses.go delete mode 100644 sim/warlock/_drain_soul.go create mode 100644 sim/warlock/drain_soul.go diff --git a/sim/core/debuffs.go b/sim/core/debuffs.go index ab3a2933f9..c71d27a3ac 100644 --- a/sim/core/debuffs.go +++ b/sim/core/debuffs.go @@ -87,7 +87,7 @@ func applyDebuffEffects(target *Unit, targetIdx int, debuffs *proto.Debuffs, rai } if debuffs.MarkOfChaos { - MakePermanent(MarkOfChaosDebuffAura(target, level)) + MakePermanent(MarkOfChaosDebuffAura(target)) } else { if debuffs.CurseOfElements { MakePermanent(CurseOfElementsAura(target, level)) @@ -582,14 +582,14 @@ func MekkatorqueFistDebuffAura(target *Unit, playerLevel int32) *Aura { } // Mark of Chaos does not stack with Curse of Shadows and Elements -func MarkOfChaosDebuffAura(target *Unit, level int32) *Aura { +func MarkOfChaosDebuffAura(target *Unit) *Aura { dmgMod := 1.11 resistance := 75.0 aura := target.GetOrRegisterAura(Aura{ Label: "Mark of Chaos", ActionID: ActionID{SpellID: 461615}, - Duration: time.Second * 60, + Duration: NeverExpires, // Duration is set by the applying curse }) // 0.01 priority as this overwrites the other spells of this category and does not allow them to be recast diff --git a/sim/core/flags.go b/sim/core/flags.go index 7bc3f2c189..7f09e1ecf3 100644 --- a/sim/core/flags.go +++ b/sim/core/flags.go @@ -168,7 +168,6 @@ const ( SpellFlagBinary // Does not do partial resists and could need a different hit roll. SpellFlagChanneled // Spell is channeled SpellFlagDisease // Spell is categorized as disease - SpellFlagHauntSE // Spell benefits from haunt/SE effects SpellFlagPoison // Spell is categorized as poison SpellFlagHelpful // For healing spells / buffs. SpellFlagMeleeMetrics // Marks a spell as a melee ability for metrics. diff --git a/sim/core/spell_result.go b/sim/core/spell_result.go index 615abceaa5..7a42a025ee 100644 --- a/sim/core/spell_result.go +++ b/sim/core/spell_result.go @@ -555,10 +555,6 @@ func (spell *Spell) TargetDamageMultiplier(attackTable *AttackTable, isPeriodic multiplier *= attackTable.Defender.PseudoStats.PoisonDamageTakenMultiplier } - if spell.Flags.Matches(SpellFlagHauntSE) { - multiplier *= attackTable.HauntSEDamageTakenMultiplier - } - if isPeriodic && spell.SpellSchool.Matches(SpellSchoolPhysical) { multiplier *= attackTable.Defender.PseudoStats.BleedDamageTakenMultiplier } diff --git a/sim/core/target.go b/sim/core/target.go index 9c0b64abbf..ea825eff93 100644 --- a/sim/core/target.go +++ b/sim/core/target.go @@ -265,10 +265,9 @@ type AttackTable struct { // Explicitly for hunters' "Monster Slaying" and "Humanoid Slaying", but likewise for rogues' "Murder", or trolls' "Beastslaying". CritMultiplier float64 - DamageDealtMultiplier float64 // attacker buff, applied in applyAttackerModifiers() - DamageTakenMultiplier float64 // defender debuff, applied in applyTargetModifiers() - HauntSEDamageTakenMultiplier float64 // attacker buff for Haunt/SE-type effects. Applied in applyTargetModifiers() - HealingDealtMultiplier float64 + DamageDealtMultiplier float64 // attacker buff, applied in applyAttackerModifiers() + DamageTakenMultiplier float64 // defender debuff, applied in applyTargetModifiers() + HealingDealtMultiplier float64 // This is for "Apply Aura: Mod Damage Done By Caster" effects. // If set, the damage taken multiplier is multiplied by the callbacks result. @@ -284,10 +283,9 @@ func NewAttackTable(attacker *Unit, defender *Unit, weapon *Item) *AttackTable { CritMultiplier: 1, - DamageDealtMultiplier: 1, - DamageTakenMultiplier: 1, - HauntSEDamageTakenMultiplier: 1, - HealingDealtMultiplier: 1, + DamageDealtMultiplier: 1, + DamageTakenMultiplier: 1, + HealingDealtMultiplier: 1, } if defender.Type == EnemyUnit { diff --git a/sim/warlock/_curses.go b/sim/warlock/_curses.go deleted file mode 100644 index 8335037c55..0000000000 --- a/sim/warlock/_curses.go +++ /dev/null @@ -1,130 +0,0 @@ -package warlock - -import ( - "time" - - "github.com/wowsims/sod/sim/core" -) - -func (warlock *Warlock) registerCurseOfWeaknessSpell() { - warlock.CurseOfWeaknessAuras = warlock.NewEnemyAuraArray(func(target *core.Unit) *core.Aura { - return core.CurseOfWeaknessAura(target, warlock.Talents.ImprovedCurseOfWeakness) - }) - - warlock.CurseOfWeakness = warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 50511}, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.1, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault - core.TernaryDuration(warlock.Talents.AmplifyCurse, 1, 0)*500*time.Millisecond, - }, - }, - - ThreatMultiplier: 1 - 0.1*float64(warlock.Talents.ImprovedDrainSoul), - FlatThreatBonus: 142, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHit) - if result.Landed() { - warlock.CurseOfWeaknessAuras.Get(target).Activate(sim) - } - }, - - RelatedAuras: []core.AuraArray{warlock.CurseOfWeaknessAuras}, - }) -} - -func (warlock *Warlock) registerCurseOfTonguesSpell() { - actionID := core.ActionID{SpellID: 11719} - - // Empty aura so we can simulate cost/time to keep tongues up - warlock.CurseOfTonguesAuras = warlock.NewEnemyAuraArray(func(target *core.Unit) *core.Aura { - return target.GetOrRegisterAura(core.Aura{ - Label: "Curse of Tongues", - ActionID: actionID, - Duration: time.Second * 30, - }) - }) - - warlock.CurseOfTongues = warlock.RegisterSpell(core.SpellConfig{ - ActionID: actionID, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskEmpty, - Flags: core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.04, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault - core.TernaryDuration(warlock.Talents.AmplifyCurse, 1, 0)*500*time.Millisecond, - }, - }, - - ThreatMultiplier: 1 - 0.1*float64(warlock.Talents.ImprovedDrainSoul), - FlatThreatBonus: 100, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHit) - if result.Landed() { - warlock.CurseOfTonguesAuras.Get(target).Activate(sim) - } - }, - - RelatedAuras: []core.AuraArray{warlock.CurseOfTonguesAuras}, - }) -} - -func (warlock *Warlock) registerCurseOfDoomSpell() { - warlock.CurseOfDoom = warlock.RegisterSpell(core.SpellConfig{ - ActionID: core.ActionID{SpellID: 47867}, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.15, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault - core.TernaryDuration(warlock.Talents.AmplifyCurse, 1, 0)*500*time.Millisecond, - }, - CD: core.Cooldown{ - Timer: warlock.NewTimer(), - Duration: time.Minute, - }, - }, - - ThreatMultiplier: 1 - 0.1*float64(warlock.Talents.ImprovedDrainSoul), - FlatThreatBonus: 160, - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "CurseofDoom", - }, - NumberOfTicks: 1, - TickLength: time.Minute, - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - dot.SnapshotBaseDamage = 7300 + 2*dot.Spell.SpellDamage() - 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.OutcomeMagicHit) - if result.Landed() { - warlock.CurseOfAgony.Dot(target).Cancel(sim) - spell.Dot(target).Apply(sim) - } - }, - }) -} diff --git a/sim/warlock/_drain_soul.go b/sim/warlock/_drain_soul.go deleted file mode 100644 index 81aff68b42..0000000000 --- a/sim/warlock/_drain_soul.go +++ /dev/null @@ -1,139 +0,0 @@ -package warlock - -import ( - "strconv" - "time" - - "github.com/wowsims/sod/sim/core" - "github.com/wowsims/sod/sim/core/proto" -) - -func (warlock *Warlock) registerDrainSoulSpell() { - hasSoulSiphonRune := warlock.HasRune(proto.WarlockRune_RuneChestSoulSiphon) - - soulSiphonMultiplier := 0.03 * float64(warlock.Talents.SoulSiphon) - - calcSoulSiphonMult := func(target *core.Unit) float64 { - auras := []*core.Aura{ - warlock.UnstableAffliction.Dot(target).Aura, - warlock.Corruption.Dot(target).Aura, - warlock.Seed.Dot(target).Aura, - warlock.CurseOfAgony.Dot(target).Aura, - warlock.CurseOfDoom.Dot(target).Aura, - warlock.CurseOfElementsAuras.Get(target), - warlock.CurseOfWeaknessAuras.Get(target), - warlock.CurseOfTonguesAuras.Get(target), - warlock.ShadowEmbraceDebuffAura(target), - // missing: death coil - } - if warlock.HauntDebuffAuras != nil { - auras = append(auras, warlock.HauntDebuffAuras.Get(target)) - } - numActive := 0 - for _, aura := range auras { - if aura.IsActive() { - numActive++ - } - } - return 1.0 + float64(min(3, numActive))*soulSiphonMultiplier - } - - warlock.DrainSoul = warlock.RegisterSpell(core.SpellConfig{ - SpellCode: SpellCode_WarlockDrainSoul, - ActionID: core.ActionID{SpellID: 47855}, - SpellSchool: core.SpellSchoolShadow, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagChanneled | core.SpellFlagHauntSE | core.SpellFlagAPL, - - ManaCost: core.ManaCostOptions{ - BaseCost: 0.14, - }, - Cast: core.CastConfig{ - DefaultCast: core.Cast{ - GCD: core.GCDDefault, - // ChannelTime: channelTime, - }, - }, - - DamageMultiplier: 1, - ThreatMultiplier: 1 - 0.1*float64(warlock.Talents.ImprovedDrainSoul), - - Dot: core.DotConfig{ - Aura: core.Aura{ - Label: "Drain Soul", - }, - NumberOfTicks: 5, - TickLength: 3 * time.Second, - AffectedByCastSpeed: true, - - OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { - baseDmg := 142 + 0.429*dot.Spell.SpellDamage() - dot.Snapshot(target, baseDmg*calcSoulSiphonMult(target), isRollover) - - if hasSoulSiphonRune { - isExecute := sim.IsExecutePhase20() - perDoTMultiplier := core.TernaryFloat64(isExecute, SoulSiphonDoTMultiplier, SoulSiphonDoTMultiplierExecute) - maxMultiplier := 1 + core.TernaryFloat64(isExecute, SoulSiphonDoTMultiplierMax, SoulSiphonDoTMultiplierMaxExecute) - multiplier := 1.0 - - hasAura := func(target *core.Unit, label string, rank int) bool { - for i := 1; i <= rank; i++ { - if target.HasActiveAura(label + strconv.Itoa(rank)) { - return true - } - } - return false - } - if hasAura(target, "Corruption-"+warlock.Label, 7) { - multiplier += perDoTMultiplier - } - if hasAura(target, "CurseofAgony-"+warlock.Label, 6) { - multiplier += perDoTMultiplier - } - if hasAura(target, "SiphonLife-"+warlock.Label, 3) { - multiplier += perDoTMultiplier - } - if target.HasActiveAura("UnstableAffliction-" + warlock.Label) { - multiplier += perDoTMultiplier - } - if target.HasActiveAura("Haunt-" + warlock.Label) { - multiplier += perDoTMultiplier - } - - dot.SnapshotAttackerMultiplier *= max(multiplier, maxMultiplier) - } - }, - OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTickCounted) - }, - }, - - ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { - result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHit) - if result.Landed() { - spell.SpellMetrics[target.UnitIndex].Hits-- - dot := spell.Dot(target) - dot.Apply(sim) - dot.UpdateExpires(dot.ExpiresAt()) - } - }, - ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, useSnapshot bool) *core.SpellResult { - if useSnapshot { - dot := spell.Dot(target) - return dot.CalcSnapshotDamage(sim, target, spell.OutcomeExpectedMagicAlwaysHit) - } else { - baseDmg := (142 + 0.429*spell.SpellDamage()) * calcSoulSiphonMult(target) - return spell.CalcPeriodicDamage(sim, target, baseDmg, spell.OutcomeExpectedMagicAlwaysHit) - } - }, - }) - - warlock.RegisterResetEffect(func(sim *core.Simulation) { - sim.RegisterExecutePhaseCallback(func(sim *core.Simulation, isExecute int32) { - if isExecute == 25 { - mult := (4.0 + 0.04*float64(warlock.Talents.DeathsEmbrace)) / (1 + 0.04*float64(warlock.Talents.DeathsEmbrace)) - warlock.DrainSoul.DamageMultiplier = mult - } - }) - }) -} diff --git a/sim/warlock/apl_values.go b/sim/warlock/apl_values.go index 82254d7c52..2d2bd41396 100644 --- a/sim/warlock/apl_values.go +++ b/sim/warlock/apl_values.go @@ -40,17 +40,34 @@ func (value *APLValueWarlockShouldRecastDrainSoul) GetBool(sim *core.Simulation) warlock := value.warlock // Assert that we're currently channeling Drain Soul. - if warlock.ChanneledDot == nil || warlock.ChanneledDot.Spell != warlock.DrainSoul { + if warlock.ChanneledDot == nil { return false } + var activeDrainSoul *core.Spell + for _, spell := range warlock.DrainSoul { + if spell.CurDot().IsActive() { + activeDrainSoul = spell + } + } + if activeDrainSoul == nil { + return false + } + + var activeCurseOfAgony *core.Spell + for _, spell := range warlock.CurseOfAgony { + if spell.CurDot().IsActive() { + activeCurseOfAgony = spell + } + } + curseRefresh := max( - warlock.CurseOfAgony.CurDot().RemainingDuration(sim), + activeCurseOfAgony.CurDot().RemainingDuration(sim), warlock.CurseOfDoom.CurDot().RemainingDuration(sim), warlock.CurseOfElementsAuras.Get(warlock.CurrentTarget).RemainingDuration(sim), - warlock.CurseOfTonguesAuras.Get(warlock.CurrentTarget).RemainingDuration(sim), - warlock.CurseOfWeaknessAuras.Get(warlock.CurrentTarget).RemainingDuration(sim), - ) - warlock.CurseOfAgony.CastTime() + // warlock.CurseOfTonguesAuras.Get(warlock.CurrentTarget).RemainingDuration(sim), + // warlock.CurseOfWeaknessAuras.Get(warlock.CurrentTarget).RemainingDuration(sim), + ) - activeCurseOfAgony.CastTime() hauntRefresh := 1000 * time.Second if warlock.HauntDebuffAuras != nil { @@ -76,8 +93,8 @@ func (value *APLValueWarlockShouldRecastDrainSoul) GetBool(sim *core.Simulation) return false } - snapshotDmg := warlock.DrainSoul.ExpectedTickDamageFromCurrentSnapshot(sim, warlock.CurrentTarget) * float64(ticksLeft) - recastDmg := warlock.DrainSoul.ExpectedTickDamage(sim, warlock.CurrentTarget) * float64(recastTicks) + snapshotDmg := activeDrainSoul.ExpectedTickDamageFromCurrentSnapshot(sim, warlock.CurrentTarget) * float64(ticksLeft) + recastDmg := activeDrainSoul.ExpectedTickDamage(sim, warlock.CurrentTarget) * float64(recastTicks) snapshotDPS := snapshotDmg / (time.Duration(ticksLeft) * dsDot.TickPeriod()).Seconds() recastDps := recastDmg / (time.Duration(recastTicks)*warlock.ApplyCastSpeed(dsDot.TickLength) + warlock.ChannelClipDelay).Seconds() diff --git a/sim/warlock/conflagrate.go b/sim/warlock/conflagrate.go index 5a7cb66965..8033c5d6d8 100644 --- a/sim/warlock/conflagrate.go +++ b/sim/warlock/conflagrate.go @@ -7,14 +7,16 @@ import ( "github.com/wowsims/sod/sim/core/proto" ) +const ConflagrateRanks = 4 + func (warlock *Warlock) getConflagrateConfig(rank int) core.SpellConfig { hasBackdraftRune := warlock.HasRune(proto.WarlockRune_RuneHelmBackdraft) - spellId := [5]int32{0, 17962, 18930, 18931, 18932}[rank] - baseDamageMin := [5]float64{0, 249, 319, 395, 447}[rank] - baseDamageMax := [5]float64{0, 316, 400, 491, 557}[rank] - manaCost := [5]float64{0, 165, 200, 230, 255}[rank] - level := [5]int{0, 0, 48, 54, 60}[rank] + spellId := [ConflagrateRanks + 1]int32{0, 17962, 18930, 18931, 18932}[rank] + baseDamageMin := [ConflagrateRanks + 1]float64{0, 249, 319, 395, 447}[rank] + baseDamageMax := [ConflagrateRanks + 1]float64{0, 316, 400, 491, 557}[rank] + manaCost := [ConflagrateRanks + 1]float64{0, 165, 200, 230, 255}[rank] + level := [ConflagrateRanks + 1]int{0, 0, 48, 54, 60}[rank] spCoeff := 0.429 @@ -87,13 +89,12 @@ func (warlock *Warlock) registerConflagrateSpell() { return } - maxRank := 4 - - for i := 1; i <= maxRank; i++ { - config := warlock.getConflagrateConfig(i) + warlock.Conflagrate = make([]*core.Spell, 0) + for rank := 1; rank <= ConflagrateRanks; rank++ { + config := warlock.getConflagrateConfig(rank) if config.RequiredLevel <= int(warlock.Level) { - warlock.Conflagrate = warlock.GetOrRegisterSpell(config) + warlock.Conflagrate = append(warlock.Conflagrate, warlock.GetOrRegisterSpell(config)) } } } diff --git a/sim/warlock/corruption.go b/sim/warlock/corruption.go index 5830536cb4..757499a7e2 100644 --- a/sim/warlock/corruption.go +++ b/sim/warlock/corruption.go @@ -8,13 +8,15 @@ import ( "github.com/wowsims/sod/sim/core/proto" ) +const CorruptionRanks = 7 + func (warlock *Warlock) getCorruptionConfig(rank int) core.SpellConfig { - dotTickCoeff := [8]float64{0, .08, .155, .167, .167, .167, .167, .167}[rank] // per tick - ticks := [8]int32{0, 4, 5, 6, 6, 6, 6, 6}[rank] - baseDamage := [8]float64{0, 40, 90, 222, 324, 486, 666, 822}[rank] / float64(ticks) - spellId := [8]int32{0, 172, 6222, 6223, 7648, 11671, 11672, 25311}[rank] - manaCost := [8]float64{0, 35, 55, 100, 160, 225, 290, 340}[rank] - level := [8]int{0, 4, 14, 24, 34, 44, 54, 60}[rank] + dotTickCoeff := [CorruptionRanks + 1]float64{0, .08, .155, .167, .167, .167, .167, .167}[rank] // per tick + ticks := [CorruptionRanks + 1]int32{0, 4, 5, 6, 6, 6, 6, 6}[rank] + baseDamage := [CorruptionRanks + 1]float64{0, 40, 90, 222, 324, 486, 666, 822}[rank] / float64(ticks) + spellId := [CorruptionRanks + 1]int32{0, 172, 6222, 6223, 7648, 11671, 11672, 25311}[rank] + manaCost := [CorruptionRanks + 1]float64{0, 35, 55, 100, 160, 225, 290, 340}[rank] + level := [CorruptionRanks + 1]int{0, 4, 14, 24, 34, 44, 54, 60}[rank] castTime := time.Millisecond * (2000 - (400 * time.Duration(warlock.Talents.ImprovedCorruption))) hasInvocationRune := warlock.HasRune(proto.WarlockRune_RuneBeltInvocation) @@ -26,7 +28,7 @@ func (warlock *Warlock) getCorruptionConfig(rank int) core.SpellConfig { SpellCode: SpellCode_WarlockCorruption, ProcMask: core.ProcMaskSpellDamage, DefenseType: core.DefenseTypeMagic, - Flags: core.SpellFlagAPL | core.SpellFlagHauntSE | core.SpellFlagResetAttackSwing | core.SpellFlagPureDot | WarlockFlagAffliction, + Flags: core.SpellFlagAPL | WarlockFlagHaunt | core.SpellFlagResetAttackSwing | core.SpellFlagPureDot | WarlockFlagAffliction, Rank: rank, RequiredLevel: level, @@ -104,10 +106,8 @@ func (warlock *Warlock) getCorruptionConfig(rank int) core.SpellConfig { } func (warlock *Warlock) registerCorruptionSpell() { - maxRank := 7 - warlock.Corruption = make([]*core.Spell, 0) - for i := 1; i <= maxRank; i++ { + for i := 1; i <= CorruptionRanks; i++ { config := warlock.getCorruptionConfig(i) if config.RequiredLevel <= int(warlock.Level) { diff --git a/sim/warlock/curses.go b/sim/warlock/curses.go index 89e3c07c84..949b49a88f 100644 --- a/sim/warlock/curses.go +++ b/sim/warlock/curses.go @@ -8,27 +8,31 @@ import ( "github.com/wowsims/sod/sim/core/proto" ) +const CurseOfAgonyRanks = 6 + func (warlock *Warlock) getCurseOfAgonyBaseConfig(rank int) core.SpellConfig { - spellId := [7]int32{0, 980, 1014, 6217, 11711, 11712, 11713}[rank] - spellCoeff := [7]float64{0, .046, .077, .083, .083, .083, .083}[rank] - baseDamage := [7]float64{0, 7, 15, 27, 42, 65, 87}[rank] - manaCost := [7]float64{0, 25, 50, 90, 130, 170, 215}[rank] - level := [7]int{0, 8, 18, 28, 38, 48, 58}[rank] + numTicks := int32(12) + tickLength := time.Second * 2 + + spellId := [CurseOfAgonyRanks + 1]int32{0, 980, 1014, 6217, 11711, 11712, 11713}[rank] + spellCoeff := [CurseOfAgonyRanks + 1]float64{0, .046, .077, .083, .083, .083, .083}[rank] + baseDamage := [CurseOfAgonyRanks + 1]float64{0, 7, 15, 27, 42, 65, 87}[rank] + manaCost := [CurseOfAgonyRanks + 1]float64{0, 25, 50, 90, 130, 170, 215}[rank] + level := [CurseOfAgonyRanks + 1]int{0, 8, 18, 28, 38, 48, 58}[rank] hasInvocationRune := warlock.HasRune(proto.WarlockRune_RuneBeltInvocation) hasPandemicRune := warlock.HasRune(proto.WarlockRune_RuneHelmPandemic) + hasMarkOfChaosRune := warlock.HasRune(proto.WarlockRune_RuneCloakMarkOfChaos) - baseDamage *= 1 + 0.02*float64(warlock.Talents.ImprovedCurseOfWeakness) + warlock.shadowMasteryBonus() - + baseDamage *= 1 + warlock.shadowMasteryBonus() snapshotBaseDmgNoBonus := 0.0 - markOfChaosAuras := warlock.NewEnemyAuraArray(core.MarkOfChaosDebuffAura) - return core.SpellConfig{ + SpellCode: SpellCode_WarlockCurseOfAgony, ActionID: core.ActionID{SpellID: spellId}, SpellSchool: core.SpellSchoolShadow, DefenseType: core.DefenseTypeMagic, - Flags: core.SpellFlagAPL | core.SpellFlagHauntSE | core.SpellFlagResetAttackSwing | core.SpellFlagPureDot | WarlockFlagAffliction, + Flags: core.SpellFlagAPL | WarlockFlagHaunt | core.SpellFlagResetAttackSwing | core.SpellFlagPureDot | WarlockFlagAffliction, ProcMask: core.ProcMaskSpellDamage, RequiredLevel: level, Rank: rank, @@ -52,8 +56,8 @@ func (warlock *Warlock) getCurseOfAgonyBaseConfig(rank int) core.SpellConfig { Aura: core.Aura{ Label: "CurseofAgony-" + warlock.Label + strconv.Itoa(rank), }, - NumberOfTicks: 12, - TickLength: time.Second * 2, + NumberOfTicks: numTicks, + TickLength: tickLength, BonusCoefficient: spellCoeff, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { @@ -94,27 +98,36 @@ func (warlock *Warlock) getCurseOfAgonyBaseConfig(rank int) core.SpellConfig { if result.Landed() { spell.SpellMetrics[target.UnitIndex].Hits-- - if hasInvocationRune && spell.Dot(target).IsActive() { - warlock.InvocationRefresh(sim, spell.Dot(target)) - } - - markOfChaosAuras.Get(target).Activate(sim) + // If the spell's DoT is already applied, do a refresh if the Invocation rune is also being used + // Else deactivate the existing curse and apply this one instead + if spell.Dot(target).IsActive() { + if hasInvocationRune { + warlock.InvocationRefresh(sim, spell.Dot(target)) + } + } else { + if warlock.ActiveCurseAura != nil { + warlock.ActiveCurseAura.Deactivate(sim) + } + dot := spell.Dot(target) + dot.Apply(sim) + warlock.ActiveCurseAura = dot.Aura - //warlock.CurseOfDoom.Dot(target).Cancel(sim) - spell.Dot(target).Apply(sim) + if hasMarkOfChaosRune { + warlock.applyMarkOfChaosDebuff(sim, target, dot) + } + } } }, } } func (warlock *Warlock) registerCurseOfAgonySpell() { - maxRank := 6 - - for i := 1; i <= maxRank; i++ { - config := warlock.getCurseOfAgonyBaseConfig(i) + warlock.CurseOfAgony = make([]*core.Spell, 0) + for rank := 1; rank <= CurseOfAgonyRanks; rank++ { + config := warlock.getCurseOfAgonyBaseConfig(rank) if config.RequiredLevel <= int(warlock.Level) { - warlock.CurseOfAgony = warlock.GetOrRegisterSpell(config) + warlock.CurseOfAgony = append(warlock.CurseOfAgony, warlock.GetOrRegisterSpell(config)) } } } @@ -167,7 +180,11 @@ func (warlock *Warlock) registerCurseOfRecklessnessSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHit) if result.Landed() { - warlock.CurseOfRecklessnessAuras.Get(target).Activate(sim) + if warlock.ActiveCurseAura != nil { + warlock.ActiveCurseAura.Deactivate(sim) + } + warlock.ActiveCurseAura = warlock.CurseOfRecklessnessAuras.Get(target) + warlock.ActiveCurseAura.Activate(sim) } }, @@ -223,7 +240,11 @@ func (warlock *Warlock) registerCurseOfElementsSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHit) if result.Landed() { - warlock.CurseOfElementsAuras.Get(target).Activate(sim) + if warlock.ActiveCurseAura != nil { + warlock.ActiveCurseAura.Deactivate(sim) + } + warlock.ActiveCurseAura = warlock.CurseOfElementsAuras.Get(target) + warlock.ActiveCurseAura.Activate(sim) } }, @@ -276,7 +297,11 @@ func (warlock *Warlock) registerCurseOfShadowSpell() { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHit) if result.Landed() { - warlock.CurseOfShadowAuras.Get(target).Activate(sim) + if warlock.ActiveCurseAura != nil { + warlock.ActiveCurseAura.Deactivate(sim) + } + warlock.ActiveCurseAura = warlock.CurseOfShadowAuras.Get(target) + warlock.ActiveCurseAura.Activate(sim) } }, @@ -314,3 +339,76 @@ func (warlock *Warlock) registerAmplifyCurseSpell() { }, }) } + +func (warlock *Warlock) registerCurseOfDoomSpell() { + if warlock.Level < 60 { + return + } + + hasPandemicRune := warlock.HasRune(proto.WarlockRune_RuneHelmPandemic) + hasMarkOfChaosRune := warlock.HasRune(proto.WarlockRune_RuneCloakMarkOfChaos) + + warlock.CurseOfDoom = warlock.RegisterSpell(core.SpellConfig{ + SpellCode: SpellCode_WarlockCurseOfDoom, + ActionID: core.ActionID{SpellID: 449432}, // New spell created for SoD + SpellSchool: core.SpellSchoolShadow, + DefenseType: core.DefenseTypeMagic, + ProcMask: core.ProcMaskSpellDamage, + Flags: core.SpellFlagAPL | WarlockFlagAffliction, + + RequiredLevel: 60, + + ManaCost: core.ManaCostOptions{ + FlatCost: 300, + }, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + CD: core.Cooldown{ + Timer: warlock.NewTimer(), + Duration: time.Second * 60, + }, + }, + + CritDamageBonus: core.TernaryFloat64(hasPandemicRune, 1, 0), + + DamageMultiplier: 1, + ThreatMultiplier: 1 - 0.1*float64(warlock.Talents.ImprovedDrainSoul), + FlatThreatBonus: 160, + + Dot: core.DotConfig{ + Aura: core.Aura{ + Label: "CurseofDoom", + }, + NumberOfTicks: 1, + TickLength: time.Minute, + OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { + dot.Snapshot(target, 3200, isRollover) + }, + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + if hasPandemicRune { + dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTickSnapshotCritCounted) + } else { + dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTickCounted) + } + }, + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHit) + if result.Landed() { + if warlock.ActiveCurseAura != nil { + warlock.ActiveCurseAura.Deactivate(sim) + } + dot := spell.Dot(target) + dot.Apply(sim) + warlock.ActiveCurseAura = dot.Aura + + if hasMarkOfChaosRune { + warlock.applyMarkOfChaosDebuff(sim, target, dot) + } + } + }, + }) +} diff --git a/sim/warlock/death_coil.go b/sim/warlock/death_coil.go index 1bb24f2bb3..d2abaa1aa0 100644 --- a/sim/warlock/death_coil.go +++ b/sim/warlock/death_coil.go @@ -6,11 +6,13 @@ import ( "github.com/wowsims/sod/sim/core" ) +const DeathCoilRanks = 3 + func (warlock *Warlock) getDeathCoilBaseConfig(rank int) core.SpellConfig { - spellId := [4]int32{0, 6789, 17925, 17926}[rank] - baseDamage := [4]float64{0, 301, 375, 476}[rank] - manaCost := [4]float64{0, 430, 495, 565}[rank] - level := [4]int{0, 42, 50, 58}[rank] + spellId := [DeathCoilRanks + 1]int32{0, 6789, 17925, 17926}[rank] + baseDamage := [DeathCoilRanks + 1]float64{0, 301, 375, 476}[rank] + manaCost := [DeathCoilRanks + 1]float64{0, 430, 495, 565}[rank] + level := [DeathCoilRanks + 1]int{0, 42, 50, 58}[rank] spellCoeff := 0.214 baseDamage *= 1 + warlock.shadowMasteryBonus() @@ -54,13 +56,12 @@ func (warlock *Warlock) getDeathCoilBaseConfig(rank int) core.SpellConfig { } func (warlock *Warlock) registerDeathCoilSpell() { - maxRank := 3 - - for i := 1; i <= maxRank; i++ { - config := warlock.getDeathCoilBaseConfig(i) + warlock.DeathCoil = make([]*core.Spell, 0) + for rank := 1; rank <= DeathCoilRanks; rank++ { + config := warlock.getDeathCoilBaseConfig(rank) if config.RequiredLevel <= int(warlock.Level) { - warlock.DeathCoil = warlock.GetOrRegisterSpell(config) + warlock.DeathCoil = append(warlock.DeathCoil, warlock.GetOrRegisterSpell(config)) } } } diff --git a/sim/warlock/drain_life.go b/sim/warlock/drain_life.go index 1e551c59d2..8c78549f51 100644 --- a/sim/warlock/drain_life.go +++ b/sim/warlock/drain_life.go @@ -8,17 +8,19 @@ import ( "github.com/wowsims/sod/sim/core/proto" ) +const DrainLifeRanks = 6 + func (warlock *Warlock) getDrainLifeBaseConfig(rank int) core.SpellConfig { hasMasterChannelerRune := warlock.HasRune(proto.WarlockRune_RuneChestMasterChanneler) hasSoulSiphonRune := warlock.HasRune(proto.WarlockRune_RuneChestSoulSiphon) - spellId := [7]int32{0, 689, 699, 709, 7651, 11699, 11700}[rank] - spellCoeff := [7]float64{0, .078, .1, .1, .1, .1, .1}[rank] - baseDamage := [7]float64{0, 10, 17, 29, 41, 55, 71}[rank] - manaCost := [7]float64{0, 55, 85, 135, 185, 240, 300}[rank] - level := [7]int{0, 14, 22, 30, 38, 46, 54}[rank] + numTicks := core.TernaryInt32(hasMasterChannelerRune, 15, 5) - ticks := core.TernaryInt32(hasMasterChannelerRune, 15, 5) + spellId := [DrainLifeRanks + 1]int32{0, 689, 699, 709, 7651, 11699, 11700}[rank] + spellCoeff := [DrainLifeRanks + 1]float64{0, .078, .1, .1, .1, .1, .1}[rank] + baseDamage := [DrainLifeRanks + 1]float64{0, 10, 17, 29, 41, 55, 71}[rank] + manaCost := [DrainLifeRanks + 1]float64{0, 55, 85, 135, 185, 240, 300}[rank] + level := [DrainLifeRanks + 1]int{0, 14, 22, 30, 38, 46, 54}[rank] if hasMasterChannelerRune { manaCost *= 2 @@ -30,11 +32,13 @@ func (warlock *Warlock) getDrainLifeBaseConfig(rank int) core.SpellConfig { healthMetrics := warlock.NewHealthMetrics(actionID) spellConfig := core.SpellConfig{ - ActionID: actionID, - SpellSchool: core.SpellSchoolShadow, - SpellCode: SpellCode_WarlockDrainLife, - ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagHauntSE | core.SpellFlagAPL | core.SpellFlagResetAttackSwing | WarlockFlagAffliction, + ActionID: actionID, + SpellSchool: core.SpellSchoolShadow, + SpellCode: SpellCode_WarlockDrainLife, + DefenseType: core.DefenseTypeMagic, + ProcMask: core.ProcMaskSpellDamage, + Flags: WarlockFlagHaunt | core.SpellFlagAPL | core.SpellFlagResetAttackSwing | WarlockFlagAffliction, + RequiredLevel: level, Rank: rank, @@ -44,7 +48,6 @@ func (warlock *Warlock) getDrainLifeBaseConfig(rank int) core.SpellConfig { Cast: core.CastConfig{ DefaultCast: core.Cast{ GCD: core.GCDDefault, - // ChannelTime: channelTime, }, }, @@ -56,59 +59,24 @@ func (warlock *Warlock) getDrainLifeBaseConfig(rank int) core.SpellConfig { Aura: core.Aura{ Label: "DrainLife-" + warlock.Label + strconv.Itoa(rank), }, - NumberOfTicks: ticks, - TickLength: 1 * time.Second, - AffectedByCastSpeed: false, - BonusCoefficient: spellCoeff, + NumberOfTicks: numTicks, + TickLength: 1 * time.Second, + BonusCoefficient: spellCoeff, OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { dot.Snapshot(target, baseDamage, isRollover) if hasSoulSiphonRune { - multiplier := 1.0 - - hasAura := func(target *core.Unit, label string, rank int) bool { - for i := 1; i <= rank; i++ { - if target.HasActiveAura(label + strconv.Itoa(rank)) { - return true - } - } - return false - } - if hasAura(target, "Corruption-"+warlock.Label, 7) { - multiplier += SoulSiphonDoTMultiplier - } - if hasAura(target, "CurseofAgony-"+warlock.Label, 6) { - multiplier += SoulSiphonDoTMultiplier - } - if hasAura(target, "SiphonLife-"+warlock.Label, 3) { - multiplier += SoulSiphonDoTMultiplier - } - if target.HasActiveAura("UnstableAffliction-" + warlock.Label) { - multiplier += SoulSiphonDoTMultiplier - } - if target.HasActiveAura("Haunt-" + warlock.Label) { - multiplier += SoulSiphonDoTMultiplier - } - - dot.SnapshotAttackerMultiplier *= max(multiplier, SoulSiphonDoTMultiplierMax) + dot.SnapshotAttackerMultiplier *= warlock.calcSoulSiphonMultiplier(target, false) } // Drain Life heals so it snapshots target modifiers - dot.SnapshotAttackerMultiplier *= dot.Spell.TargetDamageMultiplier(dot.Spell.Unit.AttackTables[target.UnitIndex][dot.Spell.CastType], true) + // Update 2024-06-29: It no longer snapshots on PTR + // dot.SnapshotAttackerMultiplier *= dot.Spell.TargetDamageMultiplier(dot.Spell.Unit.AttackTables[target.UnitIndex][dot.Spell.CastType], true) }, OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { - // TODO: How does it interact with bonus damage taken? - // Remove target modifiers for the tick only - dot.Spell.Flags |= core.SpellFlagIgnoreTargetModifiers - //dot.Spell.Flags ^= core.SpellFlagBinary - result := dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTickCounted) - // revert flag changes - dot.Spell.Flags ^= core.SpellFlagIgnoreTargetModifiers - //dot.Spell.Flags |= core.SpellFlagBinary - health := result.Damage if hasMasterChannelerRune { health *= 1.5 @@ -149,13 +117,12 @@ func (warlock *Warlock) getDrainLifeBaseConfig(rank int) core.SpellConfig { } func (warlock *Warlock) registerDrainLifeSpell() { - maxRank := 6 - - for i := 1; i <= maxRank; i++ { - config := warlock.getDrainLifeBaseConfig(i) + warlock.DrainLife = make([]*core.Spell, 0) + for rank := 1; rank <= DrainLifeRanks; rank++ { + config := warlock.getDrainLifeBaseConfig(rank) if config.RequiredLevel <= int(warlock.Level) { - warlock.DrainLife = warlock.GetOrRegisterSpell(config) + warlock.DrainLife = append(warlock.DrainLife, warlock.GetOrRegisterSpell(config)) } } } diff --git a/sim/warlock/drain_soul.go b/sim/warlock/drain_soul.go new file mode 100644 index 0000000000..85289fffd2 --- /dev/null +++ b/sim/warlock/drain_soul.go @@ -0,0 +1,97 @@ +package warlock + +import ( + "strconv" + "time" + + "github.com/wowsims/sod/sim/core" + "github.com/wowsims/sod/sim/core/proto" +) + +const DrainSoulRanks = 4 + +func (warlock *Warlock) getDrainSoulBaseConfig(rank int) core.SpellConfig { + hasSoulSiphonRune := warlock.HasRune(proto.WarlockRune_RuneChestSoulSiphon) + + numTicks := core.TernaryInt32(hasSoulSiphonRune, 15, 5) + tickLength := time.Second * time.Duration(core.TernaryInt32(hasSoulSiphonRune, 1, 3)) + + spellId := [DrainSoulRanks + 1]int32{0, 1120, 8288, 8289, 11675}[rank] + spellCoeff := [DrainSoulRanks + 1]float64{0, 0.063, 0.1, 0.1, 0.1}[rank] + baseDamage := [DrainSoulRanks + 1]float64{0, 55, 155, 295, 455}[rank] / float64(numTicks) + manaCost := [DrainSoulRanks + 1]float64{0, 55, 125, 210, 290}[rank] + level := [DrainSoulRanks + 1]int{0, 10, 24, 38, 52}[rank] + + return core.SpellConfig{ + SpellCode: SpellCode_WarlockDrainSoul, + ActionID: core.ActionID{SpellID: spellId}, + SpellSchool: core.SpellSchoolShadow, + DefenseType: core.DefenseTypeMagic, + ProcMask: core.ProcMaskSpellDamage, + Flags: WarlockFlagHaunt | core.SpellFlagAPL | core.SpellFlagChanneled | core.SpellFlagResetAttackSwing | WarlockFlagAffliction, + + RequiredLevel: level, + Rank: rank, + + ManaCost: core.ManaCostOptions{ + FlatCost: manaCost, + }, + Cast: core.CastConfig{ + DefaultCast: core.Cast{ + GCD: core.GCDDefault, + }, + }, + + DamageMultiplier: 1, + ThreatMultiplier: 1, + + Dot: core.DotConfig{ + Aura: core.Aura{ + Label: "DrainSoul-" + warlock.Label + strconv.Itoa(rank), + }, + NumberOfTicks: numTicks, + TickLength: tickLength, + BonusCoefficient: spellCoeff, + + OnSnapshot: func(sim *core.Simulation, target *core.Unit, dot *core.Dot, isRollover bool) { + dot.Snapshot(target, baseDamage, isRollover) + + if hasSoulSiphonRune { + dot.SnapshotAttackerMultiplier *= warlock.calcSoulSiphonMultiplier(target, sim.IsExecutePhase20()) + } + }, + OnTick: func(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + dot.CalcAndDealPeriodicSnapshotDamage(sim, target, dot.OutcomeTickCounted) + }, + }, + + ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { + result := spell.CalcOutcome(sim, target, spell.OutcomeMagicHit) + if result.Landed() { + spell.SpellMetrics[target.UnitIndex].Hits-- + + dot := spell.Dot(target) + dot.Apply(sim) + } + }, + ExpectedTickDamage: func(sim *core.Simulation, target *core.Unit, spell *core.Spell, useSnapshot bool) *core.SpellResult { + if useSnapshot { + dot := spell.Dot(target) + return dot.CalcSnapshotDamage(sim, target, spell.OutcomeExpectedMagicAlwaysHit) + } else { + return spell.CalcPeriodicDamage(sim, target, baseDamage, spell.OutcomeExpectedMagicAlwaysHit) + } + }, + } +} + +func (warlock *Warlock) registerDrainSoulSpell() { + warlock.DrainSoul = make([]*core.Spell, 0) + for rank := 1; rank <= DrainSoulRanks; rank++ { + config := warlock.getDrainSoulBaseConfig(rank) + + if config.RequiredLevel <= int(warlock.Level) { + warlock.DrainSoul = append(warlock.DrainSoul, warlock.GetOrRegisterSpell(config)) + } + } +} diff --git a/sim/warlock/haunt.go b/sim/warlock/haunt.go index 5b1e3c0001..0d135bb1d9 100644 --- a/sim/warlock/haunt.go +++ b/sim/warlock/haunt.go @@ -7,6 +7,10 @@ import ( "github.com/wowsims/sod/sim/core/proto" ) +func hauntMultiplier(spell *core.Spell, _ *core.AttackTable) float64 { + return core.TernaryFloat64(spell.Flags.Matches(WarlockFlagHaunt), 1.2, 1) +} + func (warlock *Warlock) registerHauntSpell() { if !warlock.HasRune(proto.WarlockRune_RuneHandsHaunt) { return @@ -24,10 +28,10 @@ func (warlock *Warlock) registerHauntSpell() { ActionID: actionID, Duration: time.Second * 12, OnGain: func(aura *core.Aura, sim *core.Simulation) { - warlock.AttackTables[aura.Unit.UnitIndex][proto.CastType_CastTypeMainHand].HauntSEDamageTakenMultiplier *= 1.2 + warlock.AttackTables[aura.Unit.UnitIndex][proto.CastType_CastTypeMainHand].DamageDoneByCasterMultiplier = hauntMultiplier }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { - warlock.AttackTables[aura.Unit.UnitIndex][proto.CastType_CastTypeMainHand].HauntSEDamageTakenMultiplier /= 1.2 + warlock.AttackTables[aura.Unit.UnitIndex][proto.CastType_CastTypeMainHand].DamageDoneByCasterMultiplier = nil }, }) }) diff --git a/sim/warlock/immolate.go b/sim/warlock/immolate.go index 42e987d127..37abb14160 100644 --- a/sim/warlock/immolate.go +++ b/sim/warlock/immolate.go @@ -8,14 +8,16 @@ import ( "github.com/wowsims/sod/sim/core/proto" ) +const ImmolateRanks = 8 + func (warlock *Warlock) getImmolateConfig(rank int) core.SpellConfig { - directCoeff := [9]float64{0, .058, .125, .2, .2, .2, .2, .2, .2}[rank] - dotCoeff := [9]float64{0, .037, .081, .13, .13, .13, .13, .13, .13}[rank] - baseDamage := [9]float64{0, 11, 24, 53, 101, 148, 208, 258, 279}[rank] - dotDamage := [9]float64{0, 20, 40, 90, 165, 255, 365, 485, 510}[rank] / 5 - spellId := [9]int32{0, 348, 707, 1094, 2941, 11665, 11667, 11668, 25309}[rank] - manaCost := [9]float64{0, 25, 45, 90, 155, 220, 295, 370, 380}[rank] - level := [9]int{0, 1, 10, 20, 30, 40, 50, 60, 60}[rank] + directCoeff := [ImmolateRanks + 1]float64{0, .058, .125, .2, .2, .2, .2, .2, .2}[rank] + dotCoeff := [ImmolateRanks + 1]float64{0, .037, .081, .13, .13, .13, .13, .13, .13}[rank] + baseDamage := [ImmolateRanks + 1]float64{0, 11, 24, 53, 101, 148, 208, 258, 279}[rank] + dotDamage := [ImmolateRanks + 1]float64{0, 20, 40, 90, 165, 255, 365, 485, 510}[rank] / 5 + spellId := [ImmolateRanks + 1]int32{0, 348, 707, 1094, 2941, 11665, 11667, 11668, 25309}[rank] + manaCost := [ImmolateRanks + 1]float64{0, 25, 45, 90, 155, 220, 295, 370, 380}[rank] + level := [ImmolateRanks + 1]int{0, 1, 10, 20, 30, 40, 50, 60, 60}[rank] hasInvocationRune := warlock.HasRune(proto.WarlockRune_RuneBeltInvocation) hasPandemicRune := warlock.HasRune(proto.WarlockRune_RuneHelmPandemic) @@ -130,11 +132,9 @@ func (warlock *Warlock) getActiveImmolateSpell(target *core.Unit) *core.Spell { } func (warlock *Warlock) registerImmolateSpell() { - maxRank := 8 - warlock.Immolate = make([]*core.Spell, 0) - for i := 1; i <= maxRank; i++ { - config := warlock.getImmolateConfig(i) + for rank := 1; rank <= ImmolateRanks; rank++ { + config := warlock.getImmolateConfig(rank) if config.RequiredLevel <= int(warlock.Level) { warlock.Immolate = append(warlock.Immolate, warlock.GetOrRegisterSpell(config)) diff --git a/sim/warlock/item_sets_pve.go b/sim/warlock/item_sets_pve.go index 10a5e59be1..d0c5a77ac3 100644 --- a/sim/warlock/item_sets_pve.go +++ b/sim/warlock/item_sets_pve.go @@ -116,15 +116,15 @@ var ItemSetCorruptedFelheart = core.NewItemSet(core.ItemSet{ aura.Activate(sim) }, OnGain: func(aura *core.Aura, sim *core.Simulation) { - if warlock.LifeTap != nil { - warlock.LifeTap.DamageMultiplier *= 1.5 - warlock.LifeTap.ThreatMultiplier *= -1 + for _, spell := range warlock.LifeTap { + spell.DamageMultiplier *= 1.5 + spell.ThreatMultiplier *= -1 } }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { - if warlock.LifeTap != nil { - warlock.LifeTap.DamageMultiplier /= 1.5 - warlock.LifeTap.ThreatMultiplier *= -1 + for _, spell := range warlock.LifeTap { + spell.DamageMultiplier /= 1.5 + spell.ThreatMultiplier *= -1 } }, }) @@ -150,9 +150,7 @@ var ItemSetCorruptedFelheart = core.NewItemSet(core.ItemSet{ Duration: time.Second * 10, OnGain: func(aura *core.Aura, sim *core.Simulation) { for _, spell := range warlock.Immolate { - if spell != nil { - spell.CastTimeMultiplier -= 1 - } + spell.CastTimeMultiplier -= 1 } if warlock.Shadowflame != nil { warlock.Shadowflame.CastTimeMultiplier -= 1 @@ -160,9 +158,7 @@ var ItemSetCorruptedFelheart = core.NewItemSet(core.ItemSet{ }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { for _, spell := range warlock.Immolate { - if spell != nil { - spell.CastTimeMultiplier += 1 - } + spell.CastTimeMultiplier += 1 } if warlock.Shadowflame != nil { warlock.Shadowflame.CastTimeMultiplier += 1 @@ -238,16 +234,12 @@ var ItemSetWickedFelheart = core.NewItemSet(core.ItemSet{ Duration: time.Second * 10, OnGain: func(aura *core.Aura, sim *core.Simulation) { for _, spell := range warlock.SoulFire { - if spell != nil { - spell.CastTimeMultiplier -= 1 - } + spell.CastTimeMultiplier -= 1 } }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { for _, spell := range warlock.SoulFire { - if spell != nil { - spell.CastTimeMultiplier += 1 - } + spell.CastTimeMultiplier += 1 } }, OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { diff --git a/sim/warlock/item_sets_pvp.go b/sim/warlock/item_sets_pvp.go index 3685014a6e..5141444f06 100644 --- a/sim/warlock/item_sets_pvp.go +++ b/sim/warlock/item_sets_pvp.go @@ -31,16 +31,12 @@ var ItemSetChampionsThreads = core.NewItemSet(core.ItemSet{ }, OnGain: func(aura *core.Aura, sim *core.Simulation) { for _, spell := range warlock.Immolate { - if spell != nil { - spell.DefaultCast.CastTime -= time.Millisecond * 200 - } + spell.DefaultCast.CastTime -= time.Millisecond * 200 } }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { for _, spell := range warlock.Immolate { - if spell != nil { - spell.DefaultCast.CastTime += time.Millisecond * 200 - } + spell.DefaultCast.CastTime += time.Millisecond * 200 } }, }) @@ -73,16 +69,12 @@ var ItemSetLieutenantCommandersThreads = core.NewItemSet(core.ItemSet{ }, OnGain: func(aura *core.Aura, sim *core.Simulation) { for _, spell := range warlock.Immolate { - if spell != nil { - spell.DefaultCast.CastTime -= time.Millisecond * 200 - } + spell.DefaultCast.CastTime -= time.Millisecond * 200 } }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { for _, spell := range warlock.Immolate { - if spell != nil { - spell.DefaultCast.CastTime += time.Millisecond * 200 - } + spell.DefaultCast.CastTime += time.Millisecond * 200 } }, }) diff --git a/sim/warlock/lifetap.go b/sim/warlock/lifetap.go index 07dad9ee24..5687f8ee70 100644 --- a/sim/warlock/lifetap.go +++ b/sim/warlock/lifetap.go @@ -4,10 +4,12 @@ import ( "github.com/wowsims/sod/sim/core" ) +const LifeTapRanks = 6 + func (warlock *Warlock) getLifeTapBaseConfig(rank int) core.SpellConfig { - spellId := [7]int32{0, 1454, 1455, 1456, 11687, 11688, 11689}[rank] - baseDamage := [7]float64{0, 30, 75, 140, 220, 310, 424}[rank] - level := [7]int{0, 6, 16, 26, 36, 46, 56}[rank] + spellId := [LifeTapRanks + 1]int32{0, 1454, 1455, 1456, 11687, 11688, 11689}[rank] + baseDamage := [LifeTapRanks + 1]float64{0, 30, 75, 140, 220, 310, 424}[rank] + level := [LifeTapRanks + 1]int{0, 6, 16, 26, 36, 46, 56}[rank] actionID := core.ActionID{SpellID: spellId} var manaPetMetrics *core.ResourceMetrics @@ -58,13 +60,12 @@ func (warlock *Warlock) getLifeTapBaseConfig(rank int) core.SpellConfig { } func (warlock *Warlock) registerLifeTapSpell() { - maxRank := 6 - - for i := 1; i <= maxRank; i++ { + warlock.LifeTap = make([]*core.Spell, 0) + for i := 1; i <= LifeTapRanks; i++ { config := warlock.getLifeTapBaseConfig(i) if config.RequiredLevel <= int(warlock.Level) { - warlock.LifeTap = warlock.GetOrRegisterSpell(config) + warlock.LifeTap = append(warlock.LifeTap, warlock.GetOrRegisterSpell(config)) } } } diff --git a/sim/warlock/rain_of_fire.go b/sim/warlock/rain_of_fire.go index ce603adf98..acef62052d 100644 --- a/sim/warlock/rain_of_fire.go +++ b/sim/warlock/rain_of_fire.go @@ -8,14 +8,16 @@ import ( "github.com/wowsims/sod/sim/core/proto" ) +const RainOfFireRanks = 4 + func (warlock *Warlock) getRainOfFireBaseConfig(rank int) core.SpellConfig { hasLakeOfFireRune := warlock.HasRune(proto.WarlockRune_RuneChestLakeOfFire) - spellId := [5]int32{0, 5740, 6219, 11677, 11678}[rank] - spellCoeff := [5]float64{0, 0.083, 0.083, 0.083, 0.083}[rank] - baseDamage := [5]float64{0, 42, 92, 155, 226}[rank] - manaCost := [5]float64{0, 295, 605, 885, 1185}[rank] - level := [5]int{0, 20, 34, 46, 58}[rank] + spellId := [RainOfFireRanks + 1]int32{0, 5740, 6219, 11677, 11678}[rank] + spellCoeff := [RainOfFireRanks + 1]float64{0, 0.083, 0.083, 0.083, 0.083}[rank] + baseDamage := [RainOfFireRanks + 1]float64{0, 42, 92, 155, 226}[rank] + manaCost := [RainOfFireRanks + 1]float64{0, 295, 605, 885, 1185}[rank] + level := [RainOfFireRanks + 1]int{0, 20, 34, 46, 58}[rank] flags := core.SpellFlagAPL | core.SpellFlagResetAttackSwing | WarlockFlagDestruction if !hasLakeOfFireRune { @@ -78,13 +80,12 @@ func (warlock *Warlock) getRainOfFireBaseConfig(rank int) core.SpellConfig { } func (warlock *Warlock) registerRainOfFireSpell() { - maxRank := 4 - - for i := 1; i <= maxRank; i++ { - config := warlock.getRainOfFireBaseConfig(i) + warlock.RainOfFire = make([]*core.Spell, 0) + for rank := 1; rank <= RainOfFireRanks; rank++ { + config := warlock.getRainOfFireBaseConfig(rank) if config.RequiredLevel <= int(warlock.Level) { - warlock.RainOfFire = warlock.GetOrRegisterSpell(config) + warlock.RainOfFire = append(warlock.RainOfFire, warlock.GetOrRegisterSpell(config)) } } } diff --git a/sim/warlock/runes.go b/sim/warlock/runes.go index dcd2ece40e..e18b852adf 100644 --- a/sim/warlock/runes.go +++ b/sim/warlock/runes.go @@ -17,6 +17,7 @@ func (warlock *Warlock) ApplyRunes() { // Cloak Runes warlock.applyDecimation() + warlock.applyMarkOfChaos() // Chest Runes warlock.applyDemonicTactics() @@ -123,16 +124,12 @@ func (warlock *Warlock) applyDecimation() { }, OnGain: func(aura *core.Aura, sim *core.Simulation) { for _, spell := range warlock.SoulFire { - if spell != nil { - spell.CastTimeMultiplier *= .6 - } + spell.CastTimeMultiplier *= .6 } }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { for _, spell := range warlock.SoulFire { - if spell != nil { - spell.CastTimeMultiplier /= .6 - } + spell.CastTimeMultiplier /= .6 } }, }) @@ -152,6 +149,23 @@ func (warlock *Warlock) applyDecimation() { }) } +func (warlock *Warlock) applyMarkOfChaos() { + if !warlock.HasRune(proto.WarlockRune_RuneCloakMarkOfChaos) { + return + } + + warlock.MarkOfChaosAuras = warlock.NewEnemyAuraArray(func(target *core.Unit, _ int32) *core.Aura { + return core.MarkOfChaosDebuffAura(target) + }) +} + +func (warlock *Warlock) applyMarkOfChaosDebuff(sim *core.Simulation, target *core.Unit, dot *core.Dot) { + aura := warlock.MarkOfChaosAuras.Get(target) + aura.Duration = dot.Duration + aura.UpdateExpires(sim, dot.ExpiresAt()) + aura.Activate(sim) +} + func (warlock *Warlock) InvocationRefresh(sim *core.Simulation, dot *core.Dot) { if dot.RemainingDuration(sim) < time.Second*6 { ticksLeft := dot.NumberOfTicks - dot.TickCount @@ -373,6 +387,47 @@ const SoulSiphonDoTMultiplierExecute = 0.50 const SoulSiphonDoTMultiplierMax = 0.18 const SoulSiphonDoTMultiplierMaxExecute = 1.50 +func (warlock *Warlock) calcSoulSiphonMultiplier(target *core.Unit, executeBonus bool) float64 { + multiplier := 1.0 + perDoTMultiplier := core.TernaryFloat64(executeBonus, SoulSiphonDoTMultiplier, SoulSiphonDoTMultiplierExecute) + maxMultiplier := 1 + core.TernaryFloat64(executeBonus, SoulSiphonDoTMultiplierMax, SoulSiphonDoTMultiplierMaxExecute) + + for _, spell := range warlock.Corruption { + if spell.Dot(target).IsActive() { + multiplier += perDoTMultiplier + break + } + } + + for _, spell := range warlock.CurseOfAgony { + if spell.Dot(target).IsActive() { + multiplier += perDoTMultiplier + break + } + } + + if warlock.CurseOfDoom != nil && warlock.CurseOfDoom.Dot(target).IsActive() { + multiplier += perDoTMultiplier + } + + for _, spell := range warlock.SiphonLife { + if spell.Dot(target).IsActive() { + multiplier += perDoTMultiplier + break + } + } + + if warlock.UnstableAffliction != nil && warlock.UnstableAffliction.Dot(target).IsActive() { + multiplier += perDoTMultiplier + } + + if warlock.Haunt != nil && warlock.HauntDebuffAuras.Get(target).IsActive() { + multiplier += perDoTMultiplier + } + + return max(multiplier, maxMultiplier) +} + func (warlock *Warlock) applyDemonicTactics() { if !warlock.HasRune(proto.WarlockRune_RuneChestDemonicTactics) { return diff --git a/sim/warlock/searing_pain.go b/sim/warlock/searing_pain.go index c8318b38fc..95d64392e1 100644 --- a/sim/warlock/searing_pain.go +++ b/sim/warlock/searing_pain.go @@ -6,12 +6,14 @@ import ( "github.com/wowsims/sod/sim/core" ) +const SearingPainRanks = 6 + func (warlock *Warlock) getSearingPainBaseConfig(rank int) core.SpellConfig { - spellCoeff := [7]float64{0, .396, .429, .429, .429, .429, .429}[rank] - baseDamage := [7][]float64{{0}, {38, 47}, {65, 77}, {93, 112}, {131, 155}, {168, 199}, {208, 244}}[rank] - spellId := [7]int32{0, 5676, 17919, 17920, 17921, 17922, 17923}[rank] - manaCost := [7]float64{0, 45, 68, 91, 118, 141, 168}[rank] - level := [7]int{0, 18, 26, 36, 42, 50, 58}[rank] + spellCoeff := [SearingPainRanks + 1]float64{0, .396, .429, .429, .429, .429, .429}[rank] + baseDamage := [SearingPainRanks + 1][]float64{{0}, {38, 47}, {65, 77}, {93, 112}, {131, 155}, {168, 199}, {208, 244}}[rank] + spellId := [SearingPainRanks + 1]int32{0, 5676, 17919, 17920, 17921, 17922, 17923}[rank] + manaCost := [SearingPainRanks + 1]float64{0, 45, 68, 91, 118, 141, 168}[rank] + level := [SearingPainRanks + 1]int{0, 18, 26, 36, 42, 50, 58}[rank] castTime := time.Millisecond * 1500 return core.SpellConfig{ @@ -54,13 +56,12 @@ func (warlock *Warlock) getSearingPainBaseConfig(rank int) core.SpellConfig { } func (warlock *Warlock) registerSearingPainSpell() { - maxRank := 6 - - for i := 1; i <= maxRank; i++ { - config := warlock.getSearingPainBaseConfig(i) + warlock.SearingPain = make([]*core.Spell, 0) + for rank := 1; rank <= SearingPainRanks; rank++ { + config := warlock.getSearingPainBaseConfig(rank) if config.RequiredLevel <= int(warlock.Level) { - warlock.SearingPain = warlock.GetOrRegisterSpell(config) + warlock.SearingPain = append(warlock.SearingPain, warlock.GetOrRegisterSpell(config)) } } } diff --git a/sim/warlock/shadowbolt.go b/sim/warlock/shadowbolt.go index b22299d273..ef58df2cee 100644 --- a/sim/warlock/shadowbolt.go +++ b/sim/warlock/shadowbolt.go @@ -7,13 +7,15 @@ import ( "github.com/wowsims/sod/sim/core/proto" ) +const ShadowBoltRanks = 10 + func (warlock *Warlock) getShadowBoltBaseConfig(rank int) core.SpellConfig { - spellCoeff := [11]float64{0, .14, .299, .56, .857, .857, .857, .857, .857, .857, .857}[rank] - baseDamage := [11][]float64{{0}, {13, 18}, {26, 32}, {52, 61}, {92, 104}, {150, 170}, {213, 240}, {292, 327}, {373, 415}, {455, 507}, {482, 538}}[rank] - spellId := [11]int32{0, 686, 695, 705, 1088, 1106, 7641, 11659, 11660, 11661, 25307}[rank] - manaCost := [11]float64{0, 25, 40, 70, 110, 160, 210, 265, 315, 370, 380}[rank] - level := [11]int{0, 1, 6, 12, 20, 28, 36, 44, 52, 60, 60}[rank] - castTime := [11]int32{0, 1700, 2200, 2800, 3000, 3000, 3000, 3000, 3000, 3000, 3000}[rank] + spellCoeff := [ShadowBoltRanks + 1]float64{0, .14, .299, .56, .857, .857, .857, .857, .857, .857, .857}[rank] + baseDamage := [ShadowBoltRanks + 1][]float64{{0}, {13, 18}, {26, 32}, {52, 61}, {92, 104}, {150, 170}, {213, 240}, {292, 327}, {373, 415}, {455, 507}, {482, 538}}[rank] + spellId := [ShadowBoltRanks + 1]int32{0, 686, 695, 705, 1088, 1106, 7641, 11659, 11660, 11661, 25307}[rank] + manaCost := [ShadowBoltRanks + 1]float64{0, 25, 40, 70, 110, 160, 210, 265, 315, 370, 380}[rank] + level := [ShadowBoltRanks + 1]int{0, 1, 6, 12, 20, 28, 36, 44, 52, 60, 60}[rank] + castTime := [ShadowBoltRanks + 1]int32{0, 1700, 2200, 2800, 3000, 3000, 3000, 3000, 3000, 3000, 3000}[rank] shadowboltVolley := warlock.HasRune(proto.WarlockRune_RuneHandsShadowBoltVolley) damageMulti := core.TernaryFloat64(shadowboltVolley, 0.8, 1.0) @@ -62,13 +64,13 @@ func (warlock *Warlock) getShadowBoltBaseConfig(rank int) core.SpellConfig { } func (warlock *Warlock) registerShadowBoltSpell() { - maxRank := 10 + warlock.ShadowBolt = make([]*core.Spell, 0) - for i := 1; i <= maxRank; i++ { - config := warlock.getShadowBoltBaseConfig(i) + for rank := 1; rank <= ShadowBoltRanks; rank++ { + config := warlock.getShadowBoltBaseConfig(rank) if config.RequiredLevel <= int(warlock.Level) { - warlock.ShadowBolt = warlock.GetOrRegisterSpell(config) + warlock.ShadowBolt = append(warlock.ShadowBolt, warlock.GetOrRegisterSpell(config)) } } } diff --git a/sim/warlock/shadowburn.go b/sim/warlock/shadowburn.go index 986ca169c7..27c6dba840 100644 --- a/sim/warlock/shadowburn.go +++ b/sim/warlock/shadowburn.go @@ -6,11 +6,13 @@ import ( "github.com/wowsims/sod/sim/core" ) +const ShadowburnRanks = 6 + func (warlock *Warlock) registerShadowBurnBaseConfig(rank int) core.SpellConfig { - spellId := [7]int32{0, 17877, 18867, 18868, 18869, 18870, 18871}[rank] - baseDamage := [7][]float64{{0}, {91, 104}, {123, 140}, {196, 221}, {274, 307}, {365, 408}, {462, 514}}[rank] - manaCost := [7]float64{0, 105, 130, 190, 245, 305, 365}[rank] - level := [7]int{0, 15, 24, 32, 40, 48, 56}[rank] + spellId := [ShadowburnRanks + 1]int32{0, 17877, 18867, 18868, 18869, 18870, 18871}[rank] + baseDamage := [ShadowburnRanks + 1][]float64{{0}, {91, 104}, {123, 140}, {196, 221}, {274, 307}, {365, 408}, {462, 514}}[rank] + manaCost := [ShadowburnRanks + 1]float64{0, 105, 130, 190, 245, 305, 365}[rank] + level := [ShadowburnRanks + 1]int{0, 15, 24, 32, 40, 48, 56}[rank] spellCoeff := 0.429 @@ -52,13 +54,12 @@ func (warlock *Warlock) registerShadowBurnSpell() { return } - maxRank := 6 - - for i := 1; i <= maxRank; i++ { - config := warlock.registerShadowBurnBaseConfig(i) + warlock.Shadowburn = make([]*core.Spell, 0) + for rank := 1; rank <= ShadowburnRanks; rank++ { + config := warlock.registerShadowBurnBaseConfig(rank) if config.RequiredLevel <= int(warlock.Level) { - warlock.Shadowburn = warlock.GetOrRegisterSpell(config) + warlock.Shadowburn = append(warlock.Shadowburn, warlock.GetOrRegisterSpell(config)) } } } diff --git a/sim/warlock/siphon_life.go b/sim/warlock/siphon_life.go index 19c1ab8211..9e8052441f 100644 --- a/sim/warlock/siphon_life.go +++ b/sim/warlock/siphon_life.go @@ -8,11 +8,13 @@ import ( "github.com/wowsims/sod/sim/core/proto" ) +const SiphonLifeRanks = 4 + func (warlock *Warlock) getSiphonLifeBaseConfig(rank int) core.SpellConfig { - spellId := [5]int32{0, 18265, 18879, 18880, 18881}[rank] - baseDamage := [5]float64{0, 15, 22, 33, 45}[rank] - manaCost := [5]float64{0, 150, 205, 285, 365}[rank] - level := [5]int{0, 0, 38, 48, 58}[rank] + spellId := [SiphonLifeRanks + 1]int32{0, 18265, 18879, 18880, 18881}[rank] + baseDamage := [SiphonLifeRanks + 1]float64{0, 15, 22, 33, 45}[rank] + manaCost := [SiphonLifeRanks + 1]float64{0, 150, 205, 285, 365}[rank] + level := [SiphonLifeRanks + 1]int{0, 0, 38, 48, 58}[rank] spellCoeff := 0.05 actionID := core.ActionID{SpellID: spellId} @@ -28,7 +30,7 @@ func (warlock *Warlock) getSiphonLifeBaseConfig(rank int) core.SpellConfig { SpellSchool: core.SpellSchoolShadow, DefenseType: core.DefenseTypeMagic, ProcMask: core.ProcMaskSpellDamage, - Flags: core.SpellFlagHauntSE | core.SpellFlagAPL | core.SpellFlagResetAttackSwing | core.SpellFlagBinary | WarlockFlagAffliction, + Flags: WarlockFlagHaunt | core.SpellFlagAPL | core.SpellFlagResetAttackSwing | core.SpellFlagBinary | WarlockFlagAffliction, RequiredLevel: level, Rank: rank, @@ -112,13 +114,12 @@ func (warlock *Warlock) registerSiphonLifeSpell() { return } - maxRank := 4 - - for i := 1; i <= maxRank; i++ { - config := warlock.getSiphonLifeBaseConfig(i) + warlock.SiphonLife = make([]*core.Spell, 0) + for rank := 1; rank <= SiphonLifeRanks; rank++ { + config := warlock.getSiphonLifeBaseConfig(rank) if config.RequiredLevel <= int(warlock.Level) { - warlock.SiphonLife = warlock.GetOrRegisterSpell(config) + warlock.SiphonLife = append(warlock.SiphonLife, warlock.GetOrRegisterSpell(config)) } } } diff --git a/sim/warlock/soul_fire.go b/sim/warlock/soul_fire.go index b873c1ccf7..3c92ae0b04 100644 --- a/sim/warlock/soul_fire.go +++ b/sim/warlock/soul_fire.go @@ -7,13 +7,15 @@ import ( "github.com/wowsims/sod/sim/core/proto" ) +const SoulFireRanks = 2 + func (warlock *Warlock) getSoulFireBaseConfig(rank int) core.SpellConfig { hasDecimationRune := warlock.HasRune(proto.WarlockRune_RuneCloakDecimation) - spellId := [3]int32{0, 6353, 17924}[rank] - baseDamage := [3][]float64{{0, 0}, {628, 789}, {715, 894}}[rank] - manaCost := [3]float64{0, 305, 335}[rank] - level := [3]int{0, 48, 56}[rank] + spellId := [SoulFireRanks + 1]int32{0, 6353, 17924}[rank] + baseDamage := [SoulFireRanks + 1][]float64{{0, 0}, {628, 789}, {715, 894}}[rank] + manaCost := [SoulFireRanks + 1]float64{0, 305, 335}[rank] + level := [SoulFireRanks + 1]int{0, 48, 56}[rank] spellCoeff := 1.0 config := core.SpellConfig{ @@ -61,14 +63,12 @@ func (warlock *Warlock) getSoulFireBaseConfig(rank int) core.SpellConfig { } func (warlock *Warlock) registerSoulFireSpell() { - maxRank := 2 - warlock.SoulFire = make([]*core.Spell, maxRank+1) - - for i := 1; i <= maxRank; i++ { - config := warlock.getSoulFireBaseConfig(i) + warlock.SoulFire = make([]*core.Spell, 0) + for rank := 1; rank <= SoulFireRanks; rank++ { + config := warlock.getSoulFireBaseConfig(rank) if config.RequiredLevel <= int(warlock.Level) { - warlock.SoulFire[i] = warlock.GetOrRegisterSpell(config) + warlock.SoulFire = append(warlock.SoulFire, warlock.GetOrRegisterSpell(config)) } } } diff --git a/sim/warlock/talents.go b/sim/warlock/talents.go index 524798c339..45c0087c02 100644 --- a/sim/warlock/talents.go +++ b/sim/warlock/talents.go @@ -147,6 +147,7 @@ func (warlock *Warlock) applyNightfall() { return } + hasSoulSiphonRune := warlock.HasRune(proto.WarlockRune_RuneChestSoulSiphon) has6PCorruptedFelheart := warlock.HasSetBonus(ItemSetCorruptedFelheart, 6) nightfallProcChance := 0.02 * float64(warlock.Talents.Nightfall) @@ -159,14 +160,22 @@ func (warlock *Warlock) applyNightfall() { ActionID: core.ActionID{SpellID: 17941}, Duration: time.Second * 10, OnGain: func(aura *core.Aura, sim *core.Simulation) { - warlock.ShadowBolt.CastTimeMultiplier -= 1 + for _, spell := range warlock.ShadowBolt { + spell.CastTimeMultiplier -= 1 + } + + for _, spell := range warlock.ShadowCleave { + spell.CD.Reset() + } }, OnExpire: func(aura *core.Aura, sim *core.Simulation) { - warlock.ShadowBolt.CastTimeMultiplier += 1 + for _, spell := range warlock.ShadowBolt { + spell.CastTimeMultiplier += 1 + } }, OnCastComplete: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell) { // Check if the shadowbolt was instant cast and not a normal one - if spell == warlock.ShadowBolt && spell.CurCast.CastTime == 0 { + if spell.SpellCode == SpellCode_WarlockShadowBolt && spell.CurCast.CastTime == 0 { aura.Deactivate(sim) } }, @@ -179,14 +188,8 @@ func (warlock *Warlock) applyNightfall() { aura.Activate(sim) }, OnPeriodicDamageDealt: func(aura *core.Aura, sim *core.Simulation, spell *core.Spell, result *core.SpellResult) { - if spell.SpellCode == SpellCode_WarlockCorruption || spell.SpellCode == SpellCode_WarlockDrainLife { - if sim.Proc(nightfallProcChance, "Nightfall") { - warlock.NightfallProcAura.Activate(sim) - - for _, spell := range warlock.ShadowCleave { - spell.CD.Reset() - } - } + if (spell.SpellCode == SpellCode_WarlockCorruption || spell.SpellCode == SpellCode_WarlockDrainLife || (hasSoulSiphonRune && spell.SpellCode == SpellCode_WarlockDrainSoul)) && sim.Proc(nightfallProcChance, "Nightfall") { + warlock.NightfallProcAura.Activate(sim) } }, }) @@ -199,7 +202,7 @@ func (warlock *Warlock) applyShadowMastery() { warlock.OnSpellRegistered(func(spell *core.Spell) { // Shadow Mastery applies a base damage modifier to all dots / channeled spells instead - if spell.SpellSchool.Matches(core.SpellSchoolShadow) && isWarlockSpell(spell) && !spell.Flags.Matches(core.SpellFlagPureDot) && !spell.Flags.Matches(core.SpellFlagHauntSE) { + if spell.SpellSchool.Matches(core.SpellSchoolShadow) && isWarlockSpell(spell) && !spell.Flags.Matches(core.SpellFlagPureDot) && !spell.Flags.Matches(WarlockFlagHaunt) { spell.DamageMultiplierAdditive += warlock.shadowMasteryBonus() } }) diff --git a/sim/warlock/unstable_affliction.go b/sim/warlock/unstable_affliction.go index dc11f05988..b649af91cf 100644 --- a/sim/warlock/unstable_affliction.go +++ b/sim/warlock/unstable_affliction.go @@ -23,7 +23,7 @@ func (warlock *Warlock) registerUnstableAfflictionSpell() { SpellSchool: core.SpellSchoolShadow, ProcMask: core.ProcMaskSpellDamage, DefenseType: core.DefenseTypeMagic, - Flags: core.SpellFlagAPL | core.SpellFlagHauntSE | core.SpellFlagBinary | core.SpellFlagResetAttackSwing | core.SpellFlagPureDot | WarlockFlagAffliction, + Flags: core.SpellFlagAPL | WarlockFlagHaunt | core.SpellFlagBinary | core.SpellFlagResetAttackSwing | core.SpellFlagPureDot | WarlockFlagAffliction, ManaCost: core.ManaCostOptions{ BaseCost: 0.15, diff --git a/sim/warlock/warlock.go b/sim/warlock/warlock.go index 587277c778..9912a9d11c 100644 --- a/sim/warlock/warlock.go +++ b/sim/warlock/warlock.go @@ -14,12 +14,15 @@ const ( WarlockFlagAffliction = core.SpellFlagAgentReserved1 WarlockFlagDemonology = core.SpellFlagAgentReserved2 WarlockFlagDestruction = core.SpellFlagAgentReserved3 + WarlockFlagHaunt = core.SpellFlagAgentReserved4 ) const ( SpellCode_WarlockNone int32 = iota SpellCode_WarlockCorruption + SpellCode_WarlockCurseOfAgony + SpellCode_WarlockCurseOfDoom SpellCode_WarlockDrainLife SpellCode_WarlockDrainSoul SpellCode_WarlockHaunt @@ -40,26 +43,27 @@ type Warlock struct { Pet *WarlockPet ChaosBolt *core.Spell - Conflagrate *core.Spell + Conflagrate []*core.Spell Corruption []*core.Spell DarkPact *core.Spell - DrainSoul *core.Spell + DrainSoul []*core.Spell Haunt *core.Spell Immolate []*core.Spell Incinerate *core.Spell - LifeTap *core.Spell - SearingPain *core.Spell - ShadowBolt *core.Spell + LifeTap []*core.Spell + SearingPain []*core.Spell + ShadowBolt []*core.Spell ShadowCleave []*core.Spell - Shadowburn *core.Spell + Shadowburn []*core.Spell SoulFire []*core.Spell DemonicGrace *core.Spell - DrainLife *core.Spell - RainOfFire *core.Spell - SiphonLife *core.Spell - DeathCoil *core.Spell + DrainLife []*core.Spell + RainOfFire []*core.Spell + SiphonLife []*core.Spell + DeathCoil []*core.Spell UnstableAffliction *core.Spell + ActiveCurseAura *core.Aura CurseOfElements *core.Spell CurseOfElementsAuras core.AuraArray CurseOfShadow *core.Spell @@ -70,7 +74,7 @@ type Warlock struct { CurseOfWeaknessAuras core.AuraArray CurseOfTongues *core.Spell CurseOfTonguesAuras core.AuraArray - CurseOfAgony *core.Spell + CurseOfAgony []*core.Spell CurseOfDoom *core.Spell AmplifyCurse *core.Spell Shadowflame *core.Spell @@ -88,6 +92,7 @@ type Warlock struct { AmplifyCurseAura *core.Aura BackdraftAura *core.Aura ImprovedShadowBoltAuras core.AuraArray + MarkOfChaosAuras core.AuraArray // The sum total of demonic pact spell power * seconds. DPSPAggregate float64 @@ -119,7 +124,7 @@ func (warlock *Warlock) Initialize() { warlock.registerSoulFireSpell() warlock.registerUnstableAfflictionSpell() // warlock.registerSeedSpell() - // warlock.registerDrainSoulSpell() + warlock.registerDrainSoulSpell() warlock.registerConflagrateSpell() warlock.registerHauntSpell() warlock.registerSiphonLifeSpell() @@ -140,7 +145,7 @@ func (warlock *Warlock) Initialize() { warlock.registerCurseOfRecklessnessSpell() warlock.registerCurseOfAgonySpell() warlock.registerAmplifyCurseSpell() - // warlock.registerCurseOfDoomSpell() + warlock.registerCurseOfDoomSpell() warlock.registerImmolationAuraSpell() } diff --git a/ui/warlock/apls/p4/affliction.apl.json b/ui/warlock/apls/p4/affliction.apl.json index a9da5a77bb..6c5b58c05c 100644 --- a/ui/warlock/apls/p4/affliction.apl.json +++ b/ui/warlock/apls/p4/affliction.apl.json @@ -1,24 +1,25 @@ { - "type": "TypeAPL", - "prepullActions": [ - {"action":{"activateAura":{"auraId":{"spellId":18288}}},"doAtValue":{"const":{"val":"-5s"}}}, - {"action":{"castSpell":{"spellId":{"spellId":11661,"rank":9}}},"doAtValue":{"const":{"val":"-2.5"}}} - ], - "priorityList": [ - {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"15s"}}}},{"cmp":{"op":"OpLe","lhs":{"currentManaPercent":{}},"rhs":{"const":{"val":"75%"}}}}]}},"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}}}, - {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"15s"}}}},{"cmp":{"op":"OpLe","lhs":{"currentManaPercent":{}},"rhs":{"const":{"val":"65%"}}}}]}},"castSpell":{"spellId":{"itemId":12662}}}}, - {"action":{"condition":{"not":{"val":{"auraIsActive":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":11713,"rank":6}}}}},"multidot":{"spellId":{"spellId":11713,"rank":6},"maxDots":1,"maxOverlap":{"const":{"val":"0ms"}}}}}, - {"hide":true,"action":{"condition":{"not":{"val":{"auraIsActive":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":11717,"rank":4}}}}},"castSpell":{"spellId":{"spellId":11717,"rank":4}}}}, - {"action":{"condition":{"cmp":{"op":"OpLe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"1.5"}}}},"castSpell":{"spellId":{"spellId":18871,"rank":6}}}}, - {"action":{"condition":{"cmp":{"op":"OpLe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"3.5"}}}},"castSpell":{"spellId":{"spellId":17923,"rank":6}}}}, - {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpLt","lhs":{"warlockCurrentPetMana":{}},"rhs":{"const":{"val":"800"}}}},{"cmp":{"op":"OpGt","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"10s"}}}}]}},"castSpell":{"spellId":{"spellId":11689,"rank":6}}}}, - {"action":{"castSpell":{"spellId":{"spellId":403501}}}}, - {"action":{"condition":{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"6s"}}}},"multidot":{"spellId":{"spellId":11672,"rank":6},"maxDots":1,"maxOverlap":{"const":{"val":"0ms"}}}}}, - {"hide":true,"action":{"condition":{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"6s"}}}},"multidot":{"spellId":{"spellId":11700,"rank":6},"maxDots":1,"maxOverlap":{"const":{"val":"0ms"}}}}}, - {"action":{"condition":{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"6s"}}}},"multidot":{"spellId":{"spellId":427717},"maxDots":1,"maxOverlap":{"const":{"val":"0ms"}}}}}, - {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":403501}}},{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"6s"}}}}]}},"multidot":{"spellId":{"spellId":18881,"rank":4},"maxDots":1,"maxOverlap":{"const":{"val":"0ms"}}}}}, - {"action":{"condition":{"cmp":{"op":"OpLt","lhs":{"currentManaPercent":{}},"rhs":{"const":{"val":"10%"}}}},"castSpell":{"spellId":{"spellId":11689,"rank":6}}}}, - {"action":{"castSpell":{"spellId":{"spellId":11661,"rank":9}}}} - ] - } - \ No newline at end of file + "type": "TypeAPL", + "prepullActions": [ + {"action":{"activateAura":{"auraId":{"spellId":18288}}},"doAtValue":{"const":{"val":"-5s"}}}, + {"action":{"castSpell":{"spellId":{"spellId":11661,"rank":9}}},"doAtValue":{"const":{"val":"-2.5"}}} + ], + "priorityList": [ + {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"15s"}}}},{"cmp":{"op":"OpLe","lhs":{"currentManaPercent":{}},"rhs":{"const":{"val":"75%"}}}}]}},"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}}}, + {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"15s"}}}},{"cmp":{"op":"OpLe","lhs":{"currentManaPercent":{}},"rhs":{"const":{"val":"65%"}}}}]}},"castSpell":{"spellId":{"itemId":12662}}}}, + {"action":{"condition":{"not":{"val":{"auraIsActive":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":11713,"rank":6}}}}},"multidot":{"spellId":{"spellId":11713,"rank":6},"maxDots":1,"maxOverlap":{"const":{"val":"0ms"}}}}}, + {"hide":true,"action":{"condition":{"not":{"val":{"auraIsActive":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":11717,"rank":4}}}}},"castSpell":{"spellId":{"spellId":11717,"rank":4}}}}, + {"action":{"condition":{"cmp":{"op":"OpLe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"1.5"}}}},"castSpell":{"spellId":{"spellId":18871,"rank":6}}}}, + {"action":{"condition":{"cmp":{"op":"OpLe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"3.5"}}}},"castSpell":{"spellId":{"spellId":17923,"rank":6}}}}, + {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpLt","lhs":{"warlockCurrentPetMana":{}},"rhs":{"const":{"val":"800"}}}},{"cmp":{"op":"OpGt","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"10s"}}}}]}},"castSpell":{"spellId":{"spellId":11689,"rank":6}}}}, + {"action":{"castSpell":{"spellId":{"spellId":403501}}}}, + {"action":{"condition":{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"6s"}}}},"multidot":{"spellId":{"spellId":11672,"rank":6},"maxDots":1,"maxOverlap":{"const":{"val":"0ms"}}}}}, + {"hide":true,"action":{"condition":{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"6s"}}}},"multidot":{"spellId":{"spellId":11700,"rank":6},"maxDots":1,"maxOverlap":{"const":{"val":"0ms"}}}}}, + {"action":{"condition":{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"6s"}}}},"multidot":{"spellId":{"spellId":427717},"maxDots":1,"maxOverlap":{"const":{"val":"0ms"}}}}}, + {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":403501}}},{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"6s"}}}}]}},"multidot":{"spellId":{"spellId":18881,"rank":4},"maxDots":1,"maxOverlap":{"const":{"val":"0ms"}}}}}, + {"hide":true,"action":{"condition":{"and":{"vals":[{"runeIsEquipped":{"runeId":{"spellId":403511}}},{"warlockShouldRecastDrainSoul":{}}]}},"channelSpell":{"spellId":{"spellId":11675,"rank":4},"interruptIf":{}}}}, + {"hide":true,"action":{"condition":{"and":{"vals":[{"runeIsEquipped":{"runeId":{"spellId":403511}}},{"isExecutePhase":{"threshold":"E20"}}]}},"channelSpell":{"spellId":{"spellId":11675,"rank":4},"interruptIf":{"const":{"val":"true"}}}}}, + {"action":{"castSpell":{"spellId":{"spellId":11661,"rank":9}}}}, + {"action":{"castSpell":{"spellId":{"spellId":11689,"rank":6}}}} + ] +} diff --git a/ui/warlock/apls/p4/destruction.apl.json b/ui/warlock/apls/p4/destruction.apl.json index 7df6bccdd1..7baf134713 100644 --- a/ui/warlock/apls/p4/destruction.apl.json +++ b/ui/warlock/apls/p4/destruction.apl.json @@ -7,7 +7,9 @@ "priorityList": [ {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"15s"}}}},{"cmp":{"op":"OpLe","lhs":{"currentManaPercent":{}},"rhs":{"const":{"val":"75%"}}}},{"isExecutePhase":{"threshold":"E35"}}]}},"castSpell":{"spellId":{"otherId":"OtherActionPotion"}}}}, {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"15s"}}}},{"cmp":{"op":"OpLe","lhs":{"currentManaPercent":{}},"rhs":{"const":{"val":"60%"}}}},{"isExecutePhase":{"threshold":"E35"}}]}},"castSpell":{"spellId":{"itemId":12662}}}}, - {"action":{"condition":{"not":{"val":{"auraIsActive":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":11717,"rank":4}}}}},"castSpell":{"spellId":{"spellId":11717,"rank":4}}}}, + {"action":{"condition":{"and":{"vals":[{"runeIsEquipped":{"runeId":{"spellId":440892}}},{"not":{"val":{"auraIsActive":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":449432}}}}},{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"60s"}}}}]}},"castSpell":{"spellId":{"spellId":449432}}}}, + {"action":{"condition":{"and":{"vals":[{"runeIsEquipped":{"runeId":{"spellId":440892}}},{"not":{"val":{"auraIsActive":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":11713,"rank":6}}}}},{"not":{"val":{"auraIsActive":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":449432}}}}}]}},"castSpell":{"spellId":{"spellId":11713,"rank":6}}}}, + {"action":{"condition":{"and":{"vals":[{"not":{"val":{"runeIsEquipped":{"runeId":{"spellId":440892}}}}},{"not":{"val":{"auraIsActive":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":11717,"rank":4}}}}}]}},"castSpell":{"spellId":{"spellId":11717,"rank":4}}}}, {"action":{"condition":{"cmp":{"op":"OpLe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"1.5"}}}},"castSpell":{"spellId":{"spellId":18871,"rank":6}}}}, {"action":{"condition":{"cmp":{"op":"OpLe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"3.5"}}}},"castSpell":{"spellId":{"spellId":17923,"rank":6}}}}, {"action":{"condition":{"and":{"vals":[{"cmp":{"op":"OpLt","lhs":{"warlockCurrentPetMana":{}},"rhs":{"const":{"val":"800"}}}},{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"10"}}}}]}},"castSpell":{"spellId":{"spellId":11689,"rank":6}}}}, From cadbc2ef1fc03d211b0105f70859a40f5aae9062 Mon Sep 17 00:00:00 2001 From: ncberman Date: Sun, 30 Jun 2024 00:04:25 +0000 Subject: [PATCH 03/10] Updated Flanking Strike to buff all damage, removed raptorStrike-specific bonus --- sim/hunter/flanking_strike.go | 21 +++++++++++++++++++++ sim/hunter/raptor_strike.go | 5 ----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/sim/hunter/flanking_strike.go b/sim/hunter/flanking_strike.go index f2eb6c841d..30c2d1d7f5 100644 --- a/sim/hunter/flanking_strike.go +++ b/sim/hunter/flanking_strike.go @@ -23,6 +23,11 @@ func (hunter *Hunter) registerFlankingStrikeSpell() { ActionID: core.ActionID{SpellID: 415320}, MaxStacks: 3, Duration: time.Second * 10, + + OnStacksChange: func(aura *core.Aura, sim *core.Simulation, oldStacks, newStacks int32) { + hunter.PseudoStats.DamageDealtMultiplier /= 1 + (0.05 * float64(oldStacks)) + hunter.PseudoStats.DamageDealtMultiplier *= 1 + (0.05 * float64(newStacks)) + }, }) if hunter.pet != nil { @@ -42,6 +47,22 @@ func (hunter *Hunter) registerFlankingStrikeSpell() { spell.CalcAndDealDamage(sim, target, baseDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) }, }) + + hunter.pet.RegisterAura(core.Aura{ + Label: "Flanking Strike Refresh", + 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.ProcMaskMeleeMHSpecial | core.ProcMaskSpellDamage) { + if sim.RandomFloat("Flanking Strike Refresh") < 0.33 { + hunter.FlankingStrike.CD.Set(sim.CurrentTime) + } + } + }, + }) } hunter.FlankingStrike = hunter.GetOrRegisterSpell(core.SpellConfig{ diff --git a/sim/hunter/raptor_strike.go b/sim/hunter/raptor_strike.go index 012c2bbd9b..a29b368a40 100644 --- a/sim/hunter/raptor_strike.go +++ b/sim/hunter/raptor_strike.go @@ -25,7 +25,6 @@ func (hunter *Hunter) getRaptorStrikeConfig(rank int) core.SpellConfig { hasDualWieldSpec := hunter.HasRune(proto.HunterRune_RuneBootsDualWieldSpecialization) hasMeleeSpecialist := hunter.HasRune(proto.HunterRune_RuneBeltMeleeSpecialist) - flankingStrikeDmgMult := 0.1 // https://www.wowhead.com/classic/news/class-tuning-incoming-hunter-shaman-warlock-season-of-discovery-339072?webhook raptorFuryDmgMult := 0.1 @@ -80,10 +79,6 @@ func (hunter *Hunter) getRaptorStrikeConfig(rank int) core.SpellConfig { ApplyEffects: func(sim *core.Simulation, target *core.Unit, spell *core.Spell) { multiplier := 1.0 - if stacks := hunter.FlankingStrikeAura.GetStacks(); stacks > 0 { - multiplier *= 1 + flankingStrikeDmgMult*float64(stacks) - } - if stacks := hunter.RaptorFuryAura.GetStacks(); stacks > 0 { multiplier *= 1 + raptorFuryDmgMult*float64(stacks) } From 0cf05c06f2870022a84beebb2b9a1ef58f2a5287 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sat, 29 Jun 2024 20:09:58 -0400 Subject: [PATCH 04/10] update tests --- sim/warlock/dps/TestAffliction.results | 160 +++++++++++------------ sim/warlock/tank/TestAffliction.results | 66 +++++----- sim/warlock/tank/TestDemonology.results | 32 ++--- sim/warlock/tank/TestDestruction.results | 100 +++++++------- 4 files changed, 179 insertions(+), 179 deletions(-) diff --git a/sim/warlock/dps/TestAffliction.results b/sim/warlock/dps/TestAffliction.results index 713c995f40..20207ec1f8 100644 --- a/sim/warlock/dps/TestAffliction.results +++ b/sim/warlock/dps/TestAffliction.results @@ -151,9 +151,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: -0.74339 + weights: -0.8067 weights: 0 - weights: 0.41648 + weights: 0.42695 weights: 0 weights: 0 weights: 0 @@ -161,8 +161,8 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 3.24996 - weights: 1.36107 + weights: 3.04261 + weights: 1.26263 weights: 0 weights: 0 weights: 0 @@ -200,9 +200,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: -4.338 + weights: -4.13015 weights: 0 - weights: -0.31801 + weights: -0.30344 weights: 0 weights: 0 weights: 0 @@ -210,8 +210,8 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 11.85581 - weights: 7.57835 + weights: 11.22013 + weights: 7.12227 weights: 0 weights: 0 weights: 0 @@ -249,9 +249,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: -1.73762 + weights: -7.09553 weights: 0 - weights: -0.31541 + weights: -2.34116 weights: 0 weights: 0 weights: 0 @@ -259,8 +259,8 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 19.73578 - weights: 18.38375 + weights: 22.66245 + weights: 11.68328 weights: 0 weights: 0 weights: 0 @@ -295,238 +295,238 @@ stat_weights_results: { dps_results: { key: "TestAffliction-Lvl40-AllItems-DeathmistRaiment" value: { - dps: 174.10272 - tps: 141.81072 + dps: 174.39095 + tps: 142.09896 } } dps_results: { key: "TestAffliction-Lvl40-Average-Default" value: { - dps: 564.79239 - tps: 543.97591 + dps: 564.41852 + tps: 543.60204 } } dps_results: { key: "TestAffliction-Lvl40-Settings-Orc-shadow-Affliction Warlock-affliction-FullBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 556.8524 - tps: 1181.11206 + dps: 561.42411 + tps: 1185.68377 } } dps_results: { key: "TestAffliction-Lvl40-Settings-Orc-shadow-Affliction Warlock-affliction-FullBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 556.8524 - tps: 535.59065 + dps: 561.42411 + tps: 540.16236 } } dps_results: { key: "TestAffliction-Lvl40-Settings-Orc-shadow-Affliction Warlock-affliction-FullBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 544.63529 - tps: 512.68446 + dps: 552.21675 + tps: 520.26592 } } dps_results: { key: "TestAffliction-Lvl40-Settings-Orc-shadow-Affliction Warlock-affliction-NoBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 363.35476 - tps: 1007.28615 + dps: 365.31435 + tps: 1009.24573 } } dps_results: { key: "TestAffliction-Lvl40-Settings-Orc-shadow-Affliction Warlock-affliction-NoBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 363.35476 - tps: 354.54914 + dps: 365.31435 + tps: 356.50873 } } dps_results: { key: "TestAffliction-Lvl40-Settings-Orc-shadow-Affliction Warlock-affliction-NoBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 364.58223 - tps: 340.33509 + dps: 367.23087 + tps: 342.98373 } } dps_results: { key: "TestAffliction-Lvl40-SwitchInFrontOfTarget-Default" value: { - dps: 564.88757 - tps: 544.42356 + dps: 563.54805 + tps: 543.08404 } } dps_results: { key: "TestAffliction-Lvl50-AllItems-DeathmistRaiment" value: { - dps: 320.02943 - tps: 216.03189 + dps: 319.62246 + tps: 215.91733 } } dps_results: { key: "TestAffliction-Lvl50-Average-Default" value: { - dps: 1406.25459 - tps: 1211.56618 + dps: 1367.43583 + tps: 1179.28906 } } dps_results: { key: "TestAffliction-Lvl50-Settings-Orc-nf.ruin-Destruction Warlock-nf.ruin-FullBuffs-Phase 3 Consumes-LongMultiTarget" value: { - dps: 2019.45049 - tps: 2795.42782 + dps: 1987.45447 + tps: 2769.95871 } } dps_results: { key: "TestAffliction-Lvl50-Settings-Orc-nf.ruin-Destruction Warlock-nf.ruin-FullBuffs-Phase 3 Consumes-LongSingleTarget" value: { - dps: 1386.477 - tps: 1190.05688 + dps: 1355.62134 + tps: 1165.77627 } } dps_results: { key: "TestAffliction-Lvl50-Settings-Orc-nf.ruin-Destruction Warlock-nf.ruin-FullBuffs-Phase 3 Consumes-ShortSingleTarget" value: { - dps: 1438.67932 - tps: 1231.59805 + dps: 1414.13198 + tps: 1212.74271 } } dps_results: { key: "TestAffliction-Lvl50-Settings-Orc-nf.ruin-Destruction Warlock-nf.ruin-NoBuffs-Phase 3 Consumes-LongMultiTarget" value: { - dps: 1315.20111 - tps: 2212.18742 + dps: 1235.41222 + tps: 2150.12625 } } dps_results: { key: "TestAffliction-Lvl50-Settings-Orc-nf.ruin-Destruction Warlock-nf.ruin-NoBuffs-Phase 3 Consumes-LongSingleTarget" value: { - dps: 846.9146 - tps: 718.77771 + dps: 767.07977 + tps: 656.68081 } } dps_results: { key: "TestAffliction-Lvl50-Settings-Orc-nf.ruin-Destruction Warlock-nf.ruin-NoBuffs-Phase 3 Consumes-ShortSingleTarget" value: { - dps: 833.85644 - tps: 720.09992 + dps: 764.25182 + tps: 663.79369 } } dps_results: { key: "TestAffliction-Lvl50-SwitchInFrontOfTarget-Default" value: { - dps: 1390.50635 - tps: 1194.92863 + dps: 1350.64039 + tps: 1161.6244 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-BloodGuard'sDreadweave" value: { - dps: 1288.27028 - tps: 1168.82942 + dps: 1276.74579 + tps: 1157.16894 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-DeathmistRaiment" value: { - dps: 1042.74258 - tps: 949.49911 + dps: 1068.94019 + tps: 978.48311 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-EmeraldEnchantedVestments" value: { - dps: 1273.47309 - tps: 1153.70915 + dps: 1264.92985 + tps: 1146.56742 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-InfernalPactEssence-216509" value: { - dps: 2163.42828 - tps: 1932.5405 + dps: 2202.85711 + tps: 1977.14972 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-Knight-Lieutenant'sDreadweave" value: { - dps: 1288.27028 - tps: 1168.82942 + dps: 1276.74579 + tps: 1157.16894 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-MalevolentProphet'sVestments" value: { - dps: 1668.2331 - tps: 1532.6869 + dps: 1694.40086 + tps: 1562.76183 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-NightmareProphet'sGarb" value: { - dps: 1649.7997 - tps: 1515.68843 + dps: 1656.45593 + tps: 1522.22946 } } dps_results: { key: "TestAffliction-Lvl60-AllItems-ZilaGular-223214" value: { - dps: 2142.39438 - tps: 1916.71035 + dps: 2138.64175 + tps: 1913.54413 } } dps_results: { key: "TestAffliction-Lvl60-Average-Default" value: { - dps: 2204.99701 - tps: 1974.02229 + dps: 2203.80458 + tps: 1974.21363 } } dps_results: { key: "TestAffliction-Lvl60-Settings-Orc-affliction-Destruction Warlock-affliction-FullBuffs-Phase 4 Consumes-LongMultiTarget" value: { - dps: 2186.42048 - tps: 3037.73708 + dps: 2187.1084 + tps: 3049.93956 } } dps_results: { key: "TestAffliction-Lvl60-Settings-Orc-affliction-Destruction Warlock-affliction-FullBuffs-Phase 4 Consumes-LongSingleTarget" value: { - dps: 2186.42048 - tps: 1954.20575 + dps: 2187.1084 + tps: 1955.78059 } } dps_results: { key: "TestAffliction-Lvl60-Settings-Orc-affliction-Destruction Warlock-affliction-FullBuffs-Phase 4 Consumes-ShortSingleTarget" value: { - dps: 2149.30857 - tps: 1925.87209 + dps: 2158.78746 + tps: 1933.7563 } } dps_results: { key: "TestAffliction-Lvl60-Settings-Orc-affliction-Destruction Warlock-affliction-NoBuffs-Phase 4 Consumes-LongMultiTarget" value: { - dps: 1238.78445 - tps: 2263.83873 + dps: 1255.41263 + tps: 2299.81013 } } dps_results: { key: "TestAffliction-Lvl60-Settings-Orc-affliction-Destruction Warlock-affliction-NoBuffs-Phase 4 Consumes-LongSingleTarget" value: { - dps: 1238.78445 - tps: 1088.50571 + dps: 1255.41263 + tps: 1106.37263 } } dps_results: { key: "TestAffliction-Lvl60-Settings-Orc-affliction-Destruction Warlock-affliction-NoBuffs-Phase 4 Consumes-ShortSingleTarget" value: { - dps: 1215.18407 - tps: 1055.29129 + dps: 1222.234 + tps: 1063.86418 } } dps_results: { key: "TestAffliction-Lvl60-SwitchInFrontOfTarget-Default" value: { - dps: 2198.50605 - tps: 1968.22763 + dps: 2181.35145 + tps: 1950.6949 } } diff --git a/sim/warlock/tank/TestAffliction.results b/sim/warlock/tank/TestAffliction.results index 5faa35c881..34ba3e6ede 100644 --- a/sim/warlock/tank/TestAffliction.results +++ b/sim/warlock/tank/TestAffliction.results @@ -53,9 +53,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.02901 + weights: 0.04835 weights: 0 - weights: 0.5903 + weights: 0.54536 weights: 0 weights: 0 weights: 0 @@ -64,7 +64,7 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.521 + weights: 0.47249 weights: 0 weights: 0 weights: 0 @@ -99,105 +99,105 @@ stat_weights_results: { dps_results: { key: "TestAffliction-Lvl25-AllItems-DeathmistRaiment" value: { - dps: 104.61962 - tps: 87.30513 + dps: 104.71721 + tps: 87.40271 } } dps_results: { key: "TestAffliction-Lvl25-Average-Default" value: { - dps: 192.43086 - tps: 365.28207 + dps: 173.87316 + tps: 331.0974 } } dps_results: { key: "TestAffliction-Lvl25-Settings-Orc-p1.affi.tank-AffItemSwap-p1.affi.tank-FullBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 285.45891 - tps: 735.79617 + dps: 141.64184 + tps: 504.32224 } } dps_results: { key: "TestAffliction-Lvl25-Settings-Orc-p1.affi.tank-AffItemSwap-p1.affi.tank-FullBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 192.45806 - tps: 365.32071 + dps: 173.94823 + tps: 331.20034 } } dps_results: { key: "TestAffliction-Lvl25-Settings-Orc-p1.affi.tank-AffItemSwap-p1.affi.tank-FullBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 199.77546 - tps: 383.42527 + dps: 182.29585 + tps: 348.80756 } } dps_results: { key: "TestAffliction-Lvl25-Settings-Orc-p1.affi.tank-AffItemSwap-p1.affi.tank-NoBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 206.83598 - tps: 644.31384 + dps: 108.11812 + tps: 490.10915 } } dps_results: { key: "TestAffliction-Lvl25-Settings-Orc-p1.affi.tank-AffItemSwap-p1.affi.tank-NoBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 140.25875 - tps: 263.72413 + dps: 126.59944 + tps: 239.36662 } } dps_results: { key: "TestAffliction-Lvl25-Settings-Orc-p1.affi.tank-AffItemSwap-p1.affi.tank-NoBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 148.01108 - tps: 280.50982 + dps: 134.54431 + tps: 255.00039 } } dps_results: { key: "TestAffliction-Lvl25-Settings-Orc-p1.affi.tank-Affliction Warlock-p1.affi.tank-FullBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 285.45891 - tps: 735.79617 + dps: 141.64184 + tps: 504.32224 } } dps_results: { key: "TestAffliction-Lvl25-Settings-Orc-p1.affi.tank-Affliction Warlock-p1.affi.tank-FullBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 192.45806 - tps: 365.32071 + dps: 173.94823 + tps: 331.20034 } } dps_results: { key: "TestAffliction-Lvl25-Settings-Orc-p1.affi.tank-Affliction Warlock-p1.affi.tank-FullBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 199.77546 - tps: 383.42527 + dps: 182.29585 + tps: 348.80756 } } dps_results: { key: "TestAffliction-Lvl25-Settings-Orc-p1.affi.tank-Affliction Warlock-p1.affi.tank-NoBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 206.83598 - tps: 644.31384 + dps: 108.11812 + tps: 490.10915 } } dps_results: { key: "TestAffliction-Lvl25-Settings-Orc-p1.affi.tank-Affliction Warlock-p1.affi.tank-NoBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 140.25875 - tps: 263.72413 + dps: 126.59944 + tps: 239.36662 } } dps_results: { key: "TestAffliction-Lvl25-Settings-Orc-p1.affi.tank-Affliction Warlock-p1.affi.tank-NoBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 148.01108 - tps: 280.50982 + dps: 134.54431 + tps: 255.00039 } } dps_results: { key: "TestAffliction-Lvl25-SwitchInFrontOfTarget-Default" value: { - dps: 192.5428 - tps: 365.44781 + dps: 174.03296 + tps: 331.32744 } } diff --git a/sim/warlock/tank/TestDemonology.results b/sim/warlock/tank/TestDemonology.results index b999cd772d..cc73ef5bf6 100644 --- a/sim/warlock/tank/TestDemonology.results +++ b/sim/warlock/tank/TestDemonology.results @@ -53,9 +53,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: -0.89001 + weights: -0.87757 weights: 0 - weights: 0.50101 + weights: 0.48789 weights: 0 weights: 0 weights: 0 @@ -63,8 +63,8 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 7.11698 - weights: 1.719 + weights: 7.13039 + weights: 1.69787 weights: 0 weights: 0 weights: 0 @@ -99,36 +99,36 @@ stat_weights_results: { dps_results: { key: "TestDemonology-Lvl40-AllItems-DeathmistRaiment" value: { - dps: 256.96615 - tps: 145.30325 + dps: 257.32939 + tps: 145.6665 } } dps_results: { key: "TestDemonology-Lvl40-Average-Default" value: { - dps: 612.09464 - tps: 1110.02305 + dps: 612.61109 + tps: 1110.79772 } } dps_results: { key: "TestDemonology-Lvl40-Settings-Orc-p2.demo.tank-Demonology Warlock-p2.demo.tank-FullBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 574.46224 - tps: 1555.10272 + dps: 575.30968 + tps: 1556.37388 } } dps_results: { key: "TestDemonology-Lvl40-Settings-Orc-p2.demo.tank-Demonology Warlock-p2.demo.tank-FullBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 574.46224 - tps: 1048.37234 + dps: 575.30968 + tps: 1049.64351 } } dps_results: { key: "TestDemonology-Lvl40-Settings-Orc-p2.demo.tank-Demonology Warlock-p2.demo.tank-FullBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 604.34302 - tps: 1104.69318 + dps: 607.36437 + tps: 1109.2252 } } dps_results: { @@ -155,7 +155,7 @@ dps_results: { dps_results: { key: "TestDemonology-Lvl40-SwitchInFrontOfTarget-Default" value: { - dps: 607.84565 - tps: 1103.61567 + dps: 609.0058 + tps: 1105.3559 } } diff --git a/sim/warlock/tank/TestDestruction.results b/sim/warlock/tank/TestDestruction.results index d37a88b9e8..293b9a361b 100644 --- a/sim/warlock/tank/TestDestruction.results +++ b/sim/warlock/tank/TestDestruction.results @@ -151,9 +151,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.08161 + weights: 0.07352 weights: 0 - weights: 0.51408 + weights: 0.46341 weights: 0 weights: 0 weights: 0 @@ -162,7 +162,7 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.51849 + weights: 0.46769 weights: 0 weights: 0 weights: 0 @@ -249,9 +249,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 12.96013 + weights: 12.34183 weights: 0 - weights: 5.3569 + weights: 5.45731 weights: 0 weights: 0 weights: 0 @@ -260,7 +260,7 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 7.14081 + weights: 6.94806 weights: 0 weights: 0 weights: 0 @@ -295,64 +295,64 @@ stat_weights_results: { dps_results: { key: "TestDestruction-Lvl25-AllItems-DeathmistRaiment" value: { - dps: 93.59681 - tps: 61.79076 + dps: 93.00552 + tps: 61.46255 } } dps_results: { key: "TestDestruction-Lvl25-Average-Default" value: { - dps: 169.81961 - tps: 330.07921 + dps: 153.09281 + tps: 298.22158 } } dps_results: { key: "TestDestruction-Lvl25-Settings-Orc-p1.destro.tank-Destruction Warlock-p1.destro.tank-FullBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 169.37267 - tps: 463.65091 + dps: 152.676 + tps: 431.8253 } } dps_results: { key: "TestDestruction-Lvl25-Settings-Orc-p1.destro.tank-Destruction Warlock-p1.destro.tank-FullBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 169.37267 - tps: 329.52691 + dps: 152.676 + tps: 297.7013 } } dps_results: { key: "TestDestruction-Lvl25-Settings-Orc-p1.destro.tank-Destruction Warlock-p1.destro.tank-FullBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 177.66714 - tps: 352.10892 + dps: 160.50051 + tps: 317.86854 } } dps_results: { key: "TestDestruction-Lvl25-Settings-Orc-p1.destro.tank-Destruction Warlock-p1.destro.tank-NoBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 132.12615 - tps: 413.82446 + dps: 119.10252 + tps: 390.57106 } } dps_results: { key: "TestDestruction-Lvl25-Settings-Orc-p1.destro.tank-Destruction Warlock-p1.destro.tank-NoBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 132.12615 - tps: 244.60058 + dps: 119.10252 + tps: 221.34718 } } dps_results: { key: "TestDestruction-Lvl25-Settings-Orc-p1.destro.tank-Destruction Warlock-p1.destro.tank-NoBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 135.98367 - tps: 270.79007 + dps: 122.85761 + tps: 244.47335 } } dps_results: { key: "TestDestruction-Lvl25-SwitchInFrontOfTarget-Default" value: { - dps: 169.45741 - tps: 329.65401 + dps: 152.76073 + tps: 297.82841 } } dps_results: { @@ -421,72 +421,72 @@ dps_results: { dps_results: { key: "TestDestruction-Lvl50-AllItems-DeathmistRaiment" value: { - dps: 306.30366 - tps: 212.87032 + dps: 306.15079 + tps: 212.71745 hps: 10.61937 } } dps_results: { key: "TestDestruction-Lvl50-Average-Default" value: { - dps: 1328.78967 - tps: 2236.39439 - hps: 19.95318 + dps: 1294.82036 + tps: 2180.12725 + hps: 19.4229 } } dps_results: { key: "TestDestruction-Lvl50-Settings-Orc-p3.destro.tank-Destruction Warlock-p3.destro.tank-FullBuffs-Phase 3 Consumes-LongMultiTarget" value: { - dps: 1880.02857 - tps: 4025.49743 - hps: 15.78531 + dps: 1845.83558 + tps: 3969.34112 + hps: 15.36532 } } dps_results: { key: "TestDestruction-Lvl50-Settings-Orc-p3.destro.tank-Destruction Warlock-p3.destro.tank-FullBuffs-Phase 3 Consumes-LongSingleTarget" value: { - dps: 1255.97247 - tps: 2116.77338 - hps: 15.84054 + dps: 1221.18505 + tps: 2059.54211 + hps: 15.41906 } } dps_results: { key: "TestDestruction-Lvl50-Settings-Orc-p3.destro.tank-Destruction Warlock-p3.destro.tank-FullBuffs-Phase 3 Consumes-ShortSingleTarget" value: { - dps: 1239.61279 - tps: 2076.47166 - hps: 16.2687 + dps: 1213.61748 + tps: 2031.61435 + hps: 15.8622 } } dps_results: { key: "TestDestruction-Lvl50-Settings-Orc-p3.destro.tank-Destruction Warlock-p3.destro.tank-NoBuffs-Phase 3 Consumes-LongMultiTarget" value: { - dps: 1264.2298 - tps: 3045.47383 - hps: 11.37677 + dps: 1186.93441 + tps: 2924.44035 + hps: 10.26833 } } dps_results: { key: "TestDestruction-Lvl50-Settings-Orc-p3.destro.tank-Destruction Warlock-p3.destro.tank-NoBuffs-Phase 3 Consumes-LongSingleTarget" value: { - dps: 797.3697 - tps: 1300.24774 - hps: 11.48777 + dps: 719.72923 + tps: 1178.53299 + hps: 10.36833 } } dps_results: { key: "TestDestruction-Lvl50-Settings-Orc-p3.destro.tank-Destruction Warlock-p3.destro.tank-NoBuffs-Phase 3 Consumes-ShortSingleTarget" value: { - dps: 764.77233 - tps: 1270.80521 - hps: 11.74383 + dps: 695.87481 + tps: 1157.25501 + hps: 10.675 } } dps_results: { key: "TestDestruction-Lvl50-SwitchInFrontOfTarget-Default" value: { - dps: 1315.66946 - tps: 2223.35961 - hps: 20.31964 + dps: 1281.87824 + tps: 2167.18251 + hps: 19.77965 } } From 2bc77e487a2899d55c41cc0284bb801969c75be9 Mon Sep 17 00:00:00 2001 From: ncberman Date: Sun, 30 Jun 2024 00:16:52 +0000 Subject: [PATCH 05/10] Updated carve to deal 1.5x damage to the primary target --- sim/hunter/carve.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sim/hunter/carve.go b/sim/hunter/carve.go index fc86cbb510..b2684b5e29 100644 --- a/sim/hunter/carve.go +++ b/sim/hunter/carve.go @@ -60,7 +60,11 @@ func (hunter *Hunter) registerCarveSpell() { curTarget := target for idx := range results { baseDamage := spell.Unit.MHNormalizedWeaponDamage(sim, spell.MeleeAttackPower()) - results[idx] = spell.CalcDamage(sim, curTarget, baseDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) + if curTarget == target { + results[idx] = spell.CalcDamage(sim, curTarget, baseDamage * 1.5, spell.OutcomeMeleeWeaponSpecialHitAndCrit) + } else { + results[idx] = spell.CalcDamage(sim, curTarget, baseDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) + } curTarget = sim.Environment.NextTargetUnit(curTarget) } @@ -74,7 +78,11 @@ func (hunter *Hunter) registerCarveSpell() { curTarget := target for idx := range results { baseDamage := ohSpell.Unit.OHNormalizedWeaponDamage(sim, ohSpell.MeleeAttackPower()) - results[idx] = ohSpell.CalcDamage(sim, curTarget, baseDamage, ohSpell.OutcomeMeleeWeaponSpecialHitAndCrit) + if curTarget == target { + results[idx] = ohSpell.CalcDamage(sim, curTarget, baseDamage * 1.5, ohSpell.OutcomeMeleeWeaponSpecialHitAndCrit) + } else { + results[idx] = ohSpell.CalcDamage(sim, curTarget, baseDamage, ohSpell.OutcomeMeleeWeaponSpecialHitAndCrit) + } curTarget = sim.Environment.NextTargetUnit(curTarget) } From 335cdcbeb8c7085eb0a5b293b699e80958712764 Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sat, 29 Jun 2024 20:25:22 -0400 Subject: [PATCH 06/10] fix drain soul APL condition --- sim/warlock/apl_values.go | 17 ++++++++++++----- ui/warlock/apls/p4/affliction.apl.json | 1 + 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/sim/warlock/apl_values.go b/sim/warlock/apl_values.go index 2d2bd41396..2ece94cc1d 100644 --- a/sim/warlock/apl_values.go +++ b/sim/warlock/apl_values.go @@ -48,26 +48,33 @@ func (value *APLValueWarlockShouldRecastDrainSoul) GetBool(sim *core.Simulation) for _, spell := range warlock.DrainSoul { if spell.CurDot().IsActive() { activeDrainSoul = spell + break } } if activeDrainSoul == nil { return false } - var activeCurseOfAgony *core.Spell + var curseOfAgonyDuration time.Duration = 0 for _, spell := range warlock.CurseOfAgony { if spell.CurDot().IsActive() { - activeCurseOfAgony = spell + curseOfAgonyDuration = spell.CurDot().RemainingDuration(sim) + break } } + var curseOfDoomDuration time.Duration = 0 + if warlock.CurseOfDoom.CurDot().IsActive() { + curseOfDoomDuration = warlock.CurseOfDoom.CurDot().RemainingDuration(sim) + } + curseRefresh := max( - activeCurseOfAgony.CurDot().RemainingDuration(sim), - warlock.CurseOfDoom.CurDot().RemainingDuration(sim), + curseOfAgonyDuration, + curseOfDoomDuration, warlock.CurseOfElementsAuras.Get(warlock.CurrentTarget).RemainingDuration(sim), // warlock.CurseOfTonguesAuras.Get(warlock.CurrentTarget).RemainingDuration(sim), // warlock.CurseOfWeaknessAuras.Get(warlock.CurrentTarget).RemainingDuration(sim), - ) - activeCurseOfAgony.CastTime() + ) hauntRefresh := 1000 * time.Second if warlock.HauntDebuffAuras != nil { diff --git a/ui/warlock/apls/p4/affliction.apl.json b/ui/warlock/apls/p4/affliction.apl.json index 6c5b58c05c..a98f90e165 100644 --- a/ui/warlock/apls/p4/affliction.apl.json +++ b/ui/warlock/apls/p4/affliction.apl.json @@ -17,6 +17,7 @@ {"hide":true,"action":{"condition":{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"6s"}}}},"multidot":{"spellId":{"spellId":11700,"rank":6},"maxDots":1,"maxOverlap":{"const":{"val":"0ms"}}}}}, {"action":{"condition":{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"6s"}}}},"multidot":{"spellId":{"spellId":427717},"maxDots":1,"maxOverlap":{"const":{"val":"0ms"}}}}}, {"action":{"condition":{"and":{"vals":[{"auraIsActive":{"sourceUnit":{"type":"CurrentTarget"},"auraId":{"spellId":403501}}},{"cmp":{"op":"OpGe","lhs":{"remainingTime":{}},"rhs":{"const":{"val":"6s"}}}}]}},"multidot":{"spellId":{"spellId":18881,"rank":4},"maxDots":1,"maxOverlap":{"const":{"val":"0ms"}}}}}, + {"action":{"condition":{"auraIsActive":{"auraId":{"spellId":17941}}},"castSpell":{"spellId":{"spellId":11661,"rank":9}}}}, {"hide":true,"action":{"condition":{"and":{"vals":[{"runeIsEquipped":{"runeId":{"spellId":403511}}},{"warlockShouldRecastDrainSoul":{}}]}},"channelSpell":{"spellId":{"spellId":11675,"rank":4},"interruptIf":{}}}}, {"hide":true,"action":{"condition":{"and":{"vals":[{"runeIsEquipped":{"runeId":{"spellId":403511}}},{"isExecutePhase":{"threshold":"E20"}}]}},"channelSpell":{"spellId":{"spellId":11675,"rank":4},"interruptIf":{"const":{"val":"true"}}}}}, {"action":{"castSpell":{"spellId":{"spellId":11661,"rank":9}}}}, From b84a7396e6f01de92b37f0e021a463e8cfce1124 Mon Sep 17 00:00:00 2001 From: ncberman Date: Sun, 30 Jun 2024 00:30:09 +0000 Subject: [PATCH 07/10] Figured I might as well update melee spec and raptor strike to no longer reset FS and reduce RS cd --- sim/hunter/raptor_strike.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/sim/hunter/raptor_strike.go b/sim/hunter/raptor_strike.go index a29b368a40..76ffce4dc0 100644 --- a/sim/hunter/raptor_strike.go +++ b/sim/hunter/raptor_strike.go @@ -20,7 +20,6 @@ func (hunter *Hunter) getRaptorStrikeConfig(rank int) core.SpellConfig { baseDamage := [9]float64{0, 5, 11, 21, 34, 50, 80, 110, 140}[rank] manaCost := [9]float64{0, 15, 25, 35, 45, 55, 70, 80, 100}[rank] level := [9]int{0, 1, 8, 16, 24, 32, 40, 48, 56}[rank] - hasFlankingStrike := hunter.HasRune(proto.HunterRune_RuneLegsFlankingStrike) hasRaptorFury := hunter.HasRune(proto.HunterRune_RuneBracersRaptorFury) hasDualWieldSpec := hunter.HasRune(proto.HunterRune_RuneBootsDualWieldSpecialization) hasMeleeSpecialist := hunter.HasRune(proto.HunterRune_RuneBeltMeleeSpecialist) @@ -66,7 +65,7 @@ func (hunter *Hunter) getRaptorStrikeConfig(rank int) core.SpellConfig { Cast: core.CastConfig{ CD: core.Cooldown{ Timer: hunter.NewTimer(), - Duration: time.Second * time.Duration(core.TernaryInt(hasMeleeSpecialist, 3, 6)), + Duration: time.Second * 6, }, }, ExtraCastCondition: func(sim *core.Simulation, target *core.Unit) bool { @@ -111,10 +110,6 @@ func (hunter *Hunter) getRaptorStrikeConfig(rank int) core.SpellConfig { hunter.curQueueAura.Deactivate(sim) } - if hasFlankingStrike && sim.RandomFloat("Flanking Strike Refresh") < 0.2 { - hunter.FlankingStrike.CD.Set(sim.CurrentTime) - } - if hasMeleeSpecialist && sim.RandomFloat("Raptor Strike Reset") < 0.3 { spell.CD.Reset() } From de1ccc9ef5869702b89441c41eacfa45f88b52a9 Mon Sep 17 00:00:00 2001 From: ncberman Date: Sun, 30 Jun 2024 00:45:01 +0000 Subject: [PATCH 08/10] Updated tests.. oops :^) --- sim/hunter/TestBM.results | 136 +++++++++++++++++++------------------- sim/hunter/TestMM.results | 66 +++++++++--------- sim/hunter/TestSV.results | 136 +++++++++++++++++++------------------- 3 files changed, 169 insertions(+), 169 deletions(-) diff --git a/sim/hunter/TestBM.results b/sim/hunter/TestBM.results index 496d5af89d..1f12a549af 100644 --- a/sim/hunter/TestBM.results +++ b/sim/hunter/TestBM.results @@ -100,7 +100,7 @@ stat_weights_results: { key: "TestBM-Lvl25-StatWeights-Default" value: { weights: 0 - weights: 0.47504 + weights: 0.51891 weights: 0 weights: 0 weights: 0 @@ -116,9 +116,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.02213 - weights: 2.50664 - weights: 2.11154 + weights: 0.0262 + weights: 2.40326 + weights: 2.22103 weights: 0 weights: 0 weights: 0 @@ -126,7 +126,7 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.12947 + weights: 0.13124 weights: 0 weights: 0 weights: 0 @@ -149,7 +149,7 @@ stat_weights_results: { key: "TestBM-Lvl40-StatWeights-Default" value: { weights: 0 - weights: 0.83036 + weights: 0.80948 weights: 0 weights: 0 weights: 0 @@ -165,9 +165,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.33812 - weights: 10.13832 - weights: 8.0875 + weights: 0.31124 + weights: 8.50809 + weights: 7.68964 weights: 0 weights: 0 weights: 0 @@ -175,7 +175,7 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.0644 + weights: 0.06516 weights: 0 weights: 0 weights: 0 @@ -204,99 +204,99 @@ dps_results: { dps_results: { key: "TestBM-Lvl25-Average-Default" value: { - dps: 243.78646 - tps: 101.84241 + dps: 250.3442 + tps: 102.79252 } } dps_results: { key: "TestBM-Lvl25-Settings-NightElf-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 246.23964 - tps: 113.28837 + dps: 250.89875 + tps: 114.38548 } } dps_results: { key: "TestBM-Lvl25-Settings-NightElf-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 235.6603 - tps: 99.57051 + dps: 242.36747 + tps: 101.12937 } } dps_results: { key: "TestBM-Lvl25-Settings-NightElf-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 285.86131 - tps: 113.76855 + dps: 312.10516 + tps: 119.32138 } } dps_results: { key: "TestBM-Lvl25-Settings-NightElf-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 124.33105 - tps: 61.38077 + dps: 126.40871 + tps: 62.42832 } } dps_results: { key: "TestBM-Lvl25-Settings-NightElf-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 119.83474 - tps: 56.0008 + dps: 122.17212 + tps: 56.70515 } } dps_results: { key: "TestBM-Lvl25-Settings-NightElf-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 142.99612 - tps: 61.81943 + dps: 154.5189 + tps: 65.81464 } } dps_results: { key: "TestBM-Lvl25-Settings-Orc-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 247.97035 - tps: 111.0891 + dps: 251.89503 + tps: 111.02439 } } dps_results: { key: "TestBM-Lvl25-Settings-Orc-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 237.11079 - tps: 97.36767 + dps: 243.4198 + tps: 98.58413 } } dps_results: { key: "TestBM-Lvl25-Settings-Orc-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 288.79984 - tps: 111.79339 + dps: 315.17718 + tps: 117.42254 } } dps_results: { key: "TestBM-Lvl25-Settings-Orc-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 125.1305 - tps: 60.29073 + dps: 126.73686 + tps: 61.71224 } } dps_results: { key: "TestBM-Lvl25-Settings-Orc-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 120.62978 - tps: 55.05935 + dps: 123.05605 + tps: 55.86071 } } dps_results: { key: "TestBM-Lvl25-Settings-Orc-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 143.69512 - tps: 62.48865 + dps: 154.53532 + tps: 65.12392 } } dps_results: { key: "TestBM-Lvl25-SwitchInFrontOfTarget-Default" value: { - dps: 241.38249 - tps: 100.74703 + dps: 247.90955 + tps: 102.24514 } } dps_results: { @@ -309,57 +309,57 @@ dps_results: { dps_results: { key: "TestBM-Lvl40-AllItems-SignetofBeasts-209823" value: { - dps: 913.96337 - tps: 280.19495 + dps: 845.17737 + tps: 315.54554 } } dps_results: { key: "TestBM-Lvl40-Average-Default" value: { - dps: 924.79329 - tps: 288.87584 + dps: 855.66083 + tps: 322.402 } } dps_results: { key: "TestBM-Lvl40-Settings-NightElf-p2_melee-Basic-p2_melee-FullBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 1523.26417 - tps: 897.98799 + dps: 1638.99844 + tps: 1001.53042 } } dps_results: { key: "TestBM-Lvl40-Settings-NightElf-p2_melee-Basic-p2_melee-FullBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 820.2226 - tps: 216.23749 + dps: 747.41644 + tps: 242.24869 } } dps_results: { key: "TestBM-Lvl40-Settings-NightElf-p2_melee-Basic-p2_melee-FullBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 856.77014 - tps: 230.17178 + dps: 781.6619 + tps: 255.0673 } } dps_results: { key: "TestBM-Lvl40-Settings-NightElf-p2_melee-Basic-p2_melee-NoBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 764.95479 - tps: 575.94345 + dps: 865.73065 + tps: 637.11733 } } dps_results: { key: "TestBM-Lvl40-Settings-NightElf-p2_melee-Basic-p2_melee-NoBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 403.62342 - tps: 113.86075 + dps: 370.2734 + tps: 128.19394 } } dps_results: { key: "TestBM-Lvl40-Settings-NightElf-p2_melee-Basic-p2_melee-NoBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 435.51751 - tps: 118.4766 + dps: 399.71796 + tps: 131.57526 } } dps_results: { @@ -491,43 +491,43 @@ dps_results: { dps_results: { key: "TestBM-Lvl40-Settings-Orc-p2_melee-Basic-p2_melee-FullBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 1527.63221 - tps: 886.69979 + dps: 1652.92924 + tps: 998.31713 } } dps_results: { key: "TestBM-Lvl40-Settings-Orc-p2_melee-Basic-p2_melee-FullBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 827.05115 - tps: 213.50604 + dps: 763.14504 + tps: 241.72079 } } dps_results: { key: "TestBM-Lvl40-Settings-Orc-p2_melee-Basic-p2_melee-FullBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 865.04602 - tps: 231.79392 + dps: 808.22658 + tps: 256.09986 } } dps_results: { key: "TestBM-Lvl40-Settings-Orc-p2_melee-Basic-p2_melee-NoBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 772.00655 - tps: 577.6347 + dps: 865.15225 + tps: 631.16498 } } dps_results: { key: "TestBM-Lvl40-Settings-Orc-p2_melee-Basic-p2_melee-NoBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 404.06768 - tps: 112.80685 + dps: 373.97251 + tps: 126.35807 } } dps_results: { key: "TestBM-Lvl40-Settings-Orc-p2_melee-Basic-p2_melee-NoBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 436.70273 - tps: 118.03774 + dps: 407.26555 + tps: 131.79434 } } dps_results: { @@ -659,7 +659,7 @@ dps_results: { dps_results: { key: "TestBM-Lvl40-SwitchInFrontOfTarget-Default" value: { - dps: 877.23812 - tps: 264.0441 + dps: 812.33198 + tps: 295.68222 } } diff --git a/sim/hunter/TestMM.results b/sim/hunter/TestMM.results index 1b4bc1ccf3..2d56190806 100644 --- a/sim/hunter/TestMM.results +++ b/sim/hunter/TestMM.results @@ -100,7 +100,7 @@ stat_weights_results: { key: "TestMM-Lvl25-StatWeights-Default" value: { weights: 0 - weights: 0.42394 + weights: 0.41195 weights: 0 weights: 0 weights: 0 @@ -116,9 +116,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.02327 - weights: 2.4028 - weights: 1.68375 + weights: 0.03015 + weights: 2.8135 + weights: 1.89658 weights: 0 weights: 0 weights: 0 @@ -126,7 +126,7 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.12005 + weights: 0.12252 weights: 0 weights: 0 weights: 0 @@ -204,99 +204,99 @@ dps_results: { dps_results: { key: "TestMM-Lvl25-Average-Default" value: { - dps: 221.42869 - tps: 102.2639 + dps: 230.33731 + tps: 105.22762 } } dps_results: { key: "TestMM-Lvl25-Settings-Dwarf-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 221.05218 - tps: 110.3264 + dps: 227.83725 + tps: 114.03004 } } dps_results: { key: "TestMM-Lvl25-Settings-Dwarf-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 208.61293 - tps: 96.80533 + dps: 215.80644 + tps: 100.8616 } } dps_results: { key: "TestMM-Lvl25-Settings-Dwarf-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 261.08365 - tps: 117.49347 + dps: 289.20378 + tps: 125.66908 } } dps_results: { key: "TestMM-Lvl25-Settings-Dwarf-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 111.25195 - tps: 60.45476 + dps: 113.25685 + tps: 61.7826 } } dps_results: { key: "TestMM-Lvl25-Settings-Dwarf-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 106.43244 - tps: 55.39596 + dps: 109.34887 + tps: 56.55261 } } dps_results: { key: "TestMM-Lvl25-Settings-Dwarf-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 129.58967 - tps: 64.4856 + dps: 142.51357 + tps: 69.09927 } } dps_results: { key: "TestMM-Lvl25-Settings-Orc-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 226.68569 - tps: 111.57382 + dps: 232.75036 + tps: 115.27323 } } dps_results: { key: "TestMM-Lvl25-Settings-Orc-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 213.02105 - tps: 97.52423 + dps: 221.73734 + tps: 101.5884 } } dps_results: { key: "TestMM-Lvl25-Settings-Orc-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 266.83721 - tps: 117.9251 + dps: 295.382 + tps: 126.26724 } } dps_results: { key: "TestMM-Lvl25-Settings-Orc-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 113.94531 - tps: 60.92157 + dps: 116.05135 + tps: 62.0206 } } dps_results: { key: "TestMM-Lvl25-Settings-Orc-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 109.41657 - tps: 55.78624 + dps: 111.83478 + tps: 56.784 } } dps_results: { key: "TestMM-Lvl25-Settings-Orc-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 131.85365 - tps: 63.9827 + dps: 143.809 + tps: 67.92067 } } dps_results: { key: "TestMM-Lvl25-SwitchInFrontOfTarget-Default" value: { - dps: 218.63054 - tps: 101.25507 + dps: 227.63687 + tps: 105.01494 } } dps_results: { diff --git a/sim/hunter/TestSV.results b/sim/hunter/TestSV.results index 3d0991b57a..0a1a8cb34b 100644 --- a/sim/hunter/TestSV.results +++ b/sim/hunter/TestSV.results @@ -100,7 +100,7 @@ stat_weights_results: { key: "TestSV-Lvl25-StatWeights-Default" value: { weights: 0 - weights: 0.45631 + weights: 0.42471 weights: 0 weights: 0 weights: 0 @@ -116,9 +116,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.0241 - weights: 2.17364 - weights: 1.86688 + weights: 0.02742 + weights: 1.87513 + weights: 1.95844 weights: 0 weights: 0 weights: 0 @@ -126,7 +126,7 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.11883 + weights: 0.12048 weights: 0 weights: 0 weights: 0 @@ -149,7 +149,7 @@ stat_weights_results: { key: "TestSV-Lvl40-StatWeights-Default" value: { weights: 0 - weights: 0.81796 + weights: 0.94907 weights: 0 weights: 0 weights: 0 @@ -165,9 +165,9 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.38704 - weights: 9.02202 - weights: 7.58221 + weights: 0.35631 + weights: 0.45008 + weights: 6.75206 weights: 0 weights: 0 weights: 0 @@ -175,7 +175,7 @@ stat_weights_results: { weights: 0 weights: 0 weights: 0 - weights: 0.03703 + weights: 0.03686 weights: 0 weights: 0 weights: 0 @@ -204,99 +204,99 @@ dps_results: { dps_results: { key: "TestSV-Lvl25-Average-Default" value: { - dps: 220.98437 - tps: 100.43579 + dps: 226.32869 + tps: 100.41993 } } dps_results: { key: "TestSV-Lvl25-Settings-NightElf-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 224.46505 - tps: 111.37639 + dps: 229.45762 + tps: 113.50363 } } dps_results: { key: "TestSV-Lvl25-Settings-NightElf-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 213.18069 - tps: 97.58735 + dps: 219.9321 + tps: 99.07432 } } dps_results: { key: "TestSV-Lvl25-Settings-NightElf-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 272.26595 - tps: 116.71765 + dps: 296.39028 + tps: 122.13163 } } dps_results: { key: "TestSV-Lvl25-Settings-NightElf-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 112.53308 - tps: 60.00971 + dps: 114.48996 + tps: 61.20312 } } dps_results: { key: "TestSV-Lvl25-Settings-NightElf-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 108.18773 - tps: 54.49427 + dps: 110.10097 + tps: 55.36928 } } dps_results: { key: "TestSV-Lvl25-Settings-NightElf-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 135.26891 - tps: 61.68866 + dps: 144.31375 + tps: 64.76151 } } dps_results: { key: "TestSV-Lvl25-Settings-Orc-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 225.03567 - tps: 109.76107 + dps: 229.37991 + tps: 109.82405 } } dps_results: { key: "TestSV-Lvl25-Settings-Orc-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 214.69821 - tps: 96.2685 + dps: 220.96553 + tps: 96.74541 } } dps_results: { key: "TestSV-Lvl25-Settings-Orc-phase1-Basic-p1_weave-FullBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 273.83797 - tps: 114.65836 + dps: 298.54116 + tps: 120.08976 } } dps_results: { key: "TestSV-Lvl25-Settings-Orc-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-LongMultiTarget" value: { - dps: 113.01091 - tps: 59.43931 + dps: 114.91423 + tps: 60.10125 } } dps_results: { key: "TestSV-Lvl25-Settings-Orc-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-LongSingleTarget" value: { - dps: 108.27686 - tps: 53.72537 + dps: 110.5218 + tps: 54.40434 } } dps_results: { key: "TestSV-Lvl25-Settings-Orc-phase1-Basic-p1_weave-NoBuffs-Phase 1 Consumes-ShortSingleTarget" value: { - dps: 134.70515 - tps: 63.15732 + dps: 143.48348 + tps: 65.29829 } } dps_results: { key: "TestSV-Lvl25-SwitchInFrontOfTarget-Default" value: { - dps: 217.81959 - tps: 99.51962 + dps: 222.68336 + tps: 99.52159 } } dps_results: { @@ -309,105 +309,105 @@ dps_results: { dps_results: { key: "TestSV-Lvl40-AllItems-SignetofBeasts-209823" value: { - dps: 906.88493 - tps: 312.21599 + dps: 796.42075 + tps: 349.87491 } } dps_results: { key: "TestSV-Lvl40-Average-Default" value: { - dps: 915.96082 - tps: 319.51415 + dps: 805.52752 + tps: 356.22887 } } dps_results: { key: "TestSV-Lvl40-Settings-Dwarf-p2_melee-Basic-p2_melee-FullBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 1513.85641 - tps: 908.95011 + dps: 1620.39857 + tps: 1028.83995 } } dps_results: { key: "TestSV-Lvl40-Settings-Dwarf-p2_melee-Basic-p2_melee-FullBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 774.64872 - tps: 219.59195 + dps: 661.02909 + tps: 248.03501 } } dps_results: { key: "TestSV-Lvl40-Settings-Dwarf-p2_melee-Basic-p2_melee-FullBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 786.40422 - tps: 230.82291 + dps: 679.68451 + tps: 258.49315 } } dps_results: { key: "TestSV-Lvl40-Settings-Dwarf-p2_melee-Basic-p2_melee-NoBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 771.17863 - tps: 564.9808 + dps: 857.9147 + tps: 634.50205 } } dps_results: { key: "TestSV-Lvl40-Settings-Dwarf-p2_melee-Basic-p2_melee-NoBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 386.20348 - tps: 116.84614 + dps: 332.47728 + tps: 130.94997 } } dps_results: { key: "TestSV-Lvl40-Settings-Dwarf-p2_melee-Basic-p2_melee-NoBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 407.92084 - tps: 120.05064 + dps: 354.11556 + tps: 133.95478 } } dps_results: { key: "TestSV-Lvl40-Settings-Orc-p2_melee-Basic-p2_melee-FullBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 1524.97209 - tps: 906.88328 + dps: 1636.81045 + tps: 1031.03756 } } dps_results: { key: "TestSV-Lvl40-Settings-Orc-p2_melee-Basic-p2_melee-FullBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 783.63486 - tps: 219.47984 + dps: 678.52386 + tps: 249.9923 } } dps_results: { key: "TestSV-Lvl40-Settings-Orc-p2_melee-Basic-p2_melee-FullBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 798.31286 - tps: 234.73257 + dps: 697.92167 + tps: 264.48768 } } dps_results: { key: "TestSV-Lvl40-Settings-Orc-p2_melee-Basic-p2_melee-NoBuffs-Phase 2 Consumes-LongMultiTarget" value: { - dps: 772.38037 - tps: 562.94563 + dps: 859.13143 + tps: 633.41989 } } dps_results: { key: "TestSV-Lvl40-Settings-Orc-p2_melee-Basic-p2_melee-NoBuffs-Phase 2 Consumes-LongSingleTarget" value: { - dps: 388.8289 - tps: 116.46255 + dps: 338.27763 + tps: 131.80767 } } dps_results: { key: "TestSV-Lvl40-Settings-Orc-p2_melee-Basic-p2_melee-NoBuffs-Phase 2 Consumes-ShortSingleTarget" value: { - dps: 414.59527 - tps: 119.47575 + dps: 360.65389 + tps: 134.93067 } } dps_results: { key: "TestSV-Lvl40-SwitchInFrontOfTarget-Default" value: { - dps: 863.08802 - tps: 294.2469 + dps: 762.47415 + tps: 331.9278 } } From 9cd96c6e9eb0e28e3c688d71e00af37f4834c1bd Mon Sep 17 00:00:00 2001 From: ncberman Date: Sun, 30 Jun 2024 02:51:50 +0000 Subject: [PATCH 09/10] simplified carve damage calcDamage --- sim/hunter/carve.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/sim/hunter/carve.go b/sim/hunter/carve.go index b2684b5e29..e049897007 100644 --- a/sim/hunter/carve.go +++ b/sim/hunter/carve.go @@ -61,10 +61,9 @@ func (hunter *Hunter) registerCarveSpell() { for idx := range results { baseDamage := spell.Unit.MHNormalizedWeaponDamage(sim, spell.MeleeAttackPower()) if curTarget == target { - results[idx] = spell.CalcDamage(sim, curTarget, baseDamage * 1.5, spell.OutcomeMeleeWeaponSpecialHitAndCrit) - } else { - results[idx] = spell.CalcDamage(sim, curTarget, baseDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) - } + baseDamage *= 1.5 + } + results[idx] = spell.CalcDamage(sim, curTarget, baseDamage, spell.OutcomeMeleeWeaponSpecialHitAndCrit) curTarget = sim.Environment.NextTargetUnit(curTarget) } @@ -79,10 +78,9 @@ func (hunter *Hunter) registerCarveSpell() { for idx := range results { baseDamage := ohSpell.Unit.OHNormalizedWeaponDamage(sim, ohSpell.MeleeAttackPower()) if curTarget == target { - results[idx] = ohSpell.CalcDamage(sim, curTarget, baseDamage * 1.5, ohSpell.OutcomeMeleeWeaponSpecialHitAndCrit) - } else { - results[idx] = ohSpell.CalcDamage(sim, curTarget, baseDamage, ohSpell.OutcomeMeleeWeaponSpecialHitAndCrit) - } + baseDamage *= 1.5 + } + results[idx] = ohSpell.CalcDamage(sim, curTarget, baseDamage, ohSpell.OutcomeMeleeWeaponSpecialHitAndCrit) curTarget = sim.Environment.NextTargetUnit(curTarget) } From 7bfcd50e21789127f88a9364d2446f9a6f2edc7b Mon Sep 17 00:00:00 2001 From: Kayla Glick Date: Sat, 29 Jun 2024 23:32:05 -0400 Subject: [PATCH 10/10] fix warlock drain life apl value --- sim/warlock/apl_values.go | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/sim/warlock/apl_values.go b/sim/warlock/apl_values.go index 2ece94cc1d..98c2f6da35 100644 --- a/sim/warlock/apl_values.go +++ b/sim/warlock/apl_values.go @@ -55,27 +55,11 @@ func (value *APLValueWarlockShouldRecastDrainSoul) GetBool(sim *core.Simulation) return false } - var curseOfAgonyDuration time.Duration = 0 - for _, spell := range warlock.CurseOfAgony { - if spell.CurDot().IsActive() { - curseOfAgonyDuration = spell.CurDot().RemainingDuration(sim) - break - } + curseRefresh := time.Duration(0) + if warlock.ActiveCurseAura != nil { + curseRefresh = warlock.ActiveCurseAura.RemainingDuration(sim) } - var curseOfDoomDuration time.Duration = 0 - if warlock.CurseOfDoom.CurDot().IsActive() { - curseOfDoomDuration = warlock.CurseOfDoom.CurDot().RemainingDuration(sim) - } - - curseRefresh := max( - curseOfAgonyDuration, - curseOfDoomDuration, - warlock.CurseOfElementsAuras.Get(warlock.CurrentTarget).RemainingDuration(sim), - // warlock.CurseOfTonguesAuras.Get(warlock.CurrentTarget).RemainingDuration(sim), - // warlock.CurseOfWeaknessAuras.Get(warlock.CurrentTarget).RemainingDuration(sim), - ) - hauntRefresh := 1000 * time.Second if warlock.HauntDebuffAuras != nil { hauntRefresh = warlock.HauntDebuffAuras.Get(warlock.CurrentTarget).RemainingDuration(sim) - @@ -83,16 +67,14 @@ func (value *APLValueWarlockShouldRecastDrainSoul) GetBool(sim *core.Simulation) warlock.Haunt.TravelTime() } - timeUntilRefresh := curseRefresh - // the amount of ticks we have left, assuming we continue channeling dsDot := warlock.ChanneledDot - ticksLeft := int(timeUntilRefresh/dsDot.TickPeriod()) + 1 + ticksLeft := int(curseRefresh/dsDot.TickPeriod()) + 1 ticksLeft = min(ticksLeft, int(hauntRefresh/dsDot.TickPeriod())) ticksLeft = min(ticksLeft, dsDot.NumTicksRemaining(sim)) // amount of ticks we'd get assuming we recast drain soul - recastTicks := int(timeUntilRefresh/warlock.ApplyCastSpeed(dsDot.TickLength)) + 1 + recastTicks := int(curseRefresh/warlock.ApplyCastSpeed(dsDot.TickLength)) + 1 recastTicks = min(recastTicks, int(hauntRefresh/warlock.ApplyCastSpeed(dsDot.TickLength))) recastTicks = min(recastTicks, int(dsDot.NumberOfTicks))